−1 from me.
We need automatic, mandatory, high-quality formatting à la gofmt
and not more syntactic sugar.
−1 from me.
We need automatic, mandatory, high-quality formatting à la gofmt
and not more syntactic sugar.
I would not characterize function builders as an "aesthetic language change," given their impact on the language. But I would also point out that function builders are not (yet) an official part of Swift. As to single-line returns, I think you'll find that I've been consistent in my line of thinking.
I do. Why the need for the outer braces? Labeled arguments with trailing colons are necessary for disambiguation as has been amply explained before; but they are (unless I'm missing something) sufficient also.
// Single trailing closure
func foo(bar) { ... }
// Single trailing closure with spelled-out closure label
func foo(bar)
success: { ... }
// Actually multiple trailing closures
func foo(bar)
success: { ... }
failure: { ... }
For proposals like this i would like to hear the opinion of someone who teaches Swift to programmers new to Swift and especially starting programmers. Sometimes syntactic sugar can make learning a language more difficult instead of easier. E.g. i personally think being able to omit the parentheses of the function / method when the trailing closure is the only argument is too much sugar.
If this is a viable option in the grammar I think I'd be for it. It would be cool in control flow use cases if we continue to allow eliding the label of the first trailing closure in a sequence:
when(isNecessary)
{ doSomething() }
else:
{ doSomethingElse() }
I would imagine that one could cook up examples in which the last trailing closure would be the natural one for elision; and then we end up with symmetric situations like success:failure:
where it'd be awkward either way. For the sake of consistency (reminding ourselves that code is more often read than written), I'd be fine requiring all the labels.
I think it would be weird to have a trailing closure follow another one without a label in between:
something {
// first trailing closure
} {
// second trailing closure
}
The reason elision makes sense for the first trailing closure is that sometimes the base name of the method is sufficient to communicate what the closure is doing.
-1 from me. I've read through the thread and I'd like to add another argument against this proposal: added complexity.
Currently curly braces are only used in two cases: enclosing blocks of code and struct/class/enum declarations. This proposal adds a new use case for curly braces and what exactly is it?
Button {
action: {
...
}
label: {
Text("Hello!")
}
}
Outer braces contain something that looks like JavaScript object but unlike anything else in Swift. This will be confusing to new users, especially since benefits compared to current syntax are marginal.
P.S. Closing parentheses are auto inserted in Xcode, which helps with typing mix of }
and )
.
Sorry; I'm not arguing that it's actually feasible to allow elision of the last trailing closure. I'm saying that the semantic implication of your example (when...else
) is such that elision of the first trailing closure would cause the use site to read well, and I would imagine that there are other examples (were it possible to do so without ambiguity) where the semantics would suggest that elision of the last trailing closure label would read well.
Your argument is broadly similar to the argument about treating the first argument differently in Swift 2 in terms of rules surrounding labels. We dropped it ultimately for consistency--and not just for a naive pursuit of consistency for its own sake, but because the experience of using the language proved that it was more usable to have consistency in that case than to have special treatment of the first argument.
I think users would feel similarly here, and for that reason would--all things considered--require all labels.
I... kind of like this. While it doesn't achieve the "syntactically looks like a control flow structure" prerequisite that I was (perhaps too singularly) focused on, it's a significant improvement over the original proposal because it doesn't just swap punctuation—it makes the closures truly trailing.
If a closure argument were unlabeled, would it require an underscore?
foo(bar)
_: {...}
labeled: {...}
Based on @Douglas_Gregor's reply above, I believe it would, but I'd like to offer another possibility: Since multiple closure syntax requires the labels to be spelled out, an unlabeled closure argument should not be allowed to participate in multiple trailing closure syntax.
Why should we have this restriction? This would allow API designers to write functions that could take a single trailing closure but not have spellings that are awkward using the new syntax and have it enforced by the compiler. For example, since Sequence.map
is defined to take an unlabeled argument, you'd have the following:
// These are fine
let x = array.map(someFunction)
let x = array.map { ... }
// This is gross (IMO), and would be forbidden
let x = array.map
_: { ... }
Since Doug is a co-author on this proposal and some other folks on the Swift team have expressed support for this feature, I hope they will seriously consider the alternative syntax suggested by @xwu here.
But we already support elision of the label of the first (and currently only) trailing closure argument. I don't think that aspect of the design is open to change at this point.
Supporting elision of the label for the first trailing closure argument as a general rule is more consistent than elision only when there is a single trailing closure. It also happens to support some control flow use cases extremely well.
I think this rule would be a good one, modulo allowing unlabeled arguments in the first trailing closure position where we should support label elision (which would always be used for unlabeled arguments).
That sounds like a good exception to have, and it does get us closer to DSL-like syntax, with just the colons on the remaining labels being the differentiating factor. (I was somewhat against that in the original pitch thread, but I think the last few posts have brought me around.)
I like this much better then the currently proposed syntax.
Do they need to be on separate lines after the preceding }
? I actually think it gets us pretty close to a control flow structure feel, except for an extra :
after each label which might just be acceptable. The when
example feels very control flow-like to me:
when(something) {
doSomething()
} else: {
doSomethingElse()
}
I hope there would be no requirement of that in order for it to parse. The user should be able to choose this based on whether it makes sense for a particular construct.
TBF, we also currently support elision of the label of the last, and middle trailing closure arguments.
I, too, strongly prefer @xwu’s proposed alternative to the proposal under review.
This is technically true, the best kind of true :)
I'm a -1 on the currently proposed syntax, I agree with the points made by @xwu and others in this thread, I don't think this proposal pulls its weight in its current form.
I think so, with a slightly different syntax we might achieve a very close syntax to control flow for use in DSLs or otherwise.
I don't think it currently does.
I have not.
I followed the proposal thread, read through the comments of this review thread and read the proposal document.
touché. Of course what I meant is that the elision we currently support is in the position of the first closure when multiple trailing closures are allowed. I.e. immediately following the closing paren.
@xwu could goto/break/continue labels interfere in any way with the syntax you proposed?
Also, thinking a little more about that alternative, what about when there is no non-closure parameters, should this work:
Button action: {
...
} label: {
...
}
or should it be spelt:
Button() action: {
...
} label: {
...
}
or should we allow both as we currently do for single trailing closures?
Edit: these discussions should probably be held somewhere else, I'm sorry to clutter this already long thread.
It should not. (That's what the required do
is for!)
Both should be supportable and supported. (If it cannot be supported, that would be an argument for @anandabits's suggestion to allow eliding (or perhaps require eliding) the first closure label--but I don't think there should be a problem.)
What is your evaluation of the proposal?
Pretty firm -1
This adds a significant amount of syntactic complexity, for very little gain. I don't agree that it even adds "multiple trailing closures" - it adds a second parameter list, which in turn removes most of the convenience of trailing closures in the first place. It's worth remembering that trailing closure syntax is supposed to be a convenience feature; meanwhile, the proposal is just as syntactically "heavy-weight" as a regular parameter list.
Is the problem being addressed significant enough to warrant a change to Swift?
I don't think so, and I think the proposal overstates the benefits it could bring to APIs such as UIView's animation methods (see below).
It's also worth considering what regular users would think of this. I saw a thread on the r/swift subreddit the other day about how we can now use keypath literals in place of closures, and one user had this to say:
There's enough added to Swift already that it's just another set of visually clumsy code, but now there is another way to make something and it's supposed to be less visually clumsy, but isn't really.
That stuck with me. In our enthusiasm to make the language better, sometimes we just add too many ways of doing things, and the language as a whole ends up feeling "clumsy". What would regular developers, who don't regularly participate on these forums, think about this proposal? I don't think we're making anything easier or better for them overall. Just more complex.
Does this proposal fit well with the feel and direction of Swift?
I don't think so. This new syntax has none of the convenience of trailing closures, and more complexity than a regular function call. It makes the language more confusing to newcomers.
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?
Participated in the pitch thread, read the proposal a few times and the comments above, thought about it for a bit...
No it won't, though. animateWithDuration
is a nice API for simple animations, but it quickly gets nasty as your animations become more complex, and this proposal wouldn't solve any of the reasons why.
Those reasons have to do with the whole idea of completion-handler closures (which are probably the vast majority of cases where methods take multiple closures, so it's relevant to this discussion). Chaining one animation to happen when another completes results in a "pyramid of doom":
UIView.animateWithDuration(...,
animations: {
// Animation 1
}, completion: {
// Animation 1 completion.
UIView.animateWithDuration(...,
animations: {
// Animation 2
}, completion: {
// Animation 2 completion.
UIView.animateWithDuration(...,
animations: {
// Animation 3.
}, completion: {
// Animation 3 completion.
})
})
})
Which is just plain ugly. So here's how it would look with this proposal:
UIView.animateWithDuration(...) {
animations: {
// Animation 1
}
completion: {
// Animation 1 completion.
UIView.animateWithDuration(...) {
animations: {
// Animation 2
}
completion: {
// Animation 2 completion.
UIView.animateWithDuration(...) {
animations: {
// Animation 3.
}
completion: {
// Animation 3 completion.
}
}
}
}
So... pretty much exactly as ugly.
We have discussed ways to improve this situation before, using coroutines/async functions. The completion handler would become a kind of "continuation" (continuations are actually more powerful than that, but they are a superset of what we use completion handlers for). That would turn the above garbled mess of code in to this:
await UIView.animateWithDuration(...) { /* Animation 1 */ }
await UIView.animateWithDuration(...) { /* Animation 2 */ }
await UIView.animateWithDuration(...) { /* Animation 3 */ }
Notice how we have removed one of the closures entirely, allowing trailing-closure syntax to be used more naturally with the resulting function. Again, I would posit that async functions with completion-handlers make up the vast majority of code which takes multiple closures, making this a widely-applicable transformation and dramatically reducing the utility of the proposed feature (which in turn further weighs against introducing this complexity in to the language).