SE-0289: Function Builders

I have no delusions that some naysayers could cancel this change, but still: I don't think this is a good direction.

Many DSLs extend the main language, but this completely changes the meaning of code, and some basic instructions even become illegal in the context of function builders.
Despite its complexity, the concept leaves some rough edges in its major showcase (the limit of views allowed in a SwiftUI container).

Simpler alternatives have not been discussed (afaik), and decisions have not been explained as good as they should — and besides adding special cases to the language, this proposal is also a special case on its own: I'm really surprised that it should be compatible with the official evolution process.
Surely, Core knows the rules better than I, but to me, this review looks like a charade.

16 Likes

You mean the stateful builders (which has been discussed a few times, slightly different each time)? Or do you have something else in mind?

I don't think this review is a charade at all, it's just that in the committee decision-making spectrum from bike shed to nuclear power plant, it's over closer to the nuclear power plant side.

SwiftUI can continue being SwiftUI even if this proposal doesn't become an accepted part of the language, so if, for example, @joeyKL's point that the API ought to be simplified for most cases carries a lot of community weight, maybe the proposal gets declined for now until those simplifications are also implemented. Similarly, things like name changes to the proposed API, followed by approval, are hopefully easily accomplished because they are currently all internal to SwiftUI and not part of any Apple published API or ABI.

I don't think any of us are currently contemplating completely reimplementing this feature in a different way, but that doesn't mean that there's no scope for discussion here.

14 Likes

+1 on the feature overall.

I feel that the addition of function builders to the language is a net positive for users. It enables DSLs that feel like a more natural part of the language and its scope of use cases range from libraries like SwiftUI to things such as database schema builders.

I do think that discussions around the ergonomics of function builders are valid and there may be room for simplifying the overhead required when trying to implement a custom function builder, but don't have specific thoughts that have not already been expressed by others in different threads. That being said, the future of Swift on more platforms (eg. server, WASM) together with this feature has me excited for the possibilities that the feature enables.

2 Likes

@John_McCall @Douglas_Gregor How does the implementation differ between the Xcode 12 betas (Swift 5.3) and the development snapshots? Or does it?

3 Likes

The discussion spans quite a long timeframe - could you add some direct links?
In the "pitch", "receiver closures" are proposed as alternatives, but without serious discussion.

Most of the feature is implemented in 5.3, with a few minor additions and fixes in the mainline development snapshots:

  • Support for for..in loops (which call buildArray)

  • Support for throw statements (untouched by the translation)

  • buildDo has been removed

  • The build* functions can now be found in a superclass inherited by the function builder type and in protocols to which the function builder type conforms. (You need this to do something like ArrayBuilder that @JoeyKL refers to here)

    Doug

8 Likes

I like @_fB, it opens a door with huge potential for eDSL extension for Swift lang; html/xml/json/GraphGL ... and many more others can be expressed using DSL plugin.
Super big +1 plus :joy:

1 Like

The feature has been under development for a longer period of time because it is a significant feature for the language, larger than many of the proposals that have gone through the evolution process. This additional experience has helped explore the feature and drive the implementation. Even in cases where the Core Team has strongly implied that a particular feature will be accepted in some form, the Swift Evolution process remains important to get the feature into the right form.

1 Like

Something like this that uses an instance builder object to accumulate the values. There’s a few subtle difference, and I won’t bother digging and listing them all, but the main idea is that instead grouping the whole block via buildBlock, we accumulate data from top-to-bottom. That’s the only different broad stroke I can recall (and you already list the receiver closure approach).

I’m just asking anyway if you have any simpler alternative you’d like to discuss, since it seems you already assume its existence.

Maybe I'm too much of a beginner to Swift and I'm wasting everybody's time, and I should refrain from commenting.

Now I have read the proposal, and I am surprised that there is no description of what a function builder is.
Yes there are code examples presented, but to my opinion they all assume that you share and agree on a common known definition.
The examples propose implementation code, but are lacking the connect to a definition of what a FB is.
(When you look on the net you find hundred variations of what a FB is, but the "Swift definition" of one would be nice and useful).

The community is very critical of Apple documentation, with reason, but it would be nice if we provided better documentation than the mother ship.

Maybe I should just pass on, but I want to improve my understanding of Swift , and hopefully I'm not the only one.

15 Likes

This proposal is a refinement of work that has been ongoing for over a year. Most reviewers are going to be familiar with the existing implementation.

It should still be able to stand on its own, though.

On that note, can we add which functions are required, and which are optionally used at the beginning of each function transformation section? Even after another full reading, I’m still not sure if the buildOptional is required for cases like full if-else tree and switch.

11 Likes
  • What is your evaluation of the proposal?

Big +1.

I’m very much in favour of function builders and the design as outlined in the proposal seems reasonable. It avoids being tied to specific syntactical constructs, especially since the removal of buildDo. The concept of an (optional) internal currency type and an optional result type provides flexibility to DSL developers without imposing them on everyone, but I’m rather neutral on this specific aspect of the proposal. I’m a bit concerned about the builder methods being static, which I discuss below.

  • Is the problem being addressed significant enough to warrant a change to Swift?

Function builders are a major addition to the language (and therefore risky) but its prominent use in SwiftUI has proven the feature’s merit. It’s not merely syntactical sugar — it enables writing succinct code that fits nicely in the mould of a DSL. There are also DSLs outside of SwiftUI using it.

  • Does this proposal fit well with the feel and direction of Swift?

Function builders are clearly an advanced language facility, but I feel that they fit well in the progressive disclosure philosophy. Users of a DSL shouldn’t need to learn anything about function builders except for using the annotation, but the DSL developer can easily explain this through tutorials and documentation.

Funnily, SwiftUI (and the concept of function builders) have defined a new direction for Swift a whole year before this proposal has been put for formal review. Overall, SwiftUI has had a positive reception, or at least I didn’t see a lot of complaints about its DSL. It had (and has) some sharp edges, but they are being addressed (or there’s the intention to address them) through compiler and language improvements that are mostly orthogonal to the function builders feature: for example, one-way type inference constraints and variable generics.

Since day 1, Swift has had several language features that enable (and even encourage) inventing DSLs within Swift, such as custom operators, auto-closures, and trailing closures. Function builders provide a big boost in this space. Just like the previously mentioned features, function builders should be used mindfully, but the possibility of (syntactical) abuse hasn’t been a successful argument against a language feature, as far as I know.

The builder methods are static, which seems a bit un-Swifty to me (and Java-y instead). The builder type (like ViewBuilder) merely acts as a container of methods instead of the type of builders. The @builder syntax doesn’t instantiate a builder whereas property wrappers are instantiated using the @wrapper syntax. I don’t see a reason to introduce this inconsistency in the language. Additionally, some DSLs may enjoy having the flexibility of configuring the builder value on a per-annotation basis. An example can be found in the proposal document under Future Directions.

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

I’ve used (and enjoyed using) Gradle —which uses Groovy’s DSL features— but I didn’t use Groovy for other purposes. I’m not aware of any DSL (non-macro) features from other languages. IMHO, Swift has the potential to popularise DSLs and be an inspiration for the programming community at large.

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

I’ve been following this proposal since last year and I’ve enjoyed writing SwiftUI code. I’m also using @_functionBuilder in some of my projects.

7 Likes

Thanks. I don't mean to criticize the fact that it's been put up for review. On the contrary.

All I mean is that this is a change that has a rather large amount of momentum behind it. I think some clear guidance about the parameters of "get[ting] the feature into the right form" would help to make this a productive review. It would help commenters focus on feedback that will be actually useful to the authors. It would help the authors avoid being pulled into the weeds on points that they are unable to reasonably address in a hypothetical revision.

1 Like

How does it work with @discardableResult currently? Do we treat them as a normal function or a Void function?


In Function Building Method section:

buildFinalResult(_ components: Component) -> Return

Should it be singular component?:

buildFinalResult(_ component: Component) -> Return

Maybe we should just go with either generic term (Block, Either), or Swift-related term (limitedAvailability, Array). It's a little hard to feel that these functions follows a uniform pattern.

Extra bikeshedding:

@functionBuilder => @closureTransformer
buildArray => buildLoop
buildFinalResult => buildReturnValue
build* => transform* ?
2 Likes

Thanks @Douglas_Gregor for including the declarations as potential future thing, that sounds good enough for this proposal, no need to include them right away after all :+1:

I would be happy to see this proposal accepted because of the kinds of libraries we could start to see. I'd love to see more declarative programming in the model layer (could you imagine what the Point Free gents would do with this?), XCTest and so on.

I have sympathy for this view. SwiftUI and all the experimental examples should be enough to have some insight. But I don't think it's within the scope of this proposal. Since Swift is an open source initiative, I do not think it should only be the authors responsibility to provide it. The proposal mentioned @anreitersimon's ArrayBuilder<E>. Perhaps this person might be willing to pitch this addition to the standard library?

  • What is your evaluation of the proposal?

+1

At first, I was against this feature because of how different the semantics are to normal code. But then one of our teammates implemented a Texture (AsyncDisplayKit) DSL using function builders and our views setup code dramatically shrunk and now it's very hard to write new ones without it.

Although, I believe it would still improve drastically to have some way to signal the point where the function builder world starts, at the call site. Something like a keyword for the enclosing closure:

var body: some View { 
    return Button(
       action: { in
           // signals standard execution closure
       },
       label: { with // instead of "in"
           // signals function builder context
       }
    )
}

This can be optional like in, but when it's there it would be significantly easier to differentiate the context switching going around.

  • Is the problem being addressed significant enough to warrant a change to Swift?

Yes, but I understand that not many are affected by this "problem" right now and may not see its use yet. But we are seeing more and more use of declarative UI across the industry, and I think it's very very good that Swift has this tool very early on. As an example, our team was used to the usual UIKit view setup style that it was insignificant for those cases, but we saw how bad our old code were in places where we used Texture (i.e. declarative UI) and how game-changing function builders were for them.

I saw a lot of arguments against function builders that is based mostly on how they dislike the syntax. While consensus should be met regarding that before accepting this proposal, that should not be enough to reject the proposal altogether because the current alternatives are really not ideal.

  • Does this proposal fit well with the feel and direction of Swift?

Yes. While I think there is room for improvement (as I mentioned above), the current implementation is a very good step if Swift is to be used with modern code and modern paradigms (ex: Declarative/reactive UI).

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

I've seen a lot of DSL libraries in Swift, but putting syntax improvements aside, I think they would all benefit from the extra functionality of function builders like if-else handling, optional components in lists, static typing, etc.

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

I've used the feature both in real-world SwiftUI and in my own @_functionBuilders. I had actively been reading past function builder posts in the Swift forums, read the proposal, read earlier posts in this thread.

3 Likes

Given the current state of the proposal, -0.5.

During pitching I brought up two main concerns that were acknowledged but never addressed.

:one: I believe functionBuilder is the wrong name. This feature is not about "building functions" nor defining a "builder of functions". At its heart its about defining a domain-specific language, but could also be viewed as a "source code transformation".

There's nothing in the pitch that describes why the name "function builder" is appropriate, and even the initial definition of "function builder" goes out of its way to clarify what it actually means:

At the very least the pitch is missing the acknowledgement of alternative names and rationale for why @functionBuilder is the best name. And at most, the feature is mis-named.

:two: There is no help provided to a DSL author to know which methods they actually need to implement.

In every other scenario of "an author wanting to supplement behavior", the compiler provides information about what's going wrong on that type that prevents successful compilation.

For protocols, we get errors about not having implemented the properties and methods defined by the protocol. For attributes like @main, failing to provide the static func main() requirement results in a compiler error that: 'MyEntryPoint' is annotated with @main and must provide a main static function of type () -> Void or () throws -> Void.

What analogous help is there here? The proposal says I can decorate a type with @functionBuilder to "make it a function builder", but then an author is immediately abandoned and left wondering "... now what?". The likely answer to this is "read the documentation", yet an ongoing thread here is all about acknowledging the flaws in our documentation. Perhaps the 1.0 version of this will ship with great documentation. But what about 1.1 versions and beyond?

Providing type-based hints from the compiler about "what do I do next" is a raison d'être for Swift, and this feature is completely lacking them.


I like the feature and want to use it, but I don't think this proposed change is good enough.

Mixed. This proposal highlights a massive missing area in Swift: a proper macro feature. I've had several situations where I've wanted to do source-level transformations and generation of code as part of compilation, and it pains me to see, yet again, a one-off solution being baked into the compiler.

N/A

I've been avidly following along and experimenting with function builders on my own.

34 Likes