Reputation: 88378
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.
Upvotes: 3
Views: 432
Reputation: 9148
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?
Upvotes: 3