Function builders

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...

4 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

Swift's current generics can't express a function that takes different views for each of its arguments; that's variadic arguments. You can do it if you're willing to erase type differences, but SwiftUI doesn't want to do that. Like I said, it's not a long-term restriction; this probably increases the pressure to do variadic generics sooner.

19 Likes

That's what I missed, got it.

3 Likes

It is clear that this is the alternative to SE-0257 which I was opposed to. I am in favor of this change but can you add why ExpressibleBy... type of protocol was not adopted for this use case? My biggest reservation on the current proposal is that if statements look as if they are expressions.

2 Likes

I really don't like this. It looks like a totally different language (which I guess is kind of the point... but ugh), and I find it very difficult to read. I think we're trying to shoehorn too many things in to this language.

There's large parts of the proposal which I just flat-out disagree with, like this part:

  • Third, how recognizable is the DSL from the use site? Or, balancing this with the divergence consideration above, What's the expected harm introduced by the DSL for an unsuspecting programmer who needs to understand a use site? In this case, it seems likely that a programmer would recognize that something strange is happening from the large number of statements which normally would have no purpose. Furthermore, if they're at all familiar with common HTML tags, they're likely to immediately grasp what the code must be doing even if they don't understand how it's doing it. In other words, the code in the DSL is quite visually distinctive, as well as fairly suggestive of its behavior. The expected harm is low.

IMO, this falls in the uncanny valley of looking like invalid code (just instantiating a bunch of objects that go nowhere), but actually being valid. I'm very uncomfortable with it. Compare it to, say, a String literal, the contents of which typically don't look like valid code, and indeed are not parsed as code by the compiler or highlighted as code by an IDE.

This seems like a massive, massive amount of harm just to avoid some punctuation characters.

22 Likes

The translation is far more complex than could reasonably be expressed in a literal protocol, and we wanted the result to be an ordinary function.

2 Likes

I'm still processing the pitch, but I just want to say that I've wanted a feature like this for a while, and I'm really excited about it!

5 Likes

This is phenomenal work! It manages to introduce a principled way of creating new DSLs such that Swift can have something like JSX without it being an entirely separate language. SwiftPM manifests could probably have (still could?) benefitted from this feature.

Question: What was the thought behind supporting special treatment of do blocks and buildDo? Clearly, the authors have some use case in mind but I don't know that I understand.

4 Likes

I’m not sure how important that is. I think the idea was that do naturally provides a kind of grouping, which might be relevant for some DSLs, but on balance I’m not sure expressing grouping through do is very fluent compared to calling some sort of group function provided with the DSL.

1 Like

This is a lot to take in, but I have one comment and one question while I digest it.

Comment: I found the Detailed design section to be difficult to understand. I find myself referring back to proposals somewhat regularly, so I'd love to see some more work here to make it easy to understand.

Question: How does this affect composition?

i.e. let's take this code:

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

A natural thing might be to pull out a function that takes 2 strings to create 2 paragraphs:

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

// Okay, this is a bit contrived. But it hopefully gets the point across.
func pp(_: String, _: String) -> [HTML] { … }

Is that possible? I don't see where an array of HTMLs can be added in this way.

What if I wanted to use map instead?

div {
  if useChapterTitles {
    h1(chapter.title)
  }

  chapter.paragraphs.map(p)
}
11 Likes

The HTMLBuilder design in the proposal is really just for exposition, but you're right that the design as given has a problem with composition, and I agree that it would be good to allow multiple elements to be expanded in the parent. Our design choice as the author of HTMLBuilder is between two options:

  • If a statement should be able to just produce a collection of HTML, then we can allow that by just giving an overload of buildExpression that takes an [HTML] (or maybe generically any Collection where Element == HTML).
  • Alternatively, we can require that to be more explicit by, say, defining a function like func include(_ elements: [HTML]) and having it return some private-to-the-DSL type that will be recognized by buildExpression and then unwrapped.
2 Likes