Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift inferred closure parameter puzzle

Tags:

ios

ruby

swift

As a homage to Ruby I've been playing with an extension to Int that allows me to write useful code like this:

3.times { println("I keep holding on") }

This works well and here's the extension:

extension Int {
    func times(fn: () -> ()) {
        for i in 1...self {
            fn()
        }
    }
}

Now, I'd like to pass the iteration number in to the closure, so I added a 2nd times() function to extension:

extension Int {
    func times(fn: (iteration: Int) -> ()) {
        for i in 1...self {
            fn(iteration: i)
        }
    }
}

Which can be invoked like this:

5.times { (i: Int) -> () in println("Year \(i)") }

Now, according to the Swift docs,

It is always possible to infer the parameter types and return type when passing a closure to a function as an inline closure expression. As a result, you never need to write an inline closure in its fullest form when the closure is used a function argument.

That sounds great, because then I can omit the parameter and return types, i.e. (i: Int) -> (), and just use the following syntax instead:

5.times { i in println("Year \(i)") }

But this leads to the following error: Error: Ambiguous use of 'times'

Is this way of invoking my times() function really ambigous to the compiler?

like image 634
augustzf Avatar asked Sep 06 '14 06:09

augustzf


2 Answers

It is ambiguous. Both .times() methods can be used with the given closure expression if the parameter type of the closure is not known.

If you just write { i in println("Year \(i)") }, it is just a closure that takes one parameter of any type. Well, EVERY function type in Swift can be viewed as taking one parameter:

  • What you think of as zero-parameter functions actually take one parameter of type () (a.k.a. Void), of value (), that's why the type is written as () -> something
  • What you think of as multiple-parameter functions actually take one parameter of tuple type, a tuple of all the "multiple arguments", that's why the type is written as (foo, bar) -> something.

So, basically, your closure expression, without specifying the type of i, can be inferred to ANY function type in Swift that returns Void. The functions taken by both .times() methods match this -- for the first .times() method, it is inferred as a function of type () -> (), i.e. i has type (); for the second .times() method, it is inferred as a function of type Int -> (), i.e. i has type Int.

like image 142
newacct Avatar answered Oct 03 '22 20:10

newacct


it looks like the ambiguity derives from the 2 extension methods having the same name. If you comment your first version of the times function, it works fine - if you comment the 2nd instead, surprisingly, you do not get an error from the compiler.

There is no ambiguity in my opinion, because the 2 functions have different signature - () -> () is different than (iteration: Int) -> (). I think it's a bug in the compiler, specifically type inference is failing.

Making the call explicit instead it works fine

5.times { (i: Int) -> () in println("Year \(i)") }

If the first version with the parameterless closure is commented, the line above compiles correctly, if instead the 2nd overload is commented, compilation fails as expected.

To prove that something is wrong in the compiler, this seems to work, whereas I'd expect a compilation error instead:

extension Int {
    func times(fn: () -> ()) {
        for i in 1...self {
            fn()
        }
    }
}

5.times { i in println("Year \(i)") }

This is the output from the playground console:

Year ()
Year ()
Year ()
Year ()
Year ()
like image 32
Antonio Avatar answered Oct 03 '22 20:10

Antonio