Как использовать buildExpression в построителях функций Swift 5.2?

Я понимаю, что это черновик предложения. Я попытался реализовать простой DSL для построения строки, например так:

@_functionBuilder
struct StringBuilder {
    static func buildExpression(_ string: String) -> [String] {
        [string]
    }
    static func buildBlock(_ children: [String]...) -> [String] {
        children.flatMap{ $0 }
    }
}

func s(separator: String = "", @StringBuilder _ makeString: () -> [String]) -> String {
    makeString().joined(separator: separator)
}

let z = s(separator: " ") {
   "this"
   "is"
   "cool"
}

Тем не менее, компилятор жалуется, что "String" не конвертируется в "[String]". Это заставляет меня верить, что buildBlock это единственная часть предложения, которое в настоящее время реализовано. (Это понятно, учитывая, что в SwiftUI они строят иерархию представлений, так что это все, что им нужно.)

Это правильно или я что-то не так делаю? Как правильно использовать buildExpression?

Ответ ielyamani показывает, как создать построитель рабочей строки, который я использовал в моем примере выше. Однако это не решает актуальную проблему. Я не пытаюсь построить строитель строк. Я пытаюсь выяснить функции строителей. Строитель строк - только пример. Например, если мы хотим иметь построитель строк, который принимает целые числа, мы можем теоретически сделать следующее:

@_functionBuilder
struct StringBuilder {
    static func buildExpression(_ int: Int) -> [String] {
        ["\(int)"]
    }

    // The rest of it implemented just as above
}

В этом случае, когда компилятор обнаружил Intэто будет называть buildExpression затем выплюнуть наш тип компонента, в этом случае [String], Но, как сказал Мартин Р в комментарии к этому вопросу, buildExpression в настоящее время не реализовано.

2 ответа

Я столкнулся с той же проблемой сегодня, кажется, что buildExpression не реализован. Я закончил тем, что сделал обходной путь, используя протокол "ComponentProtocol", а затем создал "Expression: ComponentProtocol" и "Component: ComponentProtocol". Это работает для меня на данный момент. Я надеюсь, что это будет реализовано позже.

protocol ComponentProtocol: ExpressibleByIntegerLiteral, ExpressibleByStringLiteral  {
    var value: String { get }
}

struct Expression: ComponentProtocol {
    let _value: String
    var value: String { _value }
    init(_ value: String) { _value = value }
    init(integerLiteral value: Int) { self.init(value) }
    init(stringLiteral  value: String) { self.init(value) }
    init<E: CustomStringConvertible>(_ value: E) {_value = String(describing: value) }
}

struct Component: ComponentProtocol {
    let _values: [String]
    var value: String { _values.joined(separator: ", ") }
    init(integerLiteral value: Int) { self.init(value) }
    init(stringLiteral  value: String) { self.init(value) }
    init<E: CustomStringConvertible>(_ value: E) { _values = [String(describing: value)] }
    init<T: ComponentProtocol>(_ values: T...) { _values = values.map { $0.value } }
    init<T: ComponentProtocol>(_ values: [T]) { _values = values.map { $0.value } }
}

@_functionBuilder struct StringReduceBuilder {
    static func buildBlock<T: ComponentProtocol>(_ components: T ...) -> Component { Component(components) }

    static func buildEither<T: ComponentProtocol>(first: T) -> Component { Component(first.value) }
    static func buildEither<T: ComponentProtocol>(second: T) -> Component { Component(second.value) }
    static func buildOptional<T: ComponentProtocol>(_ component: T?) -> Component? {
        component == nil ? nil : Component(component!.value)
    }
}

func stringsReduce (@StringReduceBuilder block: () -> Component) -> Component {
    return block()
}

let result = stringsReduce {
    Expression(3)
    "one"
    Expression(5)
    Expression("2")
    83
}

let s2 = stringsReduce {
    if .random () { // random value Bool
        Expression(11)
    } else {
        Expression("another one")
    }
}

Поскольку buildBlock(_:) принимает переменное число массивов строк, это будет работать:

let z = s(separator: " ") {
    ["this"]
    ["is"]
    ["cool"]
}

Но это все еще неуклюже. Чтобы взять строки вместо массивов строк, добавьте эту функцию в StringBuilder который принимает переменное количество строк:

static func buildBlock(_ strings: String...) -> [String] {
    Array(strings)
}

И теперь вы можете сделать это:

let z = s(separator: " ") {
    "Hello"
    "my"
    "friend!"
}

print(z)    //Hello my friend!
Другие вопросы по тегам