Lift limitation that closures must use last shorthand argument

This is an old topic, recognized as a bug by @Erica_Sadun SR-1528, @cocoaphony, @Chris_Lattner3 ("obvious bug"), @Joe_Groff and @John_McCall ("it's dumb") Removing in from Empty Closures, @Paul_Cantrell Related: Make $0 refer to first argument and many others, including @jrose who initially cited reasons for the current state but later acknowledged the problem. @svanimpe who teaches Swift states "My students run into this issue so often that I actually have to include it in my course, and try to sell it as a safety feature" (via bug report). Core team members have admitted it is not an easy fix. From @John_McCall "Yeah, this is just a bug which several people have made various efforts over the last three years to fix. It's not easy."

In any case, it remains a major hole in closure usage. Given multiple arguments using shorthand argument names, the compiler requires the last argument to be used.

Examples from @Joe_Groff "These should all be valid":

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

The current state of affairs also kills code completion until you type the last argument. Sometimes you have to type the last argument on it's own line just so you can get code completion to work temporarily.

So there are two usability problems, (1) the general limitation and (2) code completion.

Fixing this would be non-breaking. Existing code would just work and new code could take advantage of the improvement.

Please fix.

27 Likes

IIRC, there was a thread a few months back where @Douglas_Gregor expressed optimism that some of his work on result builders over the past couple years has left the type inference system better prepared to handle this, so it may be an easier fix now than in the past. Would love to see this restriction lifted!

13 Likes

I sure would love to see this problem fixed. I’ve gone so far as to design APIs around it, making the more-frequently-used argument to closures appear second so that API clients can use only $1 without errors.

2 Likes

Before this sinks to the bottom of the evolutionary pond could a member of the core team provide some indication if or when this might be resolved. Thank you.

1 Like

Last I remember the discussion about it, it seems to be feasible. We just need someone to actually spend resources implementing it.

1 Like

I'm all for this change, but wouldn't it be source breaking? Currently $0 has two possible meanings in some contexts:

    let ff = [(1, 2)].map { $0.0 + $0.1 }

is the same as

    let ff = [(1, 2)].map { $0 + $1 }

If the proposed change goes through, would the upper example stop working?

Btw, this does not work:

let _: (Int, Int) -> Int = { $0.0 + $0.1 }

I suppose it's related to the fact the inferred type for transform in map above is ((Int, Int)) -> Int and not (Int, Int) -> Int.

I think this would continue to work—I was unable to track down the specific post, but I believe the first example is the 'correct' one (since the closure expression ostensibly has type ((Int, Int)) -> Int, which is the element type of the array.

That the second version is supported is, IIRC, a compromise made around the removal of equivalence between the types (T1, ..., Tn) -> U and ((T1, ..., Tn)) -> U. From the archives:

So in the first example, the closure expression is inferred to have type ((Int, Int)) -> Int which matches the type of the argument. In the second example, the closure expression is inferred to have type (Int, Int) -> Int and is then converted to match the expected argument type. I don't see anything about this proposal that would break that system.

2 Likes

It's been one year since this thread was opened, to lift the limitation that closures must use the last shorthand argument. It seems most are in agreement it needs to be done. What are next steps?

4 Likes