Result builders in Swift

от автора

Конструктор результатов можно рассматривать как встроенный предметно-ориентированный язык (DSL) для сбора деталей, которые объединяются в конечный результат. Конструкторы результатов в Swift позволяют создавать результат, используя «блоки сборки», расположенные в ряд друг за другом.

Примеры.

Вот функция, которая возвращает одну строку:

func makeSentence1() -> String {     "Why settle for a Duke when you can have a Prince?" }  print(makeSentence1())

Это отлично работает, но что, если бы у нас было несколько строк, которые мы хотели объединить? Мы могли бы захотеть предоставить их все по отдельности и попросить Swift разобраться с этим, однако такой код не сработает:

// This is invalid Swift, and will not compile.  func makeSentence2() -> String {      "Why settle for a Duke"      "when you can have"      "a Prince?"  }

Сам по себе этот код не будет работать, потому что Swift больше не понимает, что мы имеем в виду. Однако мы могли бы создать конструктор результатов, который понимает, как преобразовать несколько строк в одну строку, используя любое преобразование, которое мы хотим, например, так:

@resultBuilder struct SimpleStringBuilder {     static func buildBlock(_ parts: String...) -> String {         parts.joined(separator: "\n")     } }

Атрибут @resultBuilder сообщает Swift, что следующий тип следует рассматривать как средство построения результатов. Ранее такое поведение было достигнуто с помощью @_functionBuilder, который имел символ подчеркивания, чтобы показать, что он не предназначен для общего использования.

Каждый конструктор результатов должен предоставлять по крайней мере один статический метод, называемый buildBlock(), который должен принимать какие-либо данные и преобразовывать их. В приведенном выше примере берется ноль или более строк, они объединяются и отправляются обратно в виде одной строки.

Конечным результатом является то, что наша структура SimpleStringBuilderстановится компоновщиком результатов, что означает, что мы можем использовать @SimpleStringBuilder везде, где нам нужны его возможности объединения строк.

Мы можем использовать SimpleStringBuilder.buildBlock() напрямую:

let joined = SimpleStringBuilder.buildBlock(     "Why settle for a Duke",     "when you can have",     "a Prince?" )  print(joined)

Однако, поскольку мы использовали аннотацию @resultBuilder с нашей структурой SimpleStringBuilder, мы также можем применить ее к функциям:

@SimpleStringBuilder func makeSentence3() -> String {     "Why settle for a Duke"     "when you can have"     "a Prince?" }  print(makeSentence3())

Обратите внимание, что нам больше не нужны запятые в конце каждой строки – @resultBuilder автоматически преобразует каждый оператор в makeSentence() в отдельную строку с помощью SimpleStringBuilder.

На практике конструкторы результатов способны на значительно большее, что достигается добавлением большего количества методов к вашему типу конструктора. Например, мы могли бы добавить поддержку if/else в наш простой конструктор строк, добавив два дополнительных метода, которые описывают, как мы хотим преобразовать данные. В нашем коде мы вообще не хотим преобразовывать наши строки, поэтому можем сразу вернуть их обратно:

@resultBuilder struct ConditionalStringBuilder {     static func buildBlock(_ parts: String...) -> String {         parts.joined(separator: "\n")     }      static func buildEither(first component: String) -> String {         return component     }      static func buildEither(second component: String) -> String {         return component     } }

Теперь наши функции могут использовать условия:

@ConditionalStringBuilder func makeSentence4() -> String {     "Why settle for a Duke"     "when you can have"      if Bool.random() {         "a Prince?"     } else {         "a King?"     } }  print(makeSentence4())

Аналогично, мы можем добавить поддержку циклов, добавив метод buildArray() к нашему типу builder:

@resultBuilder struct ComplexStringBuilder {     static func buildBlock(_ parts: String...) -> String {         parts.joined(separator: "\n")     }      static func buildEither(first component: String) -> String {         return component     }      static func buildEither(second component: String) -> String {         return component     }      static func buildArray(_ components: [String]) -> String {         components.joined(separator: "\n")     } }

Теперь мы можем использовать циклы:

@ComplexStringBuilder func countDown() -> String {     for i in (0...10).reversed() {         "\(i)…"     }      "Lift off!" }  print(countDown())

Swift 5.4 расширяет систему построения результатов для поддержки размещения атрибутов в сохраненных свойствах, что автоматически настраивает неявный инициализатор по элементам для структур для применения построителя результатов.

Это особенно полезно для пользовательских представлений SwiftUI, в которых используются построители результатов, такие как этот:

import SwiftUI  struct CustomVStack<Content: View>: View {     @ViewBuilder let content: Content      var body: some View {         VStack {             // custom functionality here             content         }     } }

Если вы хотите увидеть более продвинутые, реальные примеры построения результатов в действии, вам следует ознакомиться с https://github.com/carson-katri/awesome-result-builders


ссылка на оригинал статьи https://habr.com/ru/articles/813393/