John Scalo
John Scalo

Reputation: 3401

Accessing only a subset of anonymous closure arguments

I'm having some trouble understanding the usage of anonymous closure arguments. To illustrate I whipped this contrived example up in a playground:

typealias SomeClosureType = (
thing0: Float,
thing1: Float,
thing2: Float,
thing3: Float,
thing4: Float,
thing5: Float
) -> Void

class MyClass {

    var someClosure: SomeClosureType!

    init() {
        // This is OK but long
        self.someClosure = { (thing0: Float, thing1: Float, thing2: Float, thing3: Float, thing4: Float, thing5: Float) in self.handleThing0(thing0) }

        // Compiler error: "cannot assign value of type '(Float) -> ()' to type 'SomeClosureType!'"
        self.someClosure = { self.handleThing0($0) }
    }

    func handleThing0(thing0: Float) {
        print("\(thing0)")
    }
}

let myInstance = MyClass()
myInstance.someClosure(thing0: 0, thing1: 1, thing2: 2, thing3: 3, thing4: 4, thing5: 5)

So basically when I try to access anonymous closure arguments from within the closure, I get this error. What am I missing?

Upvotes: 3

Views: 384

Answers (4)

zneak
zneak

Reputation: 138171

This specific issue comes up periodically on swift-evolution. Chris Lattner said that this is considered a bug in the compiler, but it requires significant effort to fix:

On May 13, 2016, at 9:16 AM, Joe Groff via swift-evolution swift.org> wrote:

This encourages the use of empty closures over optional closures, which I think is open for debate. In general I try to avoid optionals when they can be precisely replaced with a non-optional value. Furthermore, most Cocoa completion handlers are not optional.

The alternative is to not do this, but encourage that any closure that could reasonably be empty should in fact be optional. I would then want Cocoa functions with void-returning closures to be imported as optionals to avoid "{ _ in }".

+1. In general, I think we should allow implicit arguments, without requiring the closure to use all the implicit $n variables like we do today. These should all be valid:

let _: () -> () = {}
let _: (Int) -> () = {}
let _: (Int, Int) -> Int = { 5 }
let _: (Int, Int) -> Int = { $0 }
let _: (Int, Int) -> Int = { $1 }

I agree, but I consider this to be an obvious bug in the compiler. I don’t think it requires a proposal.

Unfortunately it is non-trivial to fix…

-Chris

Until the fix happens, you're stuck with the first form.

Upvotes: 3

Daniel Hall
Daniel Hall

Reputation: 13679

To the compiler, it looks like your closure is only handling a single float parameter, because your closure never references the other 5 "things" in any way. So there is an apparent mismatch and the compiler flags it as an error.

If you reference all 6 input parameters in any valid way, the error will disappear. For example, although this is not something you should ever write, merely referencing the additional parameters like this will be valid:

self.someClosure = { self.handleThing0($0); _ = [$1,$2,$3,$4,$5] }

The shortest / best possible way to express what you are going for is:

self.someClosure = { thing0, _, _, _, _, _ in self.handleThing0(thing0) }

where each _ represents a value you are ignoring, but the compiler can infer that they would be Float values.

Upvotes: 4

Marco Boschi
Marco Boschi

Reputation: 2333

In your second closure you use only $0 so the compiler assume your anonymous closure takes only one argument, taking into consideration the signature of handleThing0(), (Float) -> (), the compiler then knows that the signature of the closure is (Float) -> () as $0 is passed to handleThing0() which doesn't return anything.

You then try to assign your (Float) -> () closure to a property whose type is (Float, Float, Float, Float, Float, Float) -> Void which is impossible as the closure take 5 argument less than requested, hence the error.

Upvotes: 0

Welton122
Welton122

Reputation: 1131

I'm not 100% sure what you are trying to do here, but I'll give it a go at trying to offer some info. With the top one (the long one), that's actually correct. So closures are just functions really, designed to be more compact as little blocks of code within your current scope. If you only intended on using the thing0 property, then you can simply ignore the rest using _, for example:

self.someClosure = { thing0, _ in
   self.handleThing0(thing0)
}

Though you need to be careful when using closures as its really easy to start getting retain cycles, due to ARC and such.

Upvotes: 0

Related Questions