[Перевод] Result builders in Swift

483663ba6b556075778befe46c5c0664.png

Конструктор результатов можно рассматривать как встроенный предметно-ориентированный язык (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: View {
    @ViewBuilder let content: Content

    var body: some View {
        VStack {
            // custom functionality here
            content
        }
    }
}

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

Habrahabr.ru прочитано 6113 раз