Function builders

The trouble is you can only have one self parameter, and closures cannot have self parameters (unless captured). You would have to outline all of your closures as extensions on the builder class / struct. You would also run into trouble if you wanted a builder function to be a method on another struct / class.

1 Like

Interesting approach to a DSL.
Maybe was hoping that Swift gets a macro system instead on which DSLs could be built.
(Maybe along these lines: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.83.859&rep=rep1&type=pdf)
But this is definitely a bigger endeavour.

1 Like

Good points. I'll think on it.

One problem I have with the proposal is that it tends to force implementors to define functions for child nodes that litter the global namespace. Has a solution been considered where the compiler tries to resolve child nodes (p, div, Text, View) as functions and properties of the builder?

17 Likes

I wonder if an alternative has been considered where there’s an implied ā€œselfā€ inside the closure, which is a kind of builder instance on which one could define and scope these functions?

If I understand correctly, Kotlin does something to that effect.

3 Likes

Sorry if these have already been asked. I read through the thread and didn’t see them... apologies if I missed something.

How would this proposal be different if we had Variadic Generics? How would SwiftUI be different? Assuming we get VG, how possible would it be have backwards compatibility with older versions of a library which implements the current ā€œmany overloads of the same function with different aritiesā€ technique?

1 Like

This was my biggest concern as well. I was hoping each call could be an initializer of some kind instead:

struct HTML: Tag { ... }
...

let doc = HTML {
    Meta { }
    P { "The quick brown fox..." }
    Img { "https://.../dog.png" }
}

But the properties idea is just as good. I'm kind of upset this is only being discussed after it was used by Apple in an official manner.

3 Likes

I do discuss the lookup issue in the proposal, but mainly I’d like to remind people that HTMLBuilder is not, in fact, being proposed.

2 Likes

the whole 11 <C1,C2,C3...CN> overloads thing would be unnecessary, as one type-variadic implementation could handle an arbitrary number of heterogenous views. there would not be backwards compatibility, but SwiftUI isn't part of the standard library, and so isn't subject to Swift ABI stability.

that being said, variadic generics have been "just on the horizon" for 2 or 3 years now, so i would manage my expectations if i was you

1 Like

This is exciting!
About the global namespace pollution problem.

If we define the node building functions in your example (div, body ...) in the HTML protocol, won’t we be able to use them in the function closure through an implicit member expression (.div)? If so, what are the drawbacks of this approach?

EDIT:
This is explained in the proposal

Contextual lookups like .p will generally not work at the top level in DSLs because they will be interpreted as continuations of the previous statement.

1 Like

Hi John. I don't think anybody is discussing the HTMLBuilder implementation. We're just using it as example for the proposal.

When you say you discuss the lookup issue int the proposal, do you mean this?

It is common for DSLs to want to introduce shorthands which might not be unreasonable to introduce into the global scope. For example, p might be a reasonable name in the context of our HTMLBuilder DSL, but actually introducing a global function named p just for DSL use is quite unfortunate. Contextual lookups like .p will generally not work at the top level in DSLs because they will be interpreted as continuations of the previous statement. It would be good if there was some way for the DSL to affect lexical lookup within transformed functions (although this might be unfortunate for features like code-completion and diagnostics).

But I'm not suggesting "Contextual lookups like .p". I'm suggesting that:

div {
  if useChapterTitles {
    h1(chapter + "1. Loomings.")
  }
  p {
    "Call me Ishmael. Some years ago"
  }
  p {
    "There is now your insular city"
  }
}

is transformed into:

div {
  var v0_opt: [HTML]?
  if useChapterTitles {
    let v0: [HTML] = HTMLBuilder.buildExpression(HTMLBuilder.h1(chapter + "1. Loomings."))
    v0_opt = v0
  }
  let v0_result = HTMLBuilder.buildOptional(v0_opt)
  
  let v1 = HTMLBuilder.buildExpression(HTMLBuilder.p {
    "Call me Ishmael. Some years ago"
  })
  
  let v2 = HTMLBuilder.buildExpression(HTMLBuilder.p {
    "There is now your insular city"
  })
  
  return HTMLBuilder.buildBlock(v0_result, v1, v2)
}

Basically, I'm asking if its feasible for the generator in the compiler to do lookup of identifiers in the block as static functions or properties on the builder type.

8 Likes

How about this design, that addresses:

  1. Supporting stateful builders
  2. Namespacing builder members to avoid global namespace pollution
return build HTMLBuilder { builder in
  div {
    if useChapterTitles {
      h1(chapter + "1. Loomings.")
    }
    builder.someInstanceState = "some example value" // An instance property that would need to exist on `HTMLBuilder`.
    p {
      "Call me Ishmael. Some years ago"
    }
    p {
      builder.someInstanceState // retrieve the value
    }
  }
}
  • The build keyword, as mentioned earlier, signals "here be magic language dragons"
  • A new HTMLBuilder instance is initialized for this call, which can be used to store and retrieve state.
  • The static functions on HTMLBuilder have been replaced with instance methods, which have access to the state of the newly initialized HTMLBuilder (although HTMLBuilder isn't a good demonstration for this, since it would be stateless anyway)
  • The p, h1, div, etc. global functions have been moved to be instance methods on builder. They also gain access to that state.
    • Symbol look up within this region follows the regular look-up rules, unless it can't find the symbol (e.g. p, in which case it will try HTMLBuilder.p)
  • The outermost closure provide a clearly delineated scope of influence of this magic. Further, it introduces a clear way for the HTMLBuilder instance to be introduced into the source code (even allowing a developer-specified name, rather than hard coding some magic keyword).

This would transform into:

var builder = HTMLBuilder()

builder.div {
  var v0_opt: [HTML]?
  if useChapterTitles {
    let v0: [HTML] = builder.buildExpression(builder.h1(chapter + "1. Loomings."))
    v0_opt = v0
  }
  let v0_result = builder.buildOptional(v0_opt)
  
  builder.someInstanceState = "some example value"

  let v1 = builder.buildExpression(builder.p {
    "Call me Ishmael. Some years ago"
  })
  
  let v2 = builder.buildExpression(builder.p {
      builder.someInstanceState
  })
  
  return builder.buildBlock(v0_result, v1, v2)
}
6 Likes

That is ordinary lexical lookup, which is what the paragraph is about. I’ll revise.

The move from static functions to instance functions doesn't allow the builder type to be generic on the "nodes", which SwiftUI requires.

Is that an answer to my following question? If yes, I don't think I understand the answer.

Hey David,

I'm not quite catching on, if the static functions can be generic why can't the instance methods?

The instance methods can be generic, but SwiftUI requires the builder type to be generic. SwiftUI uses type inference to infer those generic types through the factory static function.

I’m saying that that section is meant to have already answered your question: I think it might be interesting, but I don’t know if it’s feasible, which is why it’s listed as a possible future direction.

1 Like

So, I've been playing around with SwiftUI, the primary reason this currently exists, and it appears that there's no support for switch statements or for/while loops? Is this only a current limitation or is this by design?

1 Like

Yes, they're listed in Future Direction.

Edit:
I'm sure for is not supported, but I get a gist that switch should be supported, may need someone to confirm switch one.