On top of what John said, my point is that this feature will only be made better by the addition of Variadic Generics. Nothing will be lost. The features are related in use but orthogonal in term of implementation.
Right. An arbitrary limitation of 10 subview expressions with a very unhelpful error message when you exceed that is going to be confusing to a huge number of people. That's why it's important to raise the priority of variadic generics. It would be really good if it was possible to get it in before SwiftUI is released.
As a language-level feature, this feels more like a hack than a natural road ahead for Swift. Something like Kotlin's approach to DSLs should be considered as an alternative in the pitch.
I like the word âbuilderâ in there somewhere. It communicates something useful.
âResult builderâ isnât bad, although the word âresultâ isnât doing much work. If a result is just âsomething that happened because the code executedâ or âsomething other code uses,â what would a builder build other than a result?
I kind of like just âbuilderâ by itself. It works when I try it in context â âa builder collects unused expression results;â âbuilder syntax does not yet support for loops;â âa SwiftUI View builderâ â although the confusion with the more general GoF Builder Pattern seems problematic.
How is this different from other instances of the builder pattern, then? Sugar and conciseness? Thus âconcise builder?â âEmbedded builder?â
The distinguishing feature of this proposal as an approach to eDSLs, the thing that differentiates it from both yielding coroutines and the common Kotlin & Ruby approach of rebinding self in the closure body, is that instead of letting the DSL code execute freely and opaquely pass values back to the caller, this proposal exposes the whole control flow of the DSL closure to the caller. (This feature of the proposal is intriguing, and took a while to sink in. Aside: does SwiftUI use this knowledge of how many conditional branches are possible to automatically handle table cell reuse?) Iâve fished around a bit for a name that captures this, but the results arenât exactly satisfying: âsyntax decorator,â âcontrol flow delegate,â âcontrol-flow-aware builder.â
Builders can be structs and other constructs though. Aside from builder functions, I think functional builders is another good option.
Do I have it right that there's no convenient way to extract part of a DSL expression-sequence for reuse? I'm thinking particularly of an if statement: if I wanted to pull that out to a reusable function, what would that look like?
(Disclaimer: I didn't get a chance to play with SwiftUI yet, which would probably answer this question fairly quickly.)
This is a very interesting addition to the Swift language, but I do agree with many of the concerns voiced here that there are still some rough edges to be worked out.
With all due respect, this feels a bit contradictory to me. As far as I understand it, the whole raison d'ĂȘtre of this proposalâand the rejection of other alternatives like receiver closuresâis SwiftUI's need for type propagation, which most users don't need, so by your logic, we shouldn't get hung up about type propagation, either, because most users don't need it.
Or maybe just "DSL builder"? ![]()
I think you could extract an if statement into a function that returns an optional value for use within a DSL builder block. For example, take this sample code from the proposal:
div {
if useChapterTitles {
h1(chapter + "1. Loomings.")
}
p {
"Call me Ishmael. Some years ago"
}
p {
"There is now your insular city"
}
}
The h1(_:) function above has the following signature:
func h1(_ text: String) -> HTMLNode { ... }
However, I think you should be able to add a new function that returns an optional, instead:
func h1(_ text: String, isVisible: Bool) -> HTMLNode? {
return isVisible ? h1(chapter + "1. Loomings.") : nil
}
You could then replace the if statement in the DSL block as follows:
div {
h1(chapter + "1. Loomings.", isVisible: useChapterTitles)
p {
"Call me Ishmael. Some years ago"
}
p {
"There is now your insular city"
}
}
If I understand the proposal correctly, the result of h1(_:isVisible:) will be included in the div if useChapterTitles is true and it will simply be omitted if useChapterTitles is false.
I've just noticed another limitation of builders - apparently you can't use any helper local variables to prepare some data to be used in view initializers:
var body: some View {
HStack {
let text = model.getText()
// error: Closure containing a declaration cannot be used with function builder 'ViewBuilder'
Text(text)
}
}
Shouldn't Swift be able to figure out that since the result of model.getText() is assigned to a local variable, then it should just ignore it and not try to pass it to the builder?
yuck. Can you use them in the top-most body declaration scope? Ie:
var body: some View {
let text = model.getText()
return HStack {
Text(text)
}
}
(I haven't had a chance to download and play with the betas yet)
Yup, you just need to remember to add the return then. The body of body is a normal method, it's when you get into a closure that's passed to a builder when things start to get weird.
I think everyone should be careful here to evaluate the proposal and not the current implementation in the Xcode beta for SwiftUI. As I understand it, your code sample should be valid according to the rules in the proposal.
While I understand that SwiftUI is not Function Builders, it is a motivating example of the language feature, and currently the only concrete implementation using it. So I don't think it's not relevant to discuss the feature in terms of that implementation...
At the very least, it is perfectly valid to use it as a lens through which to examine uses of the feature, and to point out how shortcomings in such an implementation can cause confusion and poor ergonomics for users of the language.
Well, that's at least reasonably discoverable.
Sure, it's a very relevant example but you shouldn't see the limitations of the current beta implementation as inherent to the feature, and it will get very confusing in here if people start evaluating the proposal on the merits of that implementation rather than the proposal itself.
Picking up on Johnâs terminology here:
Maybe âvalue-capturing closure,â paralleling the grammar of âescaping closureâ and âtrailing closure?â
Or, to avoid confusion with the existing meaning âcaptureâ in the context of closures, maybe âresult-gathering closure?â
I still don't get how this type-level distinction is used. Are there overloads of VStack's initializer with various TupleView type combinations to achieve some optimizations? I haven't seen any, so how is that type information actually used?
I guess knowing the full type of the entire View hierachy at compile time allows the compiler to allocate all the state properties in one block of memory which benefits caching and allows to produce optmized code paths for rerendering on all possible state changes. If you get away without type erasure you do not need any dynamc dispatches.
That's a fair complaint, although I think there are other advantages to this approach beyond what receiver closures can express.
I believe SwiftUI is able to take advantage of the structural type-level information to provide better animations. If you spend some time playing around with it an explicitly introducing type erasure in the right places you should be able to observe this difference. It would be really cool if somebody spends the time to document this behavior, showing code samples and videos of the different animation results. That hasnât been done yet though (as far as I know).
I think the syntax should express the syntax. And the names should express the meaning. You can always encounter code that is very well named but from an unfamiliar application domain. In that case the syntax should be clear anyway.