Really like the additions of switch
and if let
!
One unexpected behaviour i encountered (with the Xcode 12 Beta 4) is that the functionbuilder transform doesn't seem to consider protocols and/or protocol extensions.
My example is a "default" function-builder.
The idea was to provide a protocol with default implementations for most of the build
methods.
indirect enum FunctionBuilder<Expression> {
case expression(Expression)
case block([FunctionBuilder])
case either(Either<FunctionBuilder, FunctionBuilder>)
case optional(FunctionBuilder?)
}
protocol FunctionBuilderProtocol {
associatedtype Expression
typealias Component = FunctionBuilder<Expression>
associatedtype Return
static func buildExpression(_ expression: Expression) -> Component
static func buildBlock(_ components: Component...) -> Component
static func buildDo(_ components: Component...) -> Component
static func buildOptional(_ optional: Component?) -> Component
static func buildArray(_ components: [Component]) -> Component
static func buildLimitedAvailability(_ component: Component) -> Component
static func buildFinalResult(_ components: Component) -> Return
}
and provide default implementations as protocol extensions
extension FunctionBuilderProtocol {
static func buildExpression(_ expression: Expression) -> Component { .expression(expression) }
static func buildBlock(_ components: Component...) -> Component { .block(components) }
static func buildDo(_ components: Component...) -> Component { .block(components) }
static func buildOptional(_ optional: Component?) -> Component { .optional(optional) }
static func buildArray(_ components: [Component]) -> Component { .block(components) }
static func buildLimitedAvailability(_ component: Component) -> Component { component }
}
With this in place the only thing that needs to be implemented is buildFinalResult
.
@_functionBuilder
enum ArrayBuilder<E>: FunctionBuilderProtocol {
typealias Expression = E
typealias Return = [E]
static func buildFinalResult(_ components: Component) -> Return {
switch components {
case .expression(let e): return [e]
case .block(let children): return children.flatMap(buildFinalResult)
case .either(.left(let child)): return buildFinalResult(child)
case .either(.right(let child)): return buildFinalResult(child)
case .optional(let child?): return buildFinalResult(child)
case .optional(nil): return []
}
}
}
But this doesn't compile
func buildArray(@ArrayBuilder<String> build: () -> [String]) -> [String] {
return build()
}
let a = buildArray {
"1"
"2"
if Bool.random() {
"maybe 3"
}
}
// error: closure containing control flow statement cannot be used with function builder 'ArrayBuilder'
// if Bool.random() {
// ^
//
// note: generic enum 'ArrayBuilder' declared here
// enum ArrayBuilder<E>: FunctionBuilderProtocol {
// ^
//
// error: cannot convert value of type 'String' to expected argument type 'FunctionBuilder<ArrayBuilder<String>.Expression>' (aka 'FunctionBuilder<String>')
// "1"
// ^
// error: cannot convert value of type 'String' to expected argument type 'FunctionBuilder<ArrayBuilder<String>.Expression>' (aka 'FunctionBuilder<String>')
// "2"