Elaborating a bit more now that I feel mentally caught up - there are two main approaches for a DSL feature, both focused on reducing boilerplate to make the code 'feel' like specific language support for their domain.
The first approach is to alter the interpretation of code within a block by altering the default invocation of methods. This is very familiar for people who have Ruby experience - since ruby does late binding of types, the "binding" used to evaluate a block of code can be modified to have a different scope than context of the block. This was actually one of the very first things I pitched - days after SE started - in the form of overriding self within a closure.
More recently and more appropriately specified, this is the tact of the receiver closures pitch. You would pass in a HTML node object. For example, a system in this vein (not the proposed receivers closure syntax) might work as :
var html = HTML {
body {
p("paragraph of text")
}
And would be interpreted as
HTML() { document in
document.body() { body in
body.p("paragraph of text")
}
}
With @dynamicCallable, it is easy to imagine such a system being extended to allow building of say arbitrary xml or html documents with custom tags as well.
The second approach (which I have less experience with, but which function builders adheres to) is more of a code transformation, like generator functions or coroutines. This could be a visitor, a builder, or an accumulator, but the result is that method resolution of your code doesn't change - the execution context does.
This approach imposes some limitations on the HTML example - body and pre have to be resolvable not just within the DSL, but outside as well. This may mean:
- use of the DSL is limited to a type where a protocol, extension, or superclass declares these
- these are declared as functions at DSL-providing module scope
- you instead use P and Body as types, again at DSL-providing module scope. This at least prevents function/method/property name collision
Something like a custom XML builder would require either a more verbose function/method, or a type specific to each tag. While this is a big overhead for a XML builder, this is one of the benefits of this sort of system with SwiftUI - you wouldn't want to have to extend some View type with factory methods for each view you create in your app just to have them usable in a receiver closure-style DSL.
So obviously each approach has its pros and cons for different kinds of DSLs.
ViewBuilder also shows an additional limitation - the result of the DSL in Swift has a built concrete type. This is why the builder function uses static methods, and why other pitches for this sort of approach - like an accumulator or state monad - would wind up being very complex. These also wind up affecting the types of DSLs that could be made with this sort of approach, although in more nuanced ways.
I suppose I'm most curious about whether there is room in the swift language for both style of approaches, or if SwiftUI has pushed us firmly into the 'function builder' style only.