i feel as if i am always copy-and-pasting the same “DefaultArrayBuilder” methods:
{
static
func buildExpression(_ element:Element) -> [Element]
{
[element]
}
static
func buildExpression(_ elements:[Element]) -> [Element]
{
elements
}
static
func buildExpression<S>(_ elements:S) -> [Element]
where S:Sequence, S.Element == Element
{
[Element].init(elements)
}
static
func buildBlock(_ elements:[Element]...) -> [Element]
{
elements.flatMap{ $0 }
}
static
func buildArray(_ elements:[[Element]]) -> [Element]
{
elements.flatMap{ $0 }
}
static
func buildOptional(_ element:[Element]?) -> [Element]
{
element ?? []
}
static
func buildEither(first:[Element]) -> [Element]
{
first
}
static
func buildEither(second:[Element]) -> [Element]
{
second
}
}
within a single project, these usually end up evolving into an ArrayBuilder protocol with an Element associatedtype that the actual result builders conform to. but it still gets duplicated across projects. has anyone else had the same experience?
5 Likes
Here's what I do:
protocol ArrayBuilder
{
associatedtype Element
}
extension ArrayBuilder
{
// all those buildXxx methods
}
@resultBuilder
struct ThingBuilder: ArrayBuilder
{
typealias Element = Thing
// any extra stuff
}
3 Likes
this is exactly what i do. it’s become a pattern
3 Likes
Zollerboy1
(Josef Zoller)
4
I also had to duplicate this code multiple times in different projects. Could we maybe put a DefaultArrayBuilder protocol into the standard library? I think that would be very useful.
Maybe it could be a bit more flexible than @David_Catmull's solution. I'm thinking about e.g. having an associated type for the expression and the final result.
The protocol could look somewhat like this:
protocol DefaultArrayBuilder {
associatedtype Element
associatedtype Expression = Element
associatedtype FinalResult = [Element]
static func buildExpression(_ element: Expression) -> [Element]
static func buildFinalResult(_ elements: [Element]) -> FinalResult
}
extension DefaultArrayBuilder {
static func buildBlock(_ elements: [Element]...) -> [Element] {
elements.flatMap { $0 }
}
static func buildArray(_ elements: [[Element]]) -> [Element] {
elements.flatMap { $0 }
}
static func buildOptional(_ element: [Element]?) -> [Element] {
element ?? []
}
static func buildEither(first: [Element]) -> [Element] {
first
}
static func buildEither(second: [Element]) -> [Element] {
second
}
static func buildLimitedAvailability(_ elements: [Element]) -> [Element] {
elements
}
}
extension DefaultArrayBuilder where Expression == Element {
static func buildExpression(_ expression: Expression) -> [Element] {
[expression]
}
}
extension DefaultArrayBuilder where FinalResult == [Element] {
static func buildFinalResult(_ elements: [Element]) -> FinalResult {
elements
}
}
Having this protocol in the standard library could also help with discoverability of the result builder functions. It's nice that SourceKit is autocompleting them now, but having a simple example implementation would definitely be helpful as well.
2 Likes
mklbtz
(Michael Bates)
5
I have encountered this too, I feel like an array builder like this would be a welcome addition to the std lib.
My solution was to make the builder itself generic, so instead of
ThingBuilder: DefaultArrayBuilder {
typealias Element == Thing
}
I did this, once:
ArrayBuilder<Element> {
associatedtype Expression = Element
associatedtype FinalResult = [Element]
// etc.
}
and I usually add an Array "initializer" for good measure.
extension Array {
static func build(@ArrayBuilder<Self.Element> build: () -> Self) -> Self {
return build()
}
}
i used to do that, but i found that i often wanted to customize the buildFinalResult methods, so gradually started doing the same thing @David_Catmull does.
2 Likes
Happened here too. We have typeable resultbuilders for array, set and dict, but it is the array one that is most of the time is used.
It would make sense to see this being part of the standard library.
3 Likes