A random thought: the proposed syntax could be more useful by enabling a common capture list in the enclosing braces. It makes things more weird due to it not being a real closure but would at least add a new capability.
Xcode's poor formatting is the number 1 reason I avoid trailing closure syntax.
I'll argue for that. Not all parameters are equal when it comes to understanding the purpose of code, so we shouldn't always treat them equally. Emphasizing different parts of the code can help readability. Trailing closures are a tool for that, making it possible to distinguish between parameters that serve as the configuration for the expression as opposed to its body.
When scanning code like this, the parameters in the parentheses are often of secondary importance to understanding its purpose, which in this example is to animate the position and background color of the view:
UIView.animate(withDuration: 1, delay: 0.5) {
self.myView.backgroundColor = UIColor.green
self.myView.center.x += 20
}
The duration or delay isn't what distinguishes this code's purpose. In fact, I can remove the parameters in the parentheses without dramatically changing the structure of the code, and also without changing its purpose, which is still to animate the position and background color of the view:
UIView.animate {
self.myView.backgroundColor = UIColor.green
self.myView.center.x += 20
}
This is a primary motivation behind SwiftUI's design as well: separating configuration from body content to make view code easier to read and understand by emphasizing its hierarchical structure and de-emphasizing its configuration.
For example, below is a view that might make up the content of a list row:
HStack(spacing: 12) {
Image(person.profilePicture)
VStack(alignment: .leading) {
Text(person.name)
Text(person.address)
}
}
I can quickly figure out the overall structure of my view just by looking at the beginning of each line, letting the configuration "melt away":
HStack {
Image
VStack {
Text
Text
}
}
Treating all the parameters equally would make the code more difficult to understand, because it would obfuscate its structure by interleaving less important details:
HStack(
spacing: 12,
content: {
Image(person.profilePicture)
VStack(
alignment: .leading,
content: {
Text(person.name)
Text(person.address)
}
)
}
)
For both the animation and SwiftUI examples, the proposed multiple trailing closure syntax helps preserve that ability to separate the configuration from the body.
To echo something @Douglas_Gregor already pointed out, what concerns me most about the counter proposals that do not have the outer enclosing braces is the wide variety of natural styles for formatting/indenting it, with no clear winner in terms of what is best.
The outer braces provide structure to the expression: there is an obvious way to write it, using common Swift formatting rules, and it clearly identifies the grouped relationship of the closures. There are fewer edge cases to deal with, and more predictable scalability to larger, more complex expressions. So I think the outer braces are critical to reducing the learning curve and improving readability for multiple trailing closures.
You say that this syntax is good for separating more important parameters from less important parameters - but this proposal doesn't separate parameters by their role or importance - only by the fact that they are closures.
Not to pick on you Matt, I'm just using what you said as a jumping off point.
I'm no compiler wizard so please tell me if I'm wrong, but it seems pretty obvious this proposal is tailored towards SwiftUI, so could this new syntax be accessible only from SwiftUI? I'm a tentative -1 on this proposal, but if it was restricted to SwiftUI I could be convinced otherwise.
Closures are used for actions and DSLs so they naturally align as non-configuration parameters. You are correct though that we sometimes build DSLs using other techniques that wouldn't be captured by this. For example a list of enums is used as the DSL for SPM.
Please no, I would be really sad if we would create such language dialects.
Ditto. It'd be best if the proposal applies to common scenarios.
PS.
I still lean toward including any long/multi-line expressions in general. Though closure-only solutions would already help in a lot of places.
Sad, but also, already the status-quo. SwiftUI already mixes two entirely different kinds of closures in a single declaration as in the motivating Button example, The action closure is Swift, the second closure is most assuredly not Swift, it's a DSL dialect defined by SwiftUI in which common Swift language features are not syntactically legal.
This is not really true. ViewBuilder
closures provide an optional syntactic sugar that happens to be used most of the time. You can write normal Swift code in a view builder, you just lose the sugar that builds a return value from the expressions in the body. Instead, you have to create a value of the result type manually and return it explicitly.
The reason function builders only work with APIs that adopt the sugar is because the builder type itself must be specified. That isn't relevant to this proposal.
Yes. I think the SwiftUI team wants to clean up the syntax as this will probably be mature enough to start becoming the dominate UI framework for more developers after this years WWDC (or whatever we have in place of it). I think that is the only reason a sugar only proposal is getting a lot of attention. Particularly since something like this will probably be forced through since SwiftUI is a big deal. The proposal is a big departure from current syntax, so many of us want to see it done without too much disruption to the language. The general view is no sugar-only proposals until Swift has reached a level of maturity where they can be reasoned about without worrying about how they impact future planned features. Basically not until there are no longer manifestos for major features that substantially alter the language. There seem to be some consensus that this sugar may be OK in this instance as long as it is clarifying an existing feature and removing an ambiguity in the language.
EDIT: I think most of us are -1 on this proposal. This has turned in to a discussion about the alternative syntax suggested by xwu and Chris Lattner earlier in the thread.
I guess that's fair, and certainly technically true, but the sugar undeniably introduces a local "dialect" that is specific to that ViewBuilder syntax, making code that intermixes the two more difficult to read.
But you're right. This isn't the place. My apologies.
Having a lot of logic directly in action closures in SwiftUI could be interpreted as a sign that maybe there's a better way to approach your design or architecture. When action closures are kept simple and just route the action to the appropriate handler outside of the view builder then there is no problem with readability.
I think that the proposal in its current form is not a good addition to Swift. Changing the delimiters to braces just seems unnecessary.
Although, I do support the addition of labels to trailing closures as per @Chris_Lattner3's example.
This seems to be in line with Swift's goals and ideals as it adds disambiguation. It also seems important for trailing closures because there are many situations where the use of the label-less closure is unclear and can benefit from a label. Having the ability to have both a label and a trailing closure seems like a big benefit. IMO, one should not have to choose one over the other.
So -1 to this proposal, but +1 to the syntax in the above example.
I think there are cases for both inside and outside the view builder. I'm still working on building experience in this area in my own apps. This is the first declarative UI that I've tried to get in to (outside of playing with FRP in Haskell years ago). Other modern declarative UI frameworks mix actions in like this. Others with way more experience then me have reasoned about this and seem to think it is good design.
Maybe you misunderstand. Of course the UI needs to be able to invoke actions. What I'm saying is that it is usually not a good idea to put a lot of logic directly inline. When nontrivial logic is necessary to handle the action it should be moved elsewhere in your codebase.
Right, but even a few lines of code is enough to make the existing syntax difficult and mixes actions with configuration.
Yes. I hope we hear something about this proposal going back to the drawing board. I like the alternative syntax, but there are definitely some issues that could be worked out in different ways. I'd like to see what they come up with and how elegant it would be in the final form.
I absolutely agree that the syntax could be improved. I supported the original proposal in my review and like Chris's alternative suggestion even more. Nevertheless, the best choice will usually still be to factor logic out of the view builder.
Certainly, but I'm not sure how we would get rid of the action closure. I'd certainly be interested in alternatives. Perhaps some sort of notification alternative to @State for instantaneous actions. However SwiftUI is also used for custom views. These would probably keep much of their controller in the ViewBuilder.