Why can’t Swift’s string constructor be used to convert a generic to a string? - swift

Consider the following example.
func printGeneric<T>(_ input: T) {
let output = String(input)
print(output)
}
This will result in the error cannot invoke initializer for type 'String' with an argument list of type '(T)'. (tested in playground)
However, for some reason, this does work.
func printGeneric<T>(_ input: T) {
let output = "\(input)"
print(output)
}
Why does the first method not work, but the second does? What is the 'proper' way to get the string representation of a generic?

How about
func printGeneric<T>(_ input: T) {
let output = String(describing:input)
print(output)
}
As for why:
The first way, String(x), is for specific situations where x is of a type that can be coerced to String, for use within your program. In other words, it is a type where each value has a string equivalent, like 1 and "1". (See on LosslessStringConvertible.) We don't know whether T is that sort of type, so the compilation fails.
But String(describing:x) is exactly the same as "\(x)" — it is intended for printing anything to the console. It is not a coercion but a string representation.
You could also choose to use String(reflecting:); it is subtly different from String(describing:).

Related

How to solve generic type issue once we introduce one possible conformation to it in Swift?

I am working on this custom function:
func customPrint<T: CustomStringConvertible>(_ value: T...) {
var string: String = ""
value.forEach { item in
string += " " + String(describing: item)
}
print(string)
}
My issue is when I use one type and then use other type! for example this would work fine:
customPrint("hello", "100")
But this would not:
customPrint("hello", 100)
Xcode issue:
Cannot convert value of type 'Int' to expected argument type 'String'
The error is understandable for me, but my goal is to be able use String or Int beside together for feeding my function.
So how can i let my generic works for all types that conform to generic? obviously String and Int conforming to CustomStringConvertible, but they cannot get used both together for my function, looking to solve this issue.
Here what i am trying to solve:
I am thinking to use type Any like Any... where this Any type can conform to CustomStringConvertible.
Since generics constraint the type of the arguments to be uniform, I would suggest doing something like below:
func customPrint(_ value: CustomStringConvertible...) {
var string: String = ""
value.forEach { item in
string += " " + String(describing: item)
}
print(string)
}
Edit:
Thanks to vacawama, Any would also work with String(describing:):
func customPrint(_ value: Any...) {
print(value.map({String(describing: $0)}).joined(separator: " "))
}
When you are defining generic type T, you're actually giving the specific type for parameters that you will pass. If you give String parameter first, the function expects all the parameter types as String. If you give Int parameter first, function expects all parameters as Int. You should not use generic in this function. NoeOnJupiter's answer can be usable in your case.

How do you provide a default argument for a generic function with type constraints?

The following function definition is legal Swift:
func doSomething<T: StringProtocol>(value: T = "abc") {
// ...
}
The compiler is able to determine that the default argument "abc" is a String, and String conforms to StringProtocol.
This code however does not compile:
func doSomething<T: Collection>(value: T = "abc") where T.Element == Character {
// ...
}
The compiler error:
Default argument value of type 'String' cannot be converted to type 'T'
It seems as though the compiler would have just as much information as in the first case to determine that String is indeed convertible to T. Furthermore, if I remove the default argument and call the function with the same value, it works:
doSomething(value: "abc")
Can this function be written differently so that I can provide the default String argument? Is this a limitation of Swift, or simply a limitation of my mental model?
The significant constraint is T: ExpressibleByStringLiteral. That's what allows something to be initialized from a string literal.
func doSomething<T: Collection>(value: T = "abc")
where T.Element == Character, T: ExpressibleByStringLiteral {
// ...
}
As Leo Dabus notes, T.Element == Character is technically not necessary, but removing it changes the meaning. Just because something is a collection and can be initialized by a string literal does not mean that its elements are characters.
It's also worth noting that while all of this is possible, it generally is poor Swift IMO. Swift does not have any way to express what the default type is, so doSomething() in all of these cases causes "Generic parameter 'T' could not be inferred".
The correct solution IMO is an overload, which avoids all of these problems:
func doSomething<T: StringProtocol>(value: T) {
}
func doSomething() {
doSomething(value: "abc")
}
This allows you to make the default parameter not just "something that can be initialized with the literal "abc"", but what you really mean: the default value is the String "abc".
As a rule, default parameters are just conveniences for overloads, so you can generally replace any default parameter with an explicit overload that lacks that parameter.

Swift type inference in methods that can throw and cannot

As you may know, Swift can infer types from usage. For example, you can have overloaded methods that differ only in return type and freely use them as long as compiler is able to infer type. For example, with help of additional explicitly typed variable that will hold return value of such method.
I've found some funny moments. Imagine this class:
class MyClass {
enum MyError: Error {
case notImplemented
case someException
}
func fun1() throws -> Any {
throw MyError.notImplemented
}
func fun1() -> Int {
return 1
}
func fun2() throws -> Any {
throw MyError.notImplemented
}
func fun2() throws -> Int {
if false {
throw MyError.someException
} else {
return 2
}
}
}
Of course, it will work like:
let myClass = MyClass()
// let resul1 = myClass.fun1() // error: ambiguous use of 'fun1()'
let result1: Int = myClass.fun1() // OK
But next you can write something like:
// print(myClass.fun1()) // error: call can throw but is not marked with 'try'
// BUT
print(try? myClass.fun1()) // warning: no calls to throwing functions occur within 'try' expression
so it looks like mutual exclusive statements. Compiler tries to choose right function; with first call it tries to coerce cast from Int to Any, but what it's trying to do with second one?
Moreover, code like
if let result2 = try? myClass.fun2() { // No warnings
print(result2)
}
will have no warning, so one may assume that compiler is able to choose right overload here (maybe based on fact, that one of the overloads actually returns nothing and only throws).
Am I right with my last assumption? Are warnings for fun1() logical? Do we have some tricks to fool compiler or to help it with type inference?
Obviously you should never, ever write code like this. It's has way too many ways it can bite you, and as you see, it is. But let's see why.
First, try is just a decoration in Swift. It's not for the compiler. It's for you. The compiler works out all the types, and then determines whether a try was necessary. It doesn't use try to figure out the types. You can see this in practice here:
class X {
func x() throws -> X {
return self
}
}
let y = try X().x().x()
You only need try one time, even though there are multiple throwing calls in the chain. Imagine how this would work if you'd created overloads on x() based on throws vs non-throws. The answer is "it doesn't matter" because the compiler doesn't care about the try.
Next there's the issue of type inference vs type coercion. This is type inference:
let resul1 = myClass.fun1() // error: ambiguous use of 'fun1()'
Swift will never infer an ambiguous type. This could be Any or it could beInt`, so it gives up.
This is not type inference (the type is known):
let result1: Int = myClass.fun1() // OK
This also has a known, unambiguous type (note no ?):
let x : Any = try myClass.fun1()
But this requires type coercion (much like your print example)
let x : Any = try? myClass.fun1() // Expression implicitly coerced from `Int?` to `Any`
// No calls to throwing function occur within 'try' expression
Why does this call the Int version? try? return an Optional (which is an Any). So Swift has the option here of an expression that returns Int? and coercing that to Any or Any? and coercing that to Any. Swift pretty much always prefers real types to Any (and it properly hates Any?). This is one of the many reasons to avoid Any in your code. It interacts with Optional in bizarre ways. It's arguable that this should be an error instead, but Any is such a squirrelly type that it's very hard to nail down all its corner cases.
So how does this apply to print? The parameter of print is Any, so this is like the let x: Any =... example rather than like the let x =... example.
A few automatic coercions to keep in mind when thinking about these things:
Every T can be trivially coerced to T?
Every T can be explicitly coerced to Any
Every T? can also be explicitly coerce to Any
Any can be trivially coerced to Any? (also Any??, Any???, and Any????, etc)
Any? (Any??, Any???, etc) can be explicitly coerced to Any
Every non-throwing function can be trivially coerced to a throwing version
So overloading purely on "throws" is dangerous
So mixing throws/non-throws conversions with Any/Any? conversions, and throwing try? into the mix (which promotes everything into an optional), you've created a perfect storm of confusion.
Obviously you should never, ever write code like this.
The Swift compiler always tries to call the most specific overloaded function is there are several overloaded implementations.
The behaviour shown in your question is expected, since any type in Swift can be represented as Any, so even if you type annotate the result value as Any, like let result2: Any = try? myClass.fun1(), the compiler will actually call the implementation of fun1 returning an Int and then cast the return value to Any, since that is the more specific overloaded implementation of fun1.
You can get the compiler to call the version returning Any by casting the return value to Any rather than type annotating it.
let result2 = try? myClass.fun1() as Any //nil, since the function throws an error
This behaviour can be even better observed if you add another overloaded version of fun1 to your class, such as
func fun1() throws -> String {
return ""
}
With fun1 having 3 overloaded versions, the outputs will be the following:
let result1: Int = myClass.fun1() // 1
print(try? myClass.fun1()) //error: ambiguous use of 'fun1()'
let result2: Any = try? myClass.fun1() //error: ambiguous use of 'fun1()'
let stringResult2: String? = try? myClass.fun1() // ""
As you can see, in this example, the compiler simply cannot decide which overloaded version of fun1 to use even if you add the Any type annotation, since the versions returning Int and String are both more specialized versions than the version returning Any, so the version returning Any won't be called, but since both specialized versions would be correct, the compiler cannot decide which one to call.

Using shorthand argument with closure in swift? [duplicate]

I was trying to define a variable that can hold a closure and ran into some difficulties using Swift's shorthand argument names. Take the following code snippet:
var returnAString: (String, String) -> String
returnAString = { return $0 }
This gives me the compile error '(String, String)' is not convertible to 'String'. When I modify the closure return a string with the first argument value, I get back both argument values in a tuple.
println(returnAString("1", "2")) // Prints "(1, 2)"
However, if I modify the closure to print the second argument the correct argument is printed as expected.
returnAString = { $1 }
println(returnAString("1", "2")) // Prints "2"
I've got the feeling that I'm doing something a little silly here, but I couldn't find any explanation of why this would be happening.
Closures § Shorthand Argument Names
Shorthand Argument Names
…
the number and type of the shorthand argument names will be inferred from the expected function type.
It looks like this is the expected behavior. By providing only $0 you are inferring (at least to the system) that you want a tuple.
This only seems to be a special case of using $0. For example, the following cannot be compiled.
var returnAString3: (String, String, String) -> String
returnAString3 = { return $1 }
No matter how it's modified.
returnAString3 = { return $1.0 } // still fails.
Type inferral is treating $0 as a tuple instead of the first argument, because you are not using the 2nd parameter.
If you just reference the 2nd parameter instead it works correctly:
returnAString = { $1; return $0 }
A proof that what I am saying is correct comes from 0x7fffffff's comment:
returnAString = { $0.0 }
which means take the first value of the $0 tuple and return it.
To the language, "multiple parameters" is the same as a single parameter that is a tuple. Since only $0 was used, and not $1, etc., the compiler first guesses that there is only one parameter. And that guess is always valid because any function type can be viewed to have exactly one parameter (which may be Void, a tuple, or otherwise).

Why is the shorthand argument name $0 returning a tuple of all parameters?

I was trying to define a variable that can hold a closure and ran into some difficulties using Swift's shorthand argument names. Take the following code snippet:
var returnAString: (String, String) -> String
returnAString = { return $0 }
This gives me the compile error '(String, String)' is not convertible to 'String'. When I modify the closure return a string with the first argument value, I get back both argument values in a tuple.
println(returnAString("1", "2")) // Prints "(1, 2)"
However, if I modify the closure to print the second argument the correct argument is printed as expected.
returnAString = { $1 }
println(returnAString("1", "2")) // Prints "2"
I've got the feeling that I'm doing something a little silly here, but I couldn't find any explanation of why this would be happening.
Closures § Shorthand Argument Names
Shorthand Argument Names
…
the number and type of the shorthand argument names will be inferred from the expected function type.
It looks like this is the expected behavior. By providing only $0 you are inferring (at least to the system) that you want a tuple.
This only seems to be a special case of using $0. For example, the following cannot be compiled.
var returnAString3: (String, String, String) -> String
returnAString3 = { return $1 }
No matter how it's modified.
returnAString3 = { return $1.0 } // still fails.
Type inferral is treating $0 as a tuple instead of the first argument, because you are not using the 2nd parameter.
If you just reference the 2nd parameter instead it works correctly:
returnAString = { $1; return $0 }
A proof that what I am saying is correct comes from 0x7fffffff's comment:
returnAString = { $0.0 }
which means take the first value of the $0 tuple and return it.
To the language, "multiple parameters" is the same as a single parameter that is a tuple. Since only $0 was used, and not $1, etc., the compiler first guesses that there is only one parameter. And that guess is always valid because any function type can be viewed to have exactly one parameter (which may be Void, a tuple, or otherwise).