SE-0289 (review #2): Result Builders

I think a good name should lend itself well to spoken English:

  • foo has a @discardableResult,
  • Binding is a @propertyWrapper and is also @frozen.

This is probably why @resultBuilder is quite egregious (in both meanings of the word). ViewBuilder is a @resultBuilder is quite accurate, but it doesn't give us much more information about what a ViewBuilder actually is, only that View is the result.

1 Like

I like @expressionCollector. It does describe what is happening: a property/function/closure that looks like it just has a bunch of expressions, will have the results of those expressions "collected" by the collector it is annotated with. Bonus points for not being like any common terms in Swift or other languages, unlike both "result" and "builder". Searching for expression collector on Google didn't turn up anything Swift related on the first page.

If I consider @expressionCollector ViewBuilder {} vs @resultBuilder ViewBuilder {}, the name ViewBuilder tells me it probably will take what I give it and build a view. @resultBuilder tells me it will... produce a result, which I already got from the idea of building a view. @expressionCollector sounds like something else. Something more obvious would be nice but I like the idea of @expressionCollector ViewBuilder {} declaring a thing that "builds views by collecting expressions".

I don't think @resultBuilder would be a bad name. I just like some version of "expression collector" more.

2 Likes

Allow me to deviate away from the "builder" names for a moment. Enough arguments have been made on both sides in the past (whether "builder" should describe what's being built or what's doing the building) that I can't seem to make up my mind about how I feel.

A parallel was drawn above with string interpolation that got me thinking. Interpolation is the insertion of something into something else — resulting in something new. String interpolation allows us to build up strings with a nice, inline, composable syntax. This feature provides a similar interface for building up functions (that eventually produce a result.) So why not call this @functionInterpolation?

4 Likes

I liked this idea at first, but a ResultBuilder doesn't really interpolate functions or expressions into something though, it just gathers fragments and builds something out of them. Using strings as an analogy, it's closer to a StringBuilder in C# than string interpolation.

Ok, so am I understanding this correctly?

This feature, currently named "result builders" is basically taking a block of statements as input and it then outputs a single value. In the process this "result builder" will get the opportunity to include the result of any expressions in the final value, and (optionally) include the chosen path through some control flow statements (like if or switch statements) into the type of the final value. Both transformed as appropriate.

So we are transforming a specific execution of a block of statements into a single value. This sounds a whole lot like any ordinary function. Which I guess makes it hard to name in a non-generic way :)

As I see it, the core of this feature (and I may have misunderstood this) is defined by:

  1. Each expression is sort of a "soft return", making that expression's result part of the final result
  2. The path taken through the closure is part of the result (type)

If you see the type as part of the result, we are basically encoding or serializing an execution of a block of statements into a single value. With the important property that if two executions produce the same expression values, but by taking different control flow paths, then they have different types (depending on the builder).

So I think it makes sense to see this as an "expression flow encoder", encoding the path taken, and all of the expressions emitted through that path.

So ViewBuilder is an expression sequence encoder. Or just an expressions serializer.

@expressionsSerializer public struct ViewBuilder { ... }

2 Likes

I do feel for the @Douglas_Gregor and the others behind this feature. So much of their time and effort is being wasted through people arguing over naming, it feels slightly ridiculous. I just want to chime in and say great job to @Douglas_Gregor and the others, regardless of naming I'm a +1 on this feature as it stands.

13 Likes

I think the naming discussions help people grasping what the feature actually is :slight_smile:

And I very much appreciate the huge amount of work that went into this feature! :+1: :+1:

2 Likes
2 Likes

Come on @Diggory, at least get the attribution right. That quote is 4 years older than the tweet you linked: https://twitter.com/secretGeek/status/7269997868

• • •

In any case, the issues people have raised about the various possible names in the current review, generally revolve around confusability with existing similarly-named but unrelated features in Swift.

The obvious solution then is to find a name that doesn’t overlap with any existing Swift feature, so it cannot possibly cause confusion. To that end, it was observed upthread that what the feature does is collect the results of expressions that would otherwise be discarded, in line with @discardableResult.

Now, anything that would be discarded is, by definition, garbage. So the one true name for this feature is clearly “@garbageCollector”. And as Swift does not already have anything else with that name, it shouldn’t cause any confusion whatsoever.

24 Likes

I disagree. If users come at it the wrong way, they could misconstrue “garbage” as referring to the function and not the result. Referring to the end user’s function as “garbage” would be pejorative. I think instead we should strive for a positive tone for end users. Can we build it? Yes we can! @bob the builder...

This reads very well as an attribute: @bob ViewBuilder { ... }

4 Likes

I can’t help but feel this is a missed sponsorship opportunity - are there any construction companies that might be interested in purchasing naming rights?

We could use the money to fund an implementation of variadic genetics.

14 Likes

Is it April 1st already? Time really flies in quarantine...

4 Likes

@controlBuilder

I'm not too fond of the "builder" part of the name. It's perfect for ViewBuilder, but not for the name of the feature, IMHO.

I also do not like e.g. the buildOptional() name, as it does not build an optional, even if it may build something from an optional.

The feature does allow making types that build results from expressions + structure, or basically a "binary statement tree". So it's a way to create builders. But it's also a way to create other things.

The basic feature could be seen a traversing a statement tree with a visitor. So in this case it could be named a "statement tree visitor".

@statementTreeVisitor public struct ViewBuilder { ... }

  /// Required by every visitor to transform statement blocks into
  /// combined results.
  static func visitBlock(_ components: Component...) -> Component { ... }

  /// If declared, provides contextual type information for statement
  /// expressions to translate them into partial results.
  static func visitExpression(_ expression: Expression) -> Component { ... }

  /// Enables support for `if` statements that do not have an `else`.
  static func visitOptional(_ component: Component?) -> Component { ... }
6 Likes

Hello, I chime in because this is the first name I read in this thread that properly distinguishes the nature of our types from their purpose. Indeed they are a genuine application of the visitor pattern, and it just happens that the most discussed use cases are "builders" (ViewBuilder, HTMLBuilder, etc.)

Using the "visitor" word makes it trivial to derive new use cases that are not builders (counters, aggregators, validators, etc.). The exploration of non-builder visitors has barely started yet.

I know the GoF is not as relevant as it used to be, but "Visitor" is a well-defined pattern that stands on its own and is still relatively well-known.

3 Likes

It's complicated a bit by the fact that it will not visit the complete statement tree in the block, but only those branches that are chosen in the given run of the block. But nevertheless it's a visitor.

First, I still support @resultBuilder as a suitable name. Previous posts outline my thoughts, so I won’t revisit them here.

But I also understand people’s concerns with the name, especially since I initially had many of the same reservations, so I wanted to explore another avenue.

In the previous review @michelf had brought up the possibility of naming the feature @adjectiveBuilder with the qualifier based on some other attribute besides what is being built.

That possible approach has stuck with me since I read it, and to paraphrase @JJJ, the naming discussion has also led me to think more about the nature of the feature itself.

Along these lines, I went back to the first time I learned about this feature, which was in the SwiftUI Essentials session at WWDC 2019.

The session doesn’t reference the feature directly, but it talks about the key benefit that it enables:

This is because SwiftUI defines its views declaratively as opposed to imperatively.

Declarative code involves building a result by describing what you want but letting someone else figure out how to make it for you.

Regardless of the mechanics of how it accomplishes its task, it seems like the central purpose of this feature is to allow a result to be built using a declarative syntax.

With this in mind, I think @declarativeBuilder might also be a suitable name.

The name doesn’t try to explain exactly how it gets its job done, which I think might be impossible to do both accurately and succinctly for a complex feature like this.

But it does tell you the main reason a type with this annotation exists: to allow a result to be built declaratively.

The name also highlights the significant effect this feature has on Swift code, enabling a declarative dialect of Swift that is only valid in the context of one of these builders.

I am not sure of the full implications of staking out the word declarative for use in the name. But, I think it is very unlikely a different declarative builder syntax would be introduced and without a supporting language feature a Swift developer can’t create a different declarative builder syntax. So, I think it is a unique name with little chance of future collision.

(Note that I considered @declarationBuilder, but I think it suffers from the fact that this feature does not create declarations and also that declaration has a very specific definition and central role in Swift grammar.)

I still support @resultBuilder as a suitable name for the attribute and feature.

But I also think @declarativeBuilder might be a suitable alternative that addresses the concerns expressed about @resultBuilder.

11 Likes

If I were to choose between the two, I would much prefer @declarativeBuilder over @resultBuilder as it doesn't sound like something you already know. It will make it clear that there is something unusual happening here :slight_smile:

I'm not sure I would call the builder "declarative", though.

And it's not the result that is declarative either. As I see it, it's the specific builder that is made using this feature that may treat the visited statements in a way that defines a declarative DSL, right?

3 Likes

Is there something in combining the declarative train of thought with the word "container". The struct that we apply this attribute to will at the point of use "contain" instructions for how to build something else? @declarativeContainer maybe?

@declarativeContainer struct ViewBuilder {
    ...
}

Or just @declarative maybe?

@declarative struct ViewBuilder {
    ...
}

I guess if this was a general "statement tree visitor", then is should be able to visit all paths in a given closure. And it should be able to record the conditions of if statements etc.

As far as I can see, it's only really designed to visit the results of expressions, and noting the path taken along the way. I'm not sure what to name that :slight_smile:

I'm still leaning in favor of "statement tree visitor" to describe what the feature does and is. Even though it's not a general statement tree visitor, it's still a type that visits the chosen path through the statement tree of a given code block.

1 Like