SE-0279: Multiple Trailing Closures [Amended]

The proposed syntax is a step forward as compared to the previous iteration.

I do appreciate the effort that has gone into making truly multiple trailing closure expressions a reality--for instance, making it easier on the parser when it comes to default:.

I understand the motivation that some closures seem more important than others in a function call and therefore providing some way to elide one argument label. It is certainly elegant in that favorite toy example of ours, when(then:else:).

However, as the review feedback has shown, it is quite possible to provide many examples where there are multiple equally important closure arguments. I would therefore caution against overfitting to APIs we already have, since those have been designed explicitly for a language where only a single trailing closure syntax is available.

Moreover, we have seen examples where it might be optimal to label the closure even when there's only one argument. This is not only for cases where the label might provide call-site clarity. @allevato makes an excellent point that a labeled trailing closure syntax would solve the issue where existing trailing closure syntax can't be used in the condition of an if statement without parentheses:

My principal concern, however, regarding first-closure label elision is the "backward scan" rule.

By construction, if there are multiple closures, and one label may (or, in this version of the proposal, must) be elided, then there must be some rule to figure out which argument corresponds to the unlabeled closure. This is not just a difficulty for the machine, but also for the human reader.

I appreciate that much effort has been made so that it's more predictable or consistent which argument is unlabeled if an additional closure argument is added, or if it is of optional type, or if a default value is added. The degree of explanation required, however, for the reader to determine which argument is the unlabeled closure is mindblowing, as reflected in the length of text devoted to that purpose in the proposal. And if it is to be redone for Swift 6, all the more so.

Certainly, the backward scan rule is not teachable even if one might argue that it is the most usable possible rule. However, I am skeptical than any rule for matching unlabeled closures to arguments, even if optimally redone for Swift 6 in a source-breaking way, would be more usable than not having to have a rule at all (that is, by requiring all labels).

I do not think that the requirement for a language rule, whether "backward scan" or the possible source-breaking future "forward scan," is an adequate tradeoff to avoid repeating the word "animate" at the call site.

For that reason, and for the reasons above, I agree with @jrose that the rule should be (as was proposed in the first review): if you have multiple closures, all must take an argument label; if you have exactly one trailing closure, the argument label may be omitted.

The ability to omit the label for a single trailing closure expression goes some way (in the vein of the 80% rule) to ameliorating the use case where one closure is more important than the rest (limited, obviously, to cases where "the rest" have default values or are optional).

The core team having already decided that the underlying motivation for the proposal does merit a change, I will consider this question to be out of scope for this re-review.

See above.

I have used the trailing closure syntax already present in this language. I made the suggestion during the first review that was the basis for this revised syntax, so I'd say I have some familiarity with the topic (speaking of, some acknowledgment might be nice?). I have read the diff and followed along with the implementation.

21 Likes