SE-0289: Function Builders

Could someone confirm if/when buildLimitedAvailability is added? Xcode 12.0 Beta 2 (12A6163b) keeps using normal buildEither.

[5.3] [Function builders] Use buildLimitedAvailability() for #available block by DougGregor ¡ Pull Request #32995 ¡ apple/swift ¡ GitHub was merged on 21 July, so I think it should be in beta 4 or 5.

1 Like

Could also call it @semanticBuilder.

Using an adjective before "builder" implies how it works instead of what it builds. Your type collects values produced by statements and can impose restrictions on those value's type. It can restrict certain control flows and take note of which branches are taken. And thus it reshapes a function's semantics to fit the builder pattern.

It was after that—possibly beta 5, but don’t hold me to that.

1 Like

Since every builder type defines its subset of a DSL that uses functions, isn‘t an appropriate attribute for the proposed feature @functionDSL?

I still think that the following package remains valid in terms of their semantical names.

@functionDSL
struct ViewBuilder { ... }
1 Like

If this feature is to aid use of (embedded) domain-specific languages, why don't we call the feature "domain specifiers" and use "@specificDomain" as the attribute.

If you really like the "Builder" theme naming, what about: "@domainBuilder" instead?

Or, if you ignore my "domain specifier" idea: "@buildTransformer"?

I think it should use instance-level code, instead of the current type-level. That way, builders can have instance-level customizations (instead of global per type).

1 Like

Sometimes availability is used to include something new without an alternate value.

For instance, I might want to add a SignInWithAppleButton, added in iOS 14, when it is available. It isn't replacing the 'sign in by email' button.

var body: some View {
    VStack {
        if #available(iOS 14.0) {
           SignInWithAppleButton()
        }
        Button("Sign In With Email", action: signInWithEmail)
    }
}

It doesn't seem like the buildLimitedAvailability method would handle this case. Is this usage disallowed?

If I understand correctly, a variant of buildLimitedAvailability that returned a type-erased optional would be required.

Is my thinking correct? If so, it seems like both cases should be made possible.

limitedAvailability only wraps the block of the then case. The whole branching blocks are still subjected to normal if-else transformation (including case of if without else).

1 Like

Could you expand on how that would work? I’m having a hard time wrapping my head around how instances would complete what (unless I’m misunderstanding the whole thing) is a compile time task.

The transformation happens at compile time, but the code it produces is executed at run-time. Because the function-builder hooks are declared as static functions, it’s not easy for them to contain their own state.

The reason you might want this is if you have something like a ViewBuilder; adding an instance variable allows you to change the output only for that particular ViewBuilder.

1 Like

Okaaaay… Could you provide a use case in the context of SwiftUI were this implemented in instance methods? I still don’t quite understand the benefit, but I assume you have a hypothetical in mind.

You could have a builder that had a maximum size, and, when it had collected too many elements to draw in that size, just turned all components into some kind of "ellipses" placeholder.

A builder that was parameterized to decorate its collected views with a border, or a tint (or had finer grained options to decorate some views).

An XML builder that had a couple of options for how to format raw data.

A test case builder that could be given an environment to look up the values needed in its tests.

Anything where you want the results to depend either on the whole input (rather than each piece individually) or on an outside value.

1 Like

You can already gather external information using closure arguments, and many of the decorated behaviours can be achieved by a literal Decorator pattern:

MakeGlitter {
  ...
}

which is essentially how SwiftUI.EnvironmentObject operates. SwiftUI also performs the actual layout outside of the ViewBuilder, and I'd expect other frameworks to follow suit (it actually works better to just treat the resulted value as a descriptor).

+1

this proposal allows the creation of a new class of embedded domain-specific languages in Swift by applying builder transformations to the statements of a function.

Alternative names:

BuilderTransformer
FunctionTransformer
FunctionTransformerBuilder
BuilderFunction

Yes, but should that be baked in to the design?

Personally I think it should be possible to do something simple like enabling logging for a specific builder without it applying to all uses of that type. That means we need to allow building using a particular builder instance.

If SwiftUI wants to use a different pattern, that’s up to them.

1 Like

Do you mean that the choice of a builder instance is static or dynamic? If it is static, i.e.

func foo(@FooBuilder("parameter1") _: () -> Foo)

Then it's already possible (admittedly with some difficulty) using generic:

func foo(@FooBuilder<Patameter1> _: () -> Foo)

struct Parameter1: FooBuilderParameter {
  static var parameter1: String = "parameter1"
}

I don't think we should encourage this level of configurability. It's quite hard to reason with if the parameters keep changing at each level of nesting. In so far, we can somewhat infer the builder used from the context "this block uses builder with View, it's probably using ViewBuilder."

If you mean that the builder is dynamic and th caller supplies it to the callee, you'd need to flesh out more design. I couldn't think of one that is close to the current design.

John,

I think this is going to be more widely used than that. When we write apps we often first create the language we write our apps in - Function Builders fit that need quite well. I think many teams will use them (or be tempted to). I agree that the majority of Swift Developers won't be writing these but I don't think it's a small number of library writers.

1 Like

Both cases are possible. buildLimitedAvailability only applies inside the statement block that has the more-limited availability, because it isn't needed elsewhere. I added a new example (see the bottom of the availability section) to try to make the transformation clearer.

Doug

1 Like

Yes, buildLimitedAvailability support came in Xcode 12 beta 5.

Doug

I appreciate the thought you put into choosing this attribute name, and it is the only one I've seen so far that I felt was an improvement over the proposed @functionBuilder.

The "builder" part works well and is now heavily precedented in SwiftUI and other libraries that have adopted this feature in its earlier forms. Substituting in "transformer" (as has been suggested a couple of times) breaks that precedent for little gain.

There have been some suggestions to have "DSL" or "domain-specific" in the name, but I think that's going down the wrong path. Swift is an expressive language, and there are several ways to embed a domain-specific language in it. This is but one option, great for some kinds of DSLs, but not deserving of the "DSL" label. Plus, DSL is jargon that one shouldn't need to know to understand the feature.

But these types do build "return values", and if your first question on seeing the attribute is "what return values does it build?", that's a good thing: it's the return values from the function-like things in the language.

Doug

20 Likes