SE-0289: Function Builders

On the annotation at protocol requirement

The annotations at the protocol requirements should only be autocomplete hints instead of direct annotations. This makes them more inline with other decorations like parameter name. So when you declare:

protocol Foo {
  @FooBuilder func foo(@FooBuilder _ result: () -> Foo)
}

The IDEs would suggest

struct A: Foo {
  @FooBuilder func foo(@FooBuilder _ result: () -> Foo) {
   // Uses Builder
  }
}

At the same time, the following function can also satisfy the requirement:

struct B: Foo {
  func foo(@FooBuilder _: () -> Foo) {
    // DOES NOT use builder
  }
}

In this case, whether or not the builder is used is still annotated at the declaration.

I'm strongly against making the leading @Builder inferred from the protocol requirement. @Douglas_Gregor said that it's the same as inferring @Builder for argument closures:

I'd argue that the two are actually different enough.

  • The definition is readily available from the call site (with tooling).
  • OTOH, one can't easily get to protocol requirement from the declaration site.

Furthermore, there actually isn't a strong 1-1 link between a function definition and a protocol requirement. So jump to Protocol Requirement doesn't actually make much sense.

I'm quite concerned about this since we can add infer from requirement but cannot remove them. More so that annotation actually signifies two things:

  • that the builder is being used, and
  • which builder is being used.

We can pretty much know when a builder is used (which was the premise for omitting the annotation at call site), but we don't know exactly which builder is being used. So the builder annotation should be easily accessible, even via tooling.

So I think making protocol requirement annotation an IDE hint would leave us in a pretty comfortable place that we can wait and see if we should add infer from requirement.


On the buildEither

I think that the best behavior for buildEither would be to separate them from buildOptional allowing the fourth scenario with buildEither without buildOptional. This would decouple the absence of value (buildOptional) from the selection among multiple values (buildEither).

The other option would be to require both buildEither and buildOptional for all branching statements to work.

I'm not too concerned about this though. It seems we'd end up in a relatively good spot that can additively include buildEither without buildOptional later (we might even mildly want to remove the case buildOptional without buildEither).


On the naming

If we look at the closure as a descriptor for the return value *Builder does seem appropriate. ViewBuilder builds a single view from the provided block. That may be a perspective we can use to come up with the most fitting name.

2 Likes