Как использовать buildExpression в построителях функций Swift 5.2? - PullRequest
1 голос
/ 08 июня 2019

Я понимаю, что это черновик предложения .Я попытался реализовать простой 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 ]

1 голос
/ 21 июля 2019

Я столкнулся с той же проблемой сегодня, похоже, 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")
    }
}
1 голос
/ 08 июня 2019

Так как 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!
...