2

In the below example, why is the foo(f) call ambiguous? I understand that the second overload could also apply with P == (), but why isn't the first one considered more specialized, and therefore a better match?

func foo<R>(_ f: () -> R) { print("r") }
func foo<P, R>(_ f: (P) -> R) { print("pr") }

let f: () -> Int = { 42 }
foo(f)   //  "Ambiguous use of 'foo'"
imre
  • 1,667
  • 1
  • 14
  • 28

1 Answers1

2

I'd say your problem is that you don't explicitely tell the compiler that P == ()

try the following code in a playground :

Void.self == (Void).self // true
Void() == () // true
(Void)() == () // true
(Void) == () // Cannot convert value of type '(Void).Type' to expected argument type '()'

Foo<Int>.self == (() -> Int).self // false
(() -> Int).self == ((Void) -> Int).self // false
Foo<Int>.self == ((Void) -> Int).self // true

Since (Void) cannot be converted to (), I guess the compiler can't understand that foo<R>(_ f: () -> R) is actually a specialization of foo<P, R>(_ f: (P) -> R).

I suggest you create generic type aliases for your function types to help the compiler understand what you're doing eg. :

typealias Bar<P, R> = (P) -> R
typealias Foo<R> = Bar<Void, R>

Now you can can define your function like that :

func foo<R>(_ f: Foo<R>) { print("r") } // Note that this does not trigger a warning.
func foo<P, R>(_ f: Bar<P, R>) { print("pr") }

and then use them with any closure you want :

let f: () -> Int = { 42 }
foo(f)   // prints "r"
let b: (Int) -> Int = { $0 }
foo(b) // prints "pr"
let s: (String) -> Double = { _ in 0.0 }
foo(s) // prints "pr"

But you can actually just write :

func foo<R>(_ f: (()) -> R) { print("r") }
func foo<P, R>(_ f: (P) -> R) { print("pr") }

or even :

func foo<R>(_ f: (Void) -> R) { print("r") } // triggers warning :
// When calling this function in Swift 4 or later, you must pass a '()' tuple; did you mean for the input type to be '()'?
func foo<P, R>(_ f: (P) -> R) { print("pr") }

and you get the same results.

AnderCover
  • 2,488
  • 3
  • 23
  • 42
  • 1
    Oh, nice. The key insight I was missing is `(P0..Pn) -> R` vs `((P0..Pn)) -> R`. Thanks a lot. – imre Jun 17 '20 at 03:14
  • (Interestingly, the same tuple deconstruction trick doesn't seem to work in a generic class' init().) – imre Jun 17 '20 at 03:26