SE-0279: Multiple Trailing Closures

The review of SE-0279 — Multiple Trailing Closures begins now and runs through March 11, 2020.

Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to the review manager (via email or direct message in the Swift forums).

What goes into a review of a proposal?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift.

When reviewing a proposal, here are some questions to consider:

  • What is your evaluation of the proposal?

  • Is the problem being addressed significant enough to warrant a change to Swift?

  • Does this proposal fit well with the feel and direction of Swift?

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Thanks,
Ben Cohen
Review Manager

22 Likes

So, the trailing closures must be in parameter order? In that case, is it OK to have multiple parameters with the same label? What if the parameter has no external label (…, _ handler: …)? Is that spelled _: in the trailing closure?

1 Like

I missed one question, if this proposal has strict transformation rules with a strict order, wouldn‘t it also imply that theoretically the restriction of required labels could be lifted?

func bar(_: () -> Void, _: () -> Void) {}

bar {
  // first trailing closure
  { ... }
  // second trailing closure
  { ... }
}

// transforms into
bar({ ... }, { ... })
2 Likes

Another question, would this syntax allow passing closure variables?

doSomething(to: parameter) {
    success: {}
    completion: variableClosure
}

And to follow up, would this be valid?

doSomething(to: parameter) {
    success: variableClosure1
    completion: variableClosure2
}
6 Likes

Given that this would implemented in the parser and is just sugaring, yes, it appears that those would just be de-sugared to:

doSomething(to: parameter, success: variableClosure1, completion: variableClosure2)

I haven’t checked against the implementation, though. But according to the proposal it should be valid.


I think this looks fantastic and will increase clarity and expressiveness. The UIView animation and SwiftUI Button examples demonstrated this really well.

I do believe that functions with multiple trailing closures have suffered in usability and so yes, I believe that the change is warranted.

The proposed syntax already feels familiar and falls in line with the direction that function builders have taken Swift.

I read the review as well as the discussion thread.

If what I asked would also be eventually allowed then the syntax would almost look like a closure with a function builder:

bar { closure_1; closure_2 }
bar {
  closure_1
  closure_2
}
1 Like

Yeah I'm not sure I particularly like that we're able to do those.
Trailing closures are one thing, but now we have two syntaxes to write non-trailing-closure arguments.

I'll post my review later after giving more thought about this.

Keep in mind that this was only a question if this 'could theoretically' work. It's not being proposed though. The proposal currently still requires explicit labels.

  • What is your evaluation of the proposal?

-1 from me, I don't see how this is an improvement over adding the closures in the function call itself (so the non-trailing version)

  • Is the problem being addressed significant enough to warrant a change to Swift?

No

  • Does this proposal fit well with the feel and direction of Swift?

No

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

N/A

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

I followed the initial discussions and did a quick reading of this specific proposal.

10 Likes

I don't like it. Trailing closure syntax is there to make closure calls look more like standard control flow statements (if, while, etc.) but this proposal doesn't do that.

First, that any call is limited to a single trailing closure, making the feature awkward or even unusable when an API has more than one callback.

I agree

Second, that a trailing closure argument does not provide an argument label, which can lead to call sites that are less clear.

I think the solution to unclear code is less syntactic sugar, not more.

The version with trailing closure syntax separates out the "configuration" aspects of the animation (its duration) from the "action" to be taken (update the background color).

I don't think there's any gain from separating them

It also eliminates the unsightly })

and replaces it with unsightly }}

I think the status quo is good. Trailing closure syntax for simple cases, normal syntax for more complex ones. Two ways of writing function calls is enough, we don't need a third.

27 Likes

What is your evaluation of the proposal?

-1

Is the problem being addressed significant enough to warrant a change to Swift?

No. I think the bulk of positive feedback on the proposed syntax is rooted in the following:

  • Xcode auto-suggests trailing closures a lot more than the standard form. (for cases that apply)
  • Within function builders (e.g. SwiftUI), Xcode's indentation is a lot better for trailing closures than it does for the standard form
  • Xcode does not enforce a newline automatically after the first opening parenthesis "(", while it does so for curly braces "}", which arguably produces more breathable code.

Thus, I think the term "readable" as mentioned by some reflects more on how the current formatter behaves rather than the trailing closure syntax itself being more readable. I will bet that most of the desire for the syntax would fade if Xcode automatically formats multiple closures like this:

UIView.animate(
    withDuration: 1,
    animations: {
        // ...
    },
    completion: { _ in
        // ...
    }
)

If I am not mistaken on this, then the status quo would improve with an update of Xcode (or it's formatter) rather than a new syntax sugar.

Does this proposal fit well with the feel and direction of Swift?

Maybe. We've recently seen a good amount of language features and proposals introducing more uses for curly brackets "{}" like function builders and anonymous structs. But while I have no strong opinions on this, for me a "maybe" is not a strong enough motivation to change the language.

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

N/A

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

I read the proposal, scanned the Pitch thread, and checked my own code to reflect (which lead to my initial questions earlier in this thread)

43 Likes

Disclaimer: As this is a purely code style proposal my opinion is solely based on my personal code style preferences and what I personally find "readable" or not.

What is your evaluation of the proposal?

-1

Is the problem being addressed significant enough to warrant a change to Swift?

I personally don't see significant improvement compared with what can be done already, on the other side it introduces new way of specifying parameters to functions.

Trailing closure is already a controversial syntactic sugar, which is as well not applicable in all the cases, i.e. it's not allowed in if-else and other control statements as conflicts with the syntax to define code blocks, and I don't see yet how proposed solution will address this, so the feature will be still limited.

Also I'm personally against Xcode suggesting trailing closures by default as was mentioned before, this imposes particular code style and at least could be configured with code editor settings. I constantly find myself fighting this and trying to avoid using trailing closure where I don't want it. Even with this feature I don't think I will be keen on using trailing closures like that, I'd prefer to pass parameters to the functions in one clear way.

Even with simple things like map, filter etc. functions where trailing closure feature fits well (maybe?) in my experience I find myself more and more not using it as instead I use more functional constructs like filter(equals(otherValue)) or map(\.keyPath) which does not require different syntax for passing closure parameters.

Formatting issues mentioned in alternatives section of the proposal would be better solved with automatic code formatting improvements in Xcode or other formatting tools rather than introducing new syntactic sugar that does not really simplify writing code enough in my opinion.

It's fair to say that extra brackets and other syntax elements only add noise but the proposal does not decrease this noise significantly.

I'm also concerned that this adds unnecessary complexity to parsing such code with any 3rd party tool as it adds yet another variation, where single trailing closure is already complex enough case. For instance how this will affect working with SwiftSyntax?

Does this proposal fit well with the feel and direction of Swift?

If the direction is to rely more and more on {} then I feel we will end up with Swift becoming some other language.

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

not that I'm aware of

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

read the proposal

5 Likes

I'm also -1 on this. @JohnEstropia has summarized it pretty well. I've always solved the multiple closure arguments problem with indentation, and adding a new syntax for this problem doesn't seem motivated well enough to me.

3 Likes

What is your evaluation of the proposal?

-1, while this seems reasonable when looked at in isolation, it seems at odds with how named parameters, commas, and control statements are used in the wider language

Is the problem being addressed significant enough to warrant a change to Swift?

Potentially, the problem statement in 'Motivation' makes some good points, however there are a variety of ways this problem could be approached - from language change to a change in formatting conventions - and I don't feel that other avenues have been explored thoroughly enough.

Does this proposal fit well with the feel and direction of Swift?

No, syntactically it seems an 'uncanny valley' compared to existing syntax and formatting conventions.

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

n/a

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

I read the proposal, and this thread.

1 Like

I am +1 on this proposal.
When calling a function with several closures, I've felt that it's needed to not use trailing closure syntax because it gets confusing. And keeping track of commas and brackets is just tedious busy work.
I think the proposal will make the code better.

Is the problem being addressed significant enough to warrant a change to Swift?

I think so. Trailing closures are meant to be lightweight, so improving their syntax warrants a change.

Does this proposal fit well with the feel and direction of Swift?

Yes, the resulting code feels swifty.

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

N/A.

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

I've read the pitch thread and the proposal.

2 Likes

The closure situation with animateWithDuration syntax have been in Swift since Swift 1.0, and I don't hear many complaints about it. It's not offensive, and there's an argument that the API should have been designed differently (and could still be designed differently, like taking a configuration type) to improve usage.

The when example is unsatisfying as true Swift control flow would not label the then block and the condition wouldn't require enclosing brackets; it would just be when condition { // do something } else { // something }. The proposed sugar is an improvement over the status quo, but is it really enough to justify the language complexity?

The best motiviation in the proposal is the SwiftUI Button case, because the indentation of braces allows the Button initialiser to look nice next to every other brace-delimited block in the SwiftUI DSL.

I guess this gets to the root of my review. I can see the merits of adding this sugar but I also think it's too early. SwiftUI function builders are not even formally implemented yet, and we are already coming forward with sugar to make declarations inside the function builder DSL look nicer. SwiftUI is just a few months old and who knows what 'SwiftUI 2' will look like in four months time. Even without a second version of the framework, community SwiftUI conventions are still immature — and developing.

I think rushing out this sugar would be a mistake. I wouldn't be opposed to revisiting the idea further down the road.

20 Likes

+1

The proposal is substantively unchanged from the preceding pitch, so I will reiterate my comment:

I see the appeal of trying to make a feature more general, but in this case I have doubts about whether this pulls its own weight.

In the animation example, for instance, the status quo is--

transition(with: view, duration: 2.0,
  animations: {
    ...
  },
  completion: {
    ...
  }
)

/* versus the pitch here:
transition(with: view, duration: 2.0) {
  animations: {
    ...
  }
  completion: {
    ... 
  }
}
*/

--so the pitch offers a second spelling that isn't any lighter at all, just different. (By contrast, in the case of single trailing closure syntax, it's sugar that does make the call site lighter both by dropping the label and by avoiding nesting two 'layers' of punctuation: neither is achieved here.)

One other advantage of the trailing closure syntax is that it gets us closer to something approximating native control flow syntax, useful for DSL-related situations.

However, as you show with your example of when , the else block is counterintuitively nested within the condition that it's supposed to contradict using your proposed spelling, which far from approximating native if...else is totally alien to it.

Moreover, it seems these blocks are easily confusable with labeled do blocks; I imagine it could actually inhibit the compiler error we have today to remind users to add the missing keyword do when they label naked braces. Granted, this isn't a commonly used feature, but I think it's a double drawback that when...else both can't resemble native control flow syntax that ideally it would resemble and must resemble native control flow syntax that ideally it wouldn't resemble.

Ultimately, what's pitched here suffers from not having a solution that actually offers multiple trailing closure expressions, but rather a single trailing expression that contains multiple closures.

But enough of bikeshedding the actual spelling. I think overall my bigger concern is related to the first thing I've commented on. The idea imposes the burden of learning a second syntax for something that's already expressible, a syntax that isn't purely a natural extension of the single trailing closure expressions we already have.

33 Likes
  • What is your evaluation of the proposal?

I'm a big fan of this proposal. Lack of this feature has been a rough edge in Swift since the beginning. I'm happy to see a clean solution is coming.

One advantage of this proposal that isn't mentioned explicitly is that it eliminates the need to choose a formatting style for these calls. If multiple trailing closure syntax is accepted, I imagine it would become the pervasive convention in the Swift community. Nobody would spend time thinking about or discussing how to format these calls anymore. This will eliminate a small but meaningful bit of friction when reading code written by others.

I do have one question: can this syntax be used with a single trailing closure argument? This would be nice in cases where you don't want to elide the label but do want to take advantage of the "configuration argument" separation that trailing closure syntax affords.

  • Is the problem being addressed significant enough to warrant a change to Swift?

Yes. The current syntax works, but it's pretty awkward to both read and wrtie.

  • Does this proposal fit well with the feel and direction of Swift?

Very much so. In Swift we value clarity extremely highly. This is one of the areas where the language has let us down a bit in terms of clarity.

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

n/a

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

This is a rough edge that has bothered me for years. I gave both the discussion thread and proposal a quick read and am happy with the solution. The alternatives appear to have been explored well and there is strong rationale for the chosen design.

1 Like

Could you explain how it eliminates that? For me adding more options (especially ones that differ with just a couple characters, like xwu shown) means more thinking, not less.

5 Likes