I'm wondering if Swift has a way to let you pass in a specific overload of a function as an argument to a second function that takes a closure with a matching signature, based on type, but without explicitly creating a closure yourself.
Note: This is not a problem I'm trying to solve. It's just a curiosity about the language based on something I ran into when creating an extension to a struct that suddenly broke the compile. It was easily addressed with a closure, like below. It just had me wondering if there was another way to solve it.
Here's code showing what I mean. Let's start with this struct Foo
...
struct Foo {
let value: Int
}
Once defined, we can use it as part of a mapping operation via a closure, like so...
let values = [1, 2, 3, 4]
let foos = values.map{ Foo(value: $0) }
However, since the initializer itself already matches the signature of the closure argument type-wise, you can skip the manually-created closure entirely and pass in the function directly, like so...
let values = [1, 2, 3, 4]
let foos = values.map(Foo.init)
What's interesting to note is Swift's compiler finds that match based only on the type of the argument to init
, not it's label, which was required earlier in the closure version.
What I discovered is by defining the following extension somewhere in code-scope view of the values.map(Foo.init)
call site...
extension Foo {
init(valueToDouble value: Int) { self.value = value * 2 }
}
...that call site suddenly gets flagged as ambiguous and it breaks the compile. This is because even though the labels are different, the argument type--Int
in this example--is the same and it doesn't know which one to use.
Now again, this is easily solved with a simple closure, like so...
// Original init
let foos = values.map{ Foo(value: $0) }
// or init in the extension
let foos = values.map{ Foo(valueToDouble: $0) }
I'm just wondering if the Swift compiler has some 'magic sauce' that lets me do something like this...
let foos = values.map(Foo.init(valueToDouble:))
...which obviously doesn't work. :)
So is there anything like that, or is the closure-based version the (only) way to go?
Well, nothing "obvious" about it being wrong because what was wrong is I had a typo (now fixed) and the above syntax does in fact work! Thanks, Itai! :)
In Swift, the "base name" of a method is the method name without any arguments:
Foo.init(x:y:z:)
→ Foo.init
Foo.bar(_:)
→ Foo.bar
Foo.baz(baz:)
→ Foo.baz
Foo.quux()
→ Foo.quux
When referring to a method by name (rather than calling it), Swift will allow you to refer to it by its base name, so long as the usage is not ambiguous:
struct Foo {
func f(intValue: Int) {}
}
let f = Foo().f
However, when there are multiple methods with the same base name, you can run into situations with ambiguity:
struct Foo {
func f(intValue: Int) {}
func f(stringValue: String) {}
}
let f = Foo().f // error: ambiguous use of 'f'
When this happens, you can either:
Use explicit typing to help disambiguate the methods, if possible:
let f1: (Int) -> Void = Foo().f
let f2: (String) -> Void = Foo().f
Or,
Refer to the method by its fully-qualified name (name with parameter names included):
let f1 = Foo().f(intValue:)
let f2 = Foo().f(stringValue:)
In your case, because both methods have the same type signature, you can't use approach (1) to disambiguate between the calls, and will have to resort to (2).
The issue you ran into is that the parameter name you were using was slightly off:
// ❌ Foo.init(doubleValue:)
// ✅ Foo.init(valueToDouble:)
let foos = values.map(Foo.init(valueToDouble:)) // ✅
This does work, and will work as a shorthand instead of having to call the method directly inside of a closure argument.