Function builders

Is it possible to store these in a normal variable of type () -> [HTML]?

In this case I don’t think the annotations will be ostentatious — they’ll typically be hidden in the library on functions like div.

1 Like

I guess that's a fair point, it is actually just in the implementation of the inner details so it's not as verbose as some of the other examples in SE-0258

Yes. Nothing is special about the function dynamically; the transformation is purely a compile-time change to the implementation of the function.

Then can I explicitly do this without passing it to a function? Something like that:

let myHTMLBuilder: () -> [HTML] = @HTMLBuilder {
    //...
}

How would it look for closure variable?

let @HTMLBuilder a = {
  p {
    "Foo"
  }
}

There’s currently no syntax to add an attribute to a closure, but if we added one, it would be easy to make it capable of applying the function-builder transformation (and suppress any transformation picked up from how the argument is used).

In the current proposal, you can add it to a func (or to a var with a getter).

I think this also answers @Lantua's question.

1 Like

I want to verify something about rogue unused result.

I suppose all unused result that's not supported by the @functionBuilder will emit warning? Ie

func div(@HTMLBuilder makeChildren: () -> [HTML]) -> HTMLNode { ... }

div {
  p {
    "Call me Ishmael. Some years ago"
  } // <- this is fine
  3 // HTMLBuilder doesn't have proper `build` function, emit `unused result` warning/error
}

It’ll be an error: the compiler will try to pass the value as an argument (to buildExpression, if it exists, or else to buildBlock directly), and that call will fail if the argument type doesn’t match the parameter.

By default, that diagnostic will be awful, but we think we can do a lot to improve that.

1 Like

I want to voice my concern that there can be accidental unused result going into builder, though I understand that (as is) it'd be minimal.

I think the biggest danger there is probably with things like assert and precondition. I’d love to find some clever way to recognize and ignore them, but it’s tricky.

I think the odds that you have a non-Void unused result that you really don’t want to roll up and can’t reasonably just ignore case-by-case are not high enough to complicate the design over. We definitely don’t want to ignore things in the builder just because they wouldn’t type-check.

2 Likes

How do we deal with @discardableResult, should they be ignored when the type match/doesn't match?

I feel like they should all be tried, at least the fallback doesn't seem too bad.

Aren't they Void functions? I'm not sure I follows.

im pretty -1 on this. idk about you but this seems like a pretty good example of abuse of this feature, and it's pretty telling that this was Apple's own solution.

i'm perfectly fine, even supportive, of gybbed arity to paper over some constraints in the type system, like we do with == on tuples. (i think zip also deserves a few overloads up to say, n = 7.) this feature, however, seems to actively encourage people to do this sort of thing, which i don't like the idea of at all. any markup language (HTML, JSON, etc etc) has the concept of variadic containers,, if the function-builder system doesn't support that, it's functionally useless, except for marketing purposes.

14 Likes

Yea, it feels like it could be some kind of Collection, then again Swift doesn't have enough generic tools to do that.

2 Likes

That’s needed only because (1) SwiftUI wants to propagate sub-view types into the return type to enable some optimizations and (2) Swift doesn’t have variadic generics yet. It’s not a long-term problem, and most other libraries can just use variadic arguments.

2 Likes

A post was merged into an existing topic: IMPORTANT: Evolution discussion of the new DSL feature behind SwiftUI

when people architect their stuff they (or at least i do) try to mimic patterns they see in the standard library and other code that Apple publishes. my guess is we're going to be seeing a lot more of this in the future, and a lot of people wondering why 10 <li> elements is okay but 11 isn't...

5 Likes

Why can't it be generic buildExpression<V: View> though?

I think I'm understand that problem being addressed. My first approach to something like a hard coded - hierarchical tree data structure is use enum types, something like:

enum HTMLNode {
    case div([HTMLNode])
    case p(String)
    ....
}

The missing part here is the flexibility showed in the example with that if statement in the builders body. So maybe we are not talking about hardcoded data structures but the opposite?

Thinking from here I believe this is an interesting approach but there are a couple of things I don't feel comfortable with:

  • The type declaration of the builders didn't match what I would expect as an API consumer, right? If I call p() inside a builder body it changes his behavior compared to call it on another context.
  • Feels like inside a builder things are slightly different than the rest of my program. Why don't just pass a builder as an argument a make some append calls instead of do the exact same thing in my back?
1 Like

I would say that it's better for structures even if they're fully hardcoded, but yes, the flexibility really shines with structures that are only partially hardcoded.

This is addressed in the proposal.

1 Like
Terms of Service

Privacy Policy

Cookie Policy