[Swift 5.4] Result Builder

dev-yong·2021년 4월 8일
0

Swift

목록 보기
1/1
  • @resultBuilder

  • Swift 5.1에 등장하였던 비공식의 function builder가 Swift 5.4 에서 공식적으로 추가되었다.

  • 일련의 구성 요소들로부터 결과값을 암시적으로 구축할 수 있도록 한다.

  • 함수의 표현식문에서 부분적인 결과를 수집하고 이를 결과값으로 결합하기 위한 임베디드된 DSL로 사용할 수 있다.

    • 빌더 변환의 힘은 의도적으로 제한되므로, 결과값이 원래 코드의 dynamic semantics를 보존한다.
    • 함수의 원래 명령문은 여전히 정상적으로 실행된다.
    • 일반적인 semantics에서 무시되는 값이 실제로 결과값에 수집된다.
    • 빌더 변환을 위한 임시(ad hoc) 프로토콜의 사용은 새로운 종류의 명령문을 지원할 것인지, 아니면 변환의 세부 사항을 커스터마이징할 것인지에 대한 다양한 미래의 확장 여지를 남겨 둔다.

Motivation

return body([
  division([
    header1("Chapter 1. Loomings."),
    paragraph(["Call me Ishmael. Some years ago"]),
    paragraph(["There is now your insular city"])
  ]),
  division([
    header1("Chapter 2. The Carpet-Bag."),
    paragraph(["I stuffed a shirt or two"])
  ])
])
  • 쉼표, 괄호, 대괄호 등 많은 구두점이 존재한다.

    • 피상적인 문제로, 내용에서 집중을 분산시키기 때문에 피하는 것이 좋다.
  • children을 위해 array literal을 사용하고 있기 때문에 type-checker는 Element가 동일한 타입을 갖도록 요구할 것이다.

  • 가장 큰 문제는 이 계층 구조를 변경하는 것이 어색하다는 것이다.

division((useChapterTitles ? [header1("Chapter 1. Loomings.")] : []) +
    [paragraph(["Call me Ishmael. Some years ago"]),
     paragraph(["There is now your insular city"])])

Detail

Result builder Attribute

  • varsubscript의 경우 선언은 getter를 정의해야하며 속성은 해당 getter의 속성인 것처럼 처리된다.
    • 이러한 방식으로 사용되는 result builder attribute는 result builder transform 함수의 본문에 적용되도록 한다.
  • 프로토콜 요구 사항의 매개 변수를 포함하여 함수 유형의 매개 변수에 대한 속성으로도 사용될 수 있다.

Methods

  • Result builder method는 result builder 유형에서 호출 할 수 있는 static 메소드이다.
    • BuilderType.<methodName>(<arguments>)
@resultBuilder
struct ExampleResultBuilder {
  
  /// 변환 된 함수의 개별 명령문 표현식 유형. 
  /// `buildExpression()`이 제공되지 않은 경우 기본값은 Component이다.
  typealias Expression = ...

  /// 모든 빌드 메소드를 통해 전달되는 부분 결과의 유형
  typealias Component = ...

  /// 최종 반환 된 결과의 유형. 
  /// `buildFinalResult()`가 제공되지 않은 경우 기본값은 Component이다.
  typealias FinalResult = ...

  /// Required by every result builder to build combined results from
  /// statement blocks.
  static func buildBlock(_ components: Component...) -> Component { ... }

  /// If declared, provides contextual type information for statement
  /// expressions to translate them into partial results.
  static func buildExpression(_ expression: Expression) -> Component { ... }

  /// Enables support for `if` statements that do not have an `else`.
  static func buildOptional(_ component: Component?) -> Component { ... }

  /// With buildEither(second:), enables support for 'if-else' and 'switch'
  /// statements by folding conditional results into a single result.
  static func buildEither(first component: Component) -> Component { ... }

  /// With buildEither(first:), enables support for 'if-else' and 'switch'
  /// statements by folding conditional results into a single result.
  static func buildEither(second component: Component) -> Component { ... }

  /// Enables support for 'for..in' loops by combining the
  /// results of all iterations into a single result.
  static func buildArray(_ components: [Component]) -> Component { ... }

  /// If declared, this will be called on the partial result of an 'if
  /// #available' block to allow the result builder to erase type
  /// information.
  static func buildLimitedAvailability(_ component: Component) -> Component { ... }

  /// If declared, this will be called on the partial result from the outermost
  /// block statement to produce the final returned result.
  static func buildFinalResult(_ component: Component) -> FinalResult { ... }
}

Transform

  • Result builder transform은 명령문 블록과 그 안의 개별 명령문에서 작동하는 재귀 변환이다.

Statement blocks

  • Statment 블록 내에서, 개별 statement는 별도로 연결되는 statement의 시퀀스로 표현된다.
  • 각각의 시퀀스는 선택적으로 단일의 부분적인 결과를 생성할 수 있는 데, 이는 나중에 블록에서 사용될 수 있는 표현이다.
    • 변환이 모든 statment에 적용된 후, buildBlock 에 대한 호출이 생성되어 결합된 결과를 형성하며, 모든 부분적인 결과들은 unlabelled arguments로 표시된다.

Expression statements

  • buildExpression 메소드를 선언하는 경우, 변환은 이를 unlabelled arguments수로 expression-statement를 전달한다.
    • 이 call expression은 이후 expression stament로 사용된다.
    • 이 호출은 statement-expression과 함께 타입 체크가 되며 해당 유형에 영향을 미칠 수 있다.
  • statement-expression은 statement-expression의 결과 유형의 고유 변수를 초기화하는 데 사용된다.
    • 이 변수는 포함 블록의 일부 결과로 처리된다.
    • 이 변수에 대한 참조는 표현식 유형에 영향을 주지 않도록 독립적으로 유형 검사된다.

Assignments

  • Assigment를 수행하는 expression statement는 항상 ()를 반환하지만 다른 모든 expression statements와 동일한 방식으로 처리된다.
  • Result builder는 () 를 반환하는 expression을 다루는 것을 buildExpression 을 override하여 선택할 수 있다.
static func buildExpression(_: ()) -> Component { ... }

Selection statements

if / else 그리고 switch statement는 케이스에 따라 조건부로 값을 생성한다.

if i == 0 {
  "0"
}

📦
var vCase0: String?
if i == 0 {
  vCase0 = "0"
}
let v0 = BuilderType.buildOptional(vCase0)
if i == 0 {
  "0"
} else if i == 1 {
  "1"
} else {
  generateFibTree(i)
}

📦
let vMerged: PartialResult
if i == 0 {
  vMerged = BuilderType.buildEither(first: "0")
} else if i == 1 {
  vMerged = BuilderType.buildEither(second:
        BuilderType.buildEither(first: "1"))
} else {
  vMerged = BuilderType.buildEither(second:
        BuilderType.buildEither(second: generateFibTree(i)))
}

Imperative control-flow statements

  • return 은 속성을 몇시적으로 제공하는 func, getter 에만 적용된다.
  • break, continue, guard 는 지원되지 않는다.

Exception-handling statements

  • throw, defer, do

  • for .. in 문은 루프의 각 반복을 실행하여, 모든 반복의 부분적 결과를 배열로 수집한다.

    • 그리고 해당 배열이 buildArray로 전달된다.

Referecne

profile
🧑🏻‍💻 iOS Developer @TOSS

0개의 댓글