Function builders

It's a known limitation of the current implementation that is too risky to try to fix in 5.1 because it would require changes to how closures are type-checked even when they aren't function builders.

5 Likes

So there won't be capability of implementing empty blocks atm and on the final 5.1 implementation I assume. Moreover, I'd like to know how far the 5.1 implementation will be in this topic, will it mimic what @ViewBuilder is or will be different for what the OOS community will create.

Function builders are a private feature in Swift 5.1; defining your own function-builder types using this private feature is allowed but unsupported, in the same way that it's allowed but unsupported to use underscored attributes like @_fixedLayout. Among other things, this means that future releases of Swift may not interpret this private feature in exactly the same way, although there are limits to how much this can change because (at least in the short term) those releases will need to support SwiftUI code building with the SDKs from Xcode 11, which only provide function builders using the private feature.

The Core Team is committed to providing a public, evolution-approved feature as soon as we can.

8 Likes

Thank you for the response! That public specification is not aiming 5.1 release (which I assume It'll be on fall, when Xcode 11 goes live)?

It was clear even when we were implementing function builders internally that the minimum feature necessary to support SwiftUI in Xcode 11 wasn't going to do all of what we wanted for Swift long-term, which is why this pitch is substantially different from what was implemented. Then I made this pitch and got a lot of great feedback, and it became totally obvious that we weren't going to have the opportunity to iterate on it properly in Swift 5.1. Property wrappers should give you a good idea of much time and effort real design iteration can take.

We plan to restart that iterative design process soon, before 5.1 is released, but there's no chance it will be ready in 5.1. I don't know and shouldn't speculate about when exactly Swift 5.1 and Xcode 11 will be released.

16 Likes

Is there any way to track the availability of various pieces of this implementation?

For instance, buildExpression doesn't seem to be working in Xcode 11 Beta 3. It would be nice if there was a summary whether or not some piece of the implementation is present in a Swift branch, and some idea of how long until this is available to experiment with.

It may be little too late, but I think there is a better solution that is simpler and not limited to just builder functions.

see: Function environment parameters

I'm glad the decision was made to make this a private feature for now. I strongly support the need for DSL s in Swift, but share a lot of others' concerns.

Most of my experience with using and creating many DSLs in the past was with a dynamic language, Groovy, which at least when I was doing it would typically leverage the language's support for closure delegates at runtime, where closures are first-class objects and a delegate can be set to any instance of anything before calling the closure. There are a lot of issues with that of course, as it is all about dynamic property and method resolution.

I do currently ship a Swift open source framework that has some simple builders using "clunky" builder instances passed to closures (e.g. URL Routes | Flint framework).

Obviously SwiftUI is my only real contact point with this feature and I understand from this thread that SwiftUI has special needs re: generic function signatures and these should be resolved in the fullness of time with variadic generics.

Those current limitations/sharp edges that are present in SwiftUI re: number of expressions capped at 10 and oblique side effects of that (one can imagine View bodies with multiple if statements causing major confusion here in particular)... I personally find in the context of SwiftUI to be really offensive – particularly because of the lack of reasonable error reporting.

This is actually the topic that gives me the greatest concern. I do not think we(*) can add function builders for DSLs as a public feature to Swift until we have a 100% solid strategy for clear and unambiguous error reporting at compile time.

With e.g. a Groovy DSL that is based on property and function access in the current closure scope being resolved on an instance of a "builder" (the closure delegate), while the problems are only found at runtime, they would typically be fairly unambiguous — along the lines of "No function called 'html' found".

As I understand it, being type-based the current proposal can only spit out errors about missing types and builder argument type mismatches which are not at all conducive to regular developers who want to use a DSL created by somebody (like me) as a quick and error-reducing shortcut to achieving something. The moment they spin off into type system errors the language feature has failed them.

If there is no reasonable way for errors in function builder closure bodies to be clearly reported as errors in the context of the DSL (not in the context of the Swift Type system) I will have to say -1 to this kind of proposal and instead advocate for an approach where functions and properties are used instead of types and resolved against an instance of a builder.

The rationale is that autocomplete and basic compiler error reporting works in this case. Problems are manifest in terms of the DSL:

var body: some View {
    list {
       text("Hello")
       thisDoesNotExist()
    }
}

If these functions resolve against a builder instance, they can still be verified at compile time, autocomplete suggestions can still work, and missing functions can result in simple errors like:

The ViewBuilder DSL does not have a function thisDoesNotExist() did you meant thisOtherOne()?

I have a very strong fear response that we will end up with impenetrable compiler/type system errors and broken autocomplete suggestions for many years to come and hobble the success of SwiftUI and other frameworks that provide DSLs as a result. It will have a reputation of "Ooooh no we don't use that as nobody can debug it".

Which is almost the very opposite of why DSLs exist.

*: who am I kidding, I don't understand enough of the compiler stuff to contribute)

7 Likes

I think that if Variadic Generics are implemented in Swift, Function Builders will be much more powerful than they currently are.

2 Likes

While testing @functionBuilder for a while I noticed the following behaviour in subclasses:

import Foundation

@_functionBuilder
struct ArrayBuilder {

    static func buildBlock<T>(_ components: T...) -> [T] {
        return components
    }
}

class Base {
     // OK no problems
    @ArrayBuilder func createArray() -> [Int] {
        1
        2
        3
        4
    }
}

class Derived: Base {

    // Didn't inherit the @ArrayBuilder attribute
    // so function's body is invalid
    override func createArray() -> [Int] {
        5
        6
        7
        8
    }
}

This is expected? If so why subclasses doesn't inherit the builder annotation?

This is expected? If so why subclasses doesn't inherit the builder annotation?

This is just speculation, but I imagine the builder attribute is considered an implementation detail, not a part of the function's signature. The Base interface only requires subclasses to have a method called createArray which returns an array. The subclass gets to decide whether or not it wants to fulfill that requirement with a function builder.

If Swift would provide true union types we could circumvent the variadic generics issue as follows:

@blockFunction
static div(elements:List<T where T==Div,T==Ul,T==Span>)->Div
//or
@blockFunction
static div(elements:List<Union<Div,Ul,Span>>)->Div

Alternatively we could work with protocols and implement all containing types for a protocol related to the enclosing type:

protocol DivContainment {...}

extension Div:  DivContainment {...}
extension Span: DivContainment {...}
extension Ul:   DivContainment {...}

@blockFunction
static div(elements:List<DivContainment>)->Div

If someone already mentioned it, I'm sorry.

Edit: Another point would be an automatic conversion to an enum though I don't know how this could be achieved, yet.

I have been experimenting with function builders a bit and they are superuseful but often i wished there was a way to inspect the generated code to verify its doing what its supposed to.

Could this be somehow exposed? (Ideally directly in Xcode)

I think this could be useful for ALL generated/synthesized code like Equatable/Hashable implementations

5 Likes

What you're asking for is a way to convert the compiler's AST (abstract syntax tree, one of the ways it represents code) back into source code. There's a module in the compiler called ASTPrinter that does this, but it only handles declarations, not statements or expressions, so it can't print the bodies of functions.

Adding support for printing statements and expressions would be extremely useful—it would not only help users understand synthesized code, but might also make swiftinterface files better. It's not a small task, but I bet a solo contributor could tackle it.

7 Likes

General question: Could Element and/or Result type be function type? If I haven't missed something, this is not explicitly forbidden. However, I was not able to get Element = Return = (Int)->Int function builder to work.

  • I was able to create function builder, but the block only accepted one function at the time (due to trailing closure behavior)
  • When I run the code it resulted in SIGSEGV
  • I was unable to mark children type in buildBlock function as @escaping due to variadic argument type

What's going on with local declarations in the builder closures? I get errors in Xcode (beta 7), but the spec/pitch suggests they will be supported. Are they just not implemented yet? I really hope they will be implemented.

44%20PM

Error: Closure containing a declaration cannot be used with function builder 'ViewBuilder'

But in the pitch:

... the ability to have local declarations and explicit control flow.
... Local declarations are left alone by the transformation.
... closure/function syntax is the most natural vehicle given: [...] the existing ability of functions to have local declarations ....

Rob

I don’t think this is supported ATM, neither will be I think when it comes out of beta. Function builders will be of private usage until swift open sources it in further versions.

That's correct. We still intend to generalize the function-builder transform to cover more kinds of statement, including local declarations, but we didn't have time to get that done in the 5.1 release.

Note that this is somewhat independent of putting function builders through the evolution process to make them a publicly-supported feature.

8 Likes

Is there a way to make compiler dump the built AST? I'm trying to write a function builder, but getting type errors, and I'm struggling to see where do they come from.

You can invoke swiftc -frontend -dump-ast <path_to_swift_file> -sdk $(xcrun --show-sdk-path)

3 Likes