[Accepted] SE-0279: Multiple Trailing Closures

SE-0279 Acceptance

The second review for SE-0279 – Multiple Trailing Closures has concluded and the proposal as amended for that review is accepted.

The amended proposal was received more positively. Several reviewers who had opposed the original proposal were now unreservedly in favor. Others supported the new proposal overall but expressed a preference for also allowing an argument label on the first closure.

However, many reviewers continued to oppose the amended proposal. Some reviewers expressed their dislike of trailing closures overall, or disputed the need to allow multiple trailing closures. The core team continues to believe, as we stated at the start of the second review, that the underlying motivation for the proposal is sound and changes here are merited. Other reviewers disliked the specifics of the change or felt that it was not tenable without allowing an argument label on the first closure or without substantial changes to the model for type-checking calls with trailing closures.

The core team carefully considered the case for allowing an argument label on the first closure and came to the following conclusions:

  • The core team is not entirely satisfied with the basic design of trailing closures in Swift today. The feature stands out as one of the few places in the language that leaves discretion about the appearance of the call site — and especially the use of a label — to the caller rather than the API author. If we were free to redesign the language without restrictions, we would likely do this feature differently.
  • However, Swift is now an established language, and there are limits to what changes we can justify, even in a new source-compatibility version. A change that only allowed an unlabeled trailing closure to match an unlabeled parameter is very hard to imagine ever being acceptable. We are not ready to completely rule such a change out, but it would need a very strong source-compatibility and migration story.
  • Because of this, the core team feels that these aspects of the single trailing closure design must be taken as given, and that it is better to design multiple trailing closures as a consistent extension of those rules, as SE-0279 proposes, rather than imagining that those rules can be fundamentally rewritten to accommodate a generalization.

The core team agree with the proposal's suggestion that the API guidelines be amended to recommend naming functions "assuming that the argument label of the first trailing closure will be dropped".

14 Likes

So the label of first closure in the final decision was optional or must be dropped?

1 Like

I suggest reading the proposal for clarification of the exact behavior.

I don't want to speculate what other things influenced that decision (time constraints from Apple, etc.).

I wished we'd at least allow the first trailing closure label to be written out explicitly, but instead this is basically the same as saying that "Binding.init(get:set:) should have been designed as Binding.init(_:set:)", which is nonsense if you'd ask me the daily Swift user.

Long story short, I feel disappointed as the proposal was just accepted "as is". :frowning:

Thanks to the core team for reading my feedback (not sarcastic).

28 Likes

Today, you can call that API as Binding(get: { ... }) { ... }, which is obviously terrible, and which we can assume people simply don't do. Under this change, you could also call it as Binding { ... } set: { ... }, which is still fairly obviously terrible (albeit somewhat less so, in my mind), and which we can probably continue to assume that people also simply won't do. I don't see a viable route to a rule that actually forces you to call it as Binding(get: { ... }, set: { ... }) without any sort of new annotation, and if we add such an annotation, it can clearly account for multiple trailing closures as well.

4 Likes

Specifically, it is listed as an alternative that was considered. I have to admit I'm rather disappointed that we don't have the ability to even opt in to labeling the first closure. I thought the case was made convincingly in the review thread that this is an important use case to support.

41 Likes

Thank you for replying. I personally wasn't saying that I'd want any ability to force Binding(get: { ... }, set: { ... }) but rather the sweet spot of the current proposal, which in my mind would look like this:

Binding<...>
  get: { ... }
  set: { ... }

Binding is just one example of many. It doesn't have to be an init like this. The review thread showed other examples similar to this.

I respect the core team's decision, but I don't agree nor understand it.

35 Likes

Of course they will, because that's all Xcode will give them. In order to not get that, they can't use the autocomplete at all. They have to delete the completion token and manually complete the call themselves. This is the same thing that happens with trailing closures now and is why we see them used even when they're less readable than the enclosed alternative.

I, too, am disappointed that there will be no mechanism for disabling this feature or at least requiring the label on the first closure. Not only will a variety of community API suffer under this new syntax, so will Apple's own, including API introduced just last year.

33 Likes

I'll refrain from piling on; this sentiment has been adequately represented.

However, I will say that I am gravely disappointed at the core team's summary of the review discussion:

Others supported the new proposal overall but expressed a preference for also allowing an argument label on the first closure. [Emphasis mine.]

A significantly well received piece of feedback from @jrose and others was for a design where, in the case of multiple trailing cultures, the argument label on the first closure would be required. This is not the same as, nor merely a subset of, the alternative where it be optionally allowed.

It is perfectly fine (and indeed the core team's job) to consider alternatives and prefer one over another. It is not fine for a well received suggestion made during the initial proposal review not to be reflected in the "Alternatives Considered," then for that to be raised again on re-review and not be reflected even in the decision summary. This is bald erasure of the community's voice.

51 Likes

Generally I like the proposal as it improves current syntax but I would be much more satisfied if we could learn from the bad assumption that was made for the trailing closures and do not make this mistake again with the assumption that now the first closure is a good candidate to make the same mistake again.

8 Likes

So bad! Very disappointed about the first closure label decision.
... need time to get used to it...

Binding
  get: { ... }
  set: { ... }

:point_up_2:t2:looks symmetry and elegant, now it's changed to asymmetry form.:point_down:

Binding
  { ... }
  set: { ... }

I like Swift but truly dislike this MTC final revision.

32 Likes

I, too, am pretty surprised that this was accepted. The consensus seemed to be positive only if the first label could be spelled out.

I'm afraid that we'll regret this in the future....

40 Likes

Here is what code-completion produces when you type Binding(:

Binding(get: <#T##() -> _#>, set: <#T##(_) -> Void#>)

If you delete the placeholder, or type over it, we never generate a trailing closure here. You only get the trailing closure if you hit <enter> while selecting the placeholder, which invokes placeholder expansion. In the new syntax the behaviour is the same. The only exception is that if you already have close paren and complete after it, we will expand the closure in the new syntax, since that matches where you completed from.

The motivation for this two step behaviour is specifically that it is easier to start from the regular call syntax with argument labels and get a trailing closure by expanding one than it is to start with a trailing closure and edit it back into a normal call with argument labels.

4 Likes

If I start typing I lose all autocomplete, no? There's no way to recover autocomplete for separate variables, without explicitly naming them, is there?

Really there should be a choice in the autocomplete, and a preference for the default, to choose between completing with trailing syntax and completing the closures separately.

That was an inadvertent consequence of some concurrent editing; our apologies.

2 Likes

Not sure what you mean here. If you type over the first placeholder, you're starting from a state like this

Binding(get: imtypinghere, set: <#T##(_) -> Void#>)

and we will complete things that match "imtypinghere" as usual.

Ah good. At least that case matches the current behavior. For inline closures I do expect most users to just hit return and get the new syntax.

Edit: And my original problem still stands: if users accept the new syntax completion and don't like it, there's no way to automatically get the enclosed version. They'd have to undo the completion and manually type their inline closure parameters. Or at least the first, since doing that manually should unlock the rest of the closures to be completed individually. Ultimately I'd expect users to accept the initial completion, even if it looks bad, since that's the least amount of work.

To expand on this: we did consider the "mandatory label" suggestion, and it is (evidently too obliquely) addressed in the acceptance post by the desire to keep multi-trailing closures as a consistent extension of the single trailing closure rules. In both single and multiple closures, the first trailing closure has no label. In the case of multiple trailing closures, additional labeled closures can then follow the initial unlabeled closure.

3 Likes

Very disappointed, like fieprivate, this would be another nasty trait!
:-1::-1::-1::-1::-1::-1::-1::-1::-1::-1::-1::-1::-1::-1::-1::-1::-1::-1::-1::-1::-1::-1::-1::-1::-1::-1::-1::-1::-1::-1::-1::-1:

1 Like

This post was flagged by the community and is temporarily hidden.

7 Likes
Terms of Service

Privacy Policy

Cookie Policy