Using shorthand argument with closure in swift? [duplicate] - swift

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).

Related

In Swift, how do I explicitly specify a return value for map with anonymous closure arguments?

Say I call map like this, using the anonymous closure argument $0:
array.map {
return $0.description
}
How would I explicitly define that map returns a string? This doesn’t work:
array.map { -> String
return $0.description
}
Contextual type for closure argument list expects 1 argument, which cannot be implicitly ignored
Does that mean if I want to specify a return value I have to name my arguments?
[EDIT: I know I do not need an explicit return type here; still would like how to specify one]
You can use as to identify the type of the anonymous enclosure. In this case you'll need to specify the type of the input as well:
let result = array.map({ $0.description } as (CustomStringConvertible) -> String)
Note: You could use the type of whatever is in array as the input type. Here I just used the CustomStringConvertible protocol since that is what is needed to be able to access the .description property.
or as you mentioned, you can specify the output type if you give a name to the input parameter:
let result = array.map { value -> String in value.description }
Another way to look at it is to note that map returns an Array of whatever type the map closure returns. You could specify that the result of the map is [String] and Swift would then infer that the map closure returns String:
let result = array.map({ $0.description }) as [String]
or
let result: [String] = array.map { $0.description }
The type will be inferred automatically if it's a string , otherwise you need a cast like this
array.map { String($0.description) }

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.

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

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:).

Why don't shorthand argument names work in this Swift closure?

Here's a Swift function that takes in two ints and a three-arg function, and calls the passed-in function.
func p(x:Int, _ y:Int, _ f: (Int, Int, Int) -> ()) {
f(x, y, 0)
}
I can call this just fine using both trailing closure syntax and shorthand argument names, no problem:
> p(1, 2) {print($0 + $1 + $2)}
3
That worked as expected. But in the Foundation library, there is a string method called enumerateSubstringsInRange defined as follows:
func enumerateSubstringsInRange(
_ range: Range<Index>,
options opts: NSStringEnumerationOptions,
_ body: (substring: String?,
substringRange: Range<Index>,
enclosingRange: Range<Index>,
inout Bool) -> ())
Okay, that's easy enough: the function takes three arguments, the last of which is four-argument function. Just like my first example! Or so I thought....
I can use this function with the trailing closure syntax, but I cannot use shorthand argument names! I have no idea why. This is what I tried:
let s = "a b c"
"a b c".enumerateSubstringsInRange(s.characters.indices, options: .ByWords) {(w,_,_,_) in print(w!)}
a
b
c
All good; I just wanted to print out the matched words, one at a time. That worked when I specified by closure as ``{(w,,,_) in print(w!)}`. HOWEVER, when I try to write the closure with shorthand argument syntax, disaster:
> "a b c".enumerateSubstringsInRange(s.characters.indices, options: .ByWords) {print($0!)}
repl.swift:9:86: error: cannot force unwrap value of non-optional type '(substring: String?, substringRange: Range<Index>, enclosingRange: Range<Index>, inout Bool)' (aka '(substring: Optional<String>, substringRange: Range<String.CharacterView.Index>, enclosingRange: Range<String.CharacterView.Index>, inout Bool)')
So what did I do wrong?! The error message seems to say that closure argument $0 is the whole tuple of args. And indeed, when I tried that, that sure seems to be the case!
>"a b c".enumerateSubstringsInRange(s.characters.indices, options: .ByWords) {print($0.0!)}
a
b
c
So I'm terribly confused. Why in the first case (my function p, are the arguments understood to be $0, $1, etc., but in the second case, all the arguments are rolled up into a tuple? Or are they? FWIW, I found the signature of enumerateSubstringsInRange here.
It depends on the number of parameters.
For example,
func test( closure: (Int,Int,Int) -> Void ){
// do something
}
To make test works as you expect, you must specify $2 ( 3rd argument ). The compiler will infer to the values inside tuple, otherwise it will infer to the tuple itself.
If you don't specify $number that match the number of parameters. For example, only specify $1, will make compile error.
// work as expected ( infer to int )
test{
print($2)
}
test{
print($1+$2)
}
test{
print($0+$1+$2)
}
// not work ( infer to tuple )
test{
print($0)
}
// not work ( cannot infer and compile error )
test{
print($1)
}
There is a question relate to this question. Why is the shorthand argument name $0 returning a tuple of all parameters?

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).