Hi Doug,
awesome work on all the recent improvements in function builders, great to see the proposal 
I'd love to bring back the discussion we had about declarations in function builders (there's a writeup with a mock example, though I have a complete DSL which is hurting because of this I can share privately): Function builders and "including" let declarations in built result
Long story short, when trying to port/represent declarative APIs defined in JSON/YAML/XML that often have to "refer to" previously declared elements, it is hard/annoying to express these in today's function builders. Examples of such DSLs would include:
- "schemas" and their use where a table is first declared, and then other elements need to say "I'm using that one
- html where one wants to express "when X is clicked show that other element that I declared previously"
More specifically, today I have to do:
// TODAY:
let thingSchema = Schema(id: "thing") // to have the variable
thingSchema // to make it "visible" to function builders
// forgetting to repeat this `thingSchema` statement leads to it missing
// in the generated schemas, whoops! And it's super easy to miss.
Table(schema: thingSchema)
one proposed workaround was to use nesting, like so:
// TODAY / workaround:
Schema(id: "thing") { schema in
Table(schema: schema)
}
and that's okey for simple or "a few" examples but in the DSLs I deal with there can be tens of schemas, and tables may need to use a few of them, leading to unwieldly crazy nesting levels. And more importantly, the nesting does not represent how one thinks about those domains when declaring them.
What one really would want to write is the following:
// WHAT-IF:
let thingSchema = Schema(id: "thing")
let otherSchema = Schema(id: "other")
Table(schemas: thingSchema, otherSchema) { ... }
Table(schema: thingSchema) { ... }
Table(schema: otherSchema) { ... }
So... I fully understand that one may not want to encourage this style, because most declarations should be pure and just for organization purposes, and not accidentally "emitting" things into the function builder. I do argue that for some DSLs this would be a life-changer.
Also, function builders already are not "normal swift" because if and switch statements do emit values already (and to be honest I'd love them to do so anyway in real swift, and not just function builders but that's a another discussion entirely
).
More details are in the linked thread, but that's the TL;DR; and the proposed addition would be some form of:
public static func buildDeclaration(_ element: ThingElementConvertible) -> ThingElement? {
if let def = element as? Def {
return .importDef(def) // great, include it
} else {
return nil
}
}
which allows the function builder to inspect and by type include or not declarations. E.g. tables I want to import, but I would not import any random let int = 2
since users may want to do those ad-hoc after all.
Looking forward to see what you think about that!