Include argument labels in identifiers

For one thing, this would allow properties which are closures to witness protocol requirements which are functions, which is a feature I’ve wanted for a long time.

If we were to do that today, we’d have to drop the argument labels, since closure properties don’t support them.

4 Likes

Can you explain what you mean by that. Isn‘t this whole feature to add parameter labels back to the call side, even though it‘s sugar.

This isn’t syntactic sugar. This is critical for complying with protocols, conveying clarity at the point of use, and disambiguating between functions.

3 Likes

What do you mean? The call side of the compound properties is syntactic sugar.

var foo(a:): (Int) -> Void
// no sugar
foo(a:)(42)
// sugar
foo(a: 42)

Feel free to correct me if I‘m wrong. :thinking:

protocol FooProtocol {
  func doAThing(using: String)
}

struct ConformsUsingClosure: FooProtocol {
  var doAThing: (String)->Void = { print($0) }
}

let instance = ConformsUsingClosure()
instance.doAThing("where did my label go?")

let erasedInstance: FooProtocol = instance
erasedInstance.doAThing(using: "Okay, now we get a label?")

Since closures don't support argument labels, we'd have to drop the "using:" label when invoking the function in a concrete context. I asked about adding support for this before, and the consensus was that dropping the argument labels was not acceptable (so it's not a technical limitation - it's just something we don't want to support until we can preserve the labels).

If we could instead write this:

struct ConformsUsingClosure: FooProtocol {
  var doAThing: (using: String)->Void = { print($0) }
}

Then we get a nice, consistent calling syntax, and there shouldn't be any objection to allowing the closure to witness the function requirement.

5 Likes

One thing I still do not understand is why it should even work if you only implement a closure with a non compound name, this is name mismatch. I think only your latter form should witness the protocol requirement as it‘s pure sugar over the compound name.

var foo: (a: Int) -> Void
// is sugar for 
var foo(a:): (Int) -> Void
// and is different from 
var foo: (Int) -> Void

Last closure in the example cannot and should not witness func foo(a: Int) from a protocol requirement, but only the first two closures can.

1 Like

Ah, I think you misunderstand - I’m not saying that the first example should work. That’s just all we can do in the language today, since closures can’t have argument labels.

The latter form isn’t valid Swift - if it were (that is to say, if we had the feature this thread is about), then it would be valid Swift, and the closure could theoretically witness the protocol requirement.

So I think we’re on the same page.

1 Like

Indeed I misunderstood you at first, and yes I agree with that direction. I'm also keen to see if this will change something about optional requirement we can have for Obj-C types.

For example:

protocol Foo {
  optional func bar(a: String)
}

// could be pure sugar for
protocol Foo {
  var bar(a:): ((String) -> Void)?
}

extension Foo {
  var bar(a:): ((String) -> Void)? { .none }
}

And it would be cool if optional chaining would be adopted to support foo.bar?(a: "swift") calls.

This one's not going to work because @objc optional requirements have to be compatible with -respondsToSelector:. But on the plus side, the optional chaining syntax you demonstrate already works.

But does it have to be @objc only? I mean, could the above not be allowed as pure swift version of the optional keyword which would be translated into the compound closure?

By the end of the day, if the answer is still "no", then I still would be able to write the above boilerplate-ish code manually to achieve the same, but optional func bar(a: String) would be much cleaner and readable.

And since we love optional customization points that can be satisfied with non-optional implementation from the conforming type, we should not forget that closures with compound names as protocol requirements which should be satisfied by functions as well (even optional closures).

protocol Bar {
  var first(a:): (Int) -> Void { get }
  var second(b:) ((Int) -> Void)? { get }
}

struct S: Bar {
  func first(a: Int) {}
  func second(b: Int) {}
}

Another thought: Can we have get set compound closures as protocol requirements in the future? Maybe with generalized coroutines?

protocol P {
  var foo(a:): (Int) -> Int { get set }
}

struct T: P {
  func foo(a: Int) -> inout Int { ... }
}

Maybe I miss something here. :thinking:

Hm, that's an interesting point. I think they'd still be different, though, because the var version is allowed to change during an instance's lifetime, and the optional version probably wouldn't be.

Historically, the main reason to not bring optional requirements to pure Swift protocols has been because default implementations have generally been considered a better solution for default behavior, but it does make it harder to check for capabilities. There are probably older threads that explain this better than I've been.

2 Likes

Those two declarations wouldn't be compatible.
The second would mean that foo(a:) returns a[n inner] modifiable integer,
while the first would mean that the function foo(a:) itself is modifiable.

This would imply that a {get set} function property cannot be satisfied by a func declaration. The closest thing would be the [non-@objc] dynamic func prototypes I've seen in the Swift commit history.

1 Like

Ah, yeah, I had not enough :coffee: this morning. It probably will be var foo(a:): (Int) -> inout Int { get }. :upside_down_face:

I know I’m late to the discussion, but I’m also confused. What’s being proposed here?
Is it repealing SE-111 and bringing back argument labels to closures, is it adding syntactic sugar to turn this: var foo: (a: Int) -> () into this: var foo(a:): (Int) -> () or is it something else?

Something else: SE-111 removed argument labels from type signatures. This proposal is for adding them to identifiers.

It’s not syntactic sugar, since it isn’t just shorthand for something else.

5 Likes

It's for allowing this:

var foo(a:): (Int) -> Void = { /* ... */ }

foo(a: 42)
1 Like

Has there been any movement on this? If there's nothing in the works already, I'd be interested in taking a stab at a prototype implementation for allowing argument labels in variable names.

8 Likes

Bump. Also very curious if this is really not a problem for the community that there is lack of named arguments for closures?

I wrote up a mostly-complete pitch for this here, and (speaking for myself only) I don't really expect that this would be a particularly controversial change. There are some lingering design questions to settle I think, and a prototype implementation would need to be prepared to bring this to being a full proposal.

I wasn't able to land an implementation back when I initially worked on it, but I fully support someone else picking up this effort. Otherwise, this is on my list of things to pursue whenever I have some free time to dedicate to landing a proposal. :slightly_smiling_face:

6 Likes