Swift Result Builders

Sep 02, 2022#swift

Apply @resultBuilder to a class, structure, enumeration to use that type as a result builder. A result builder is a type that builds a nested data structure step by step. You use result builders to implement a domain-specific language (DSL) for creating nested data structures in a natural, declarative way.

The result-building methods are as follows:

  • static func buildBlock(_ components: Component...) -> Component
    Combines an array of partial results into a single partial result. A result builder must implement this method.

  • static func buildOptional(_ component: Component?) -> Component
    Builds a partial result from a partial result that can be nil. Implement this method to support if statements that donā€™t include an else clause.

  • static func buildEither(first: Component) -> Component
    Builds a partial result whose value varies depending on some condition. Implement both this method and buildEither(second:) to support switch statements and if statements that include an else clause.

  • static func buildEither(second: Component) -> Component
    Builds a partial result whose value varies depending on some condition. Implement both this method and buildEither(first:) to support switch statements and if statements that include an else clause.

  • static func buildArray(_ components: [Component]) -> Component
    Builds a partial result from an array of partial results. Implement this method to support for loops.

  • static func buildExpression(_ expression: Expression) -> Component
    Builds a partial result from an expression. You can implement this method to perform preprocessingā€”for example, converting expressions to an internal typeā€”or to provide additional information for type inference at use sites.

  • static func buildFinalResult(_ component: Component) -> FinalResult
    Builds a final result from a partial result. You can implement this method as part of a result builder that uses a different type for partial and final results, or to perform other postprocessing on a result before returning it.

  • static func buildLimitedAvailability(_ component: Component) -> Component
    Builds a partial result that propagates or erases type information outside a compiler-control statement that performs an availability check. You can use this to erase type information that varies between the conditional branches.

protocol Drawable {
  func draw() -> String
}
struct Line: Drawable {
  var elements: [Drawable]
  func draw() -> String {
    return elements.map { $0.draw() }.joined(separator: "")
  }
}
struct Text: Drawable {
  var content: String
  init(_ content: String) { self.content = content }
  func draw() -> String { return content }
}
struct Space: Drawable {
  func draw() -> String { return " " }
}
struct Stars: Drawable {
  var length: Int
  func draw() -> String { return String(repeating: "*", count: length) }
}
struct AllCaps: Drawable {
  var content: Drawable
  func draw() -> String { return content.draw().uppercased() }
}
@resultBuilder
struct DrawingBuilder {
  static func buildBlock(_ components: Drawable...) -> Drawable {
    return Line(elements: components)
  }
  static func buildEither(first: Drawable) -> Drawable {
    return first
  }
  static func buildEither(second: Drawable) -> Drawable {
    return second
  }
}
func draw(@DrawingBuilder content: () -> Drawable) -> Drawable {
  return content()
}
func caps(@DrawingBuilder content: () -> Drawable) -> Drawable {
  return AllCaps(content: content())
}

func makeGreeting(for name: String? = nil) -> Drawable {
  let greeting = draw {
    Stars(length: 3)
    Text("Hello")
    Space()
    caps {
      if let name = name {
        Text(name + "!")
      } else {
        Text("World!")
      }
    }
    Stars(length: 2)
  }
  return greeting
}
let genericGreeting = makeGreeting()
print(genericGreeting.draw())
// Prints "***Hello WORLD!**"

let personalGreeting = makeGreeting(for: "Ravi Patel")
print(personalGreeting.draw())
// Prints "***Hello RAVI PATEL!**"