SE-0289: Function Builders

The review of SE-0289: Function Builders begins now and runs through September 14, 2020.

Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to the review manager (via email or direct message in the Swift forums).

What goes into a review of a proposal?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift.

When reviewing a proposal, here are some questions to consider:

  • What is your evaluation of the proposal?
  • Is the problem being addressed significant enough to warrant a change to Swift?
  • Does this proposal fit well with the feel and direction of Swift?
  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Thanks,
Saleem Abdulrasool
Review Manager

25 Likes
  • What is your evaluation of the proposal?

-0.5. The feature is dramatically overpowered for the majority of use cases. This in itself is not a problem, but it should coincide with standard library additions that allow for the feature to be used succinctly for the most common cases. This is covered under "Future Directions", but it should be part of the proposal itself.

I believe adopting the proposal as currently described will result in a lot of people writing code that looks like this, as is already ubiquitous in projects linked in the Awesome Function Builders repository:

@_functionBuilder
public struct NodeBuilder {
    public static func buildBlock(_ components: Node...) -> Node {
        .fragment(components)
    }

    public static func buildIf(_ component: Node?) -> Node {
        component ?? []
    }

    public static func buildEither(first: Node) -> Node {
        first
    }

    public static func buildEither(second: Node) -> Node {
        second
    }
}

This is bad code. Forcing people to redefine obvious monoid operations should give one pause. Even the fact that buildBlock wraps the components in ".fragment(_:)" is misleading: what is being built is not an HTML Node, it is an array of HTML Nodes to be added as children to the node created by the calling function. Defining unnecessary weird tree structures is another symptom of functionbuilders as they currently exist.

Most people just want ArrayBuilder. Adopting the proposal without it will make users define countless redundant unspecialized functionbuilders and then despair to remove them once the feature they actually want is made available. The proposal says this could be added with "more experience" but I don't know why more experience is needed when the demand for a much simpler feature set is already apparent.

15 Likes

This is not an ordinary review. This feature has been live though "unofficial" for over a year, and a major closed-source Apple framework is absolutely dependent on its (current) implementation.

I think it is critical that the review manager/implementers/core team provide clear guidelines for the parameters of community review here. Outright rejection is almost certainly off the table regardless of the reception. What aspects are open to critique and possible change before acceptance? What aspects are out of that scope?

45 Likes

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: