I'm trying to use the new syntax of resultBuilder to make something like a "ViewBuilder" but I'm having difficulty adding for loops.
I have the protocol. I have the aggregating type. I have a working result builder, all in the working playground below. link to github
I need for loops, so I'm trying to add functions like:
public static func buildArray<L:Layer>(_ components: [L]) -> some Layer {
magicallyReduceToTupleStack(components)
}
//or
public static func buildArray(_ components: [any Layer]) -> some Layer {
evenMoreMagicallyReduceToTupleStack(components)
}
With the idea of being able to do something like:
let oneKind = Assembly {
for _ in 0...3 {
Circle()
}
}
let multiKind = Assembly {
for _ in 0...3 {
Circle()
Square()
}
}
It's not going well. I feel like I must be missing something obvious because the resultBuilder can already take in a heterogeneous list of Layers via the two partialBuildBlocks. Can I write buildArray using them some how?
Any tips?
Full working playground:
import Foundation
public protocol Layer {
associatedtype Content:Layer
var content: Content { get }
}
//Indicate a leaf by conforming it to renderable.
public protocol RenderableLayer {
var id:String { get }
func render()
typealias Content = Never
}
extension RenderableLayer {
func render() {
print("\(self.id)")
}
}
public extension Layer where Content == Never {
var content: Never { fatalError("This should never be called.") }
}
extension Never: Layer {
public var id:String { "Never" }
public typealias Content = Never
}
public extension Layer {
func _render() {
if let bottom = self as? RenderableLayer {
//print("Found a bottom \(id)")
bottom.render()
} else {
//print("Not yet. \(id)")
content._render()
}
}
}
@resultBuilder
public enum LayerBuilder {
public static func buildPartialBlock<L: Layer>(first: L) -> some Layer {
first
}
public static func buildPartialBlock<L0: Layer, L1: Layer>(accumulated: L0, next: L1) -> some Layer {
Tuple2Layer(first: accumulated, second: next)
}
}
struct Tuple2Layer<First:Layer, Second:Layer>: Layer, RenderableLayer {
var id:String { "Tuple" }
var first: First
var second: Second
init(first:First, second:Second) {
self.first = first
self.second = second
}
func render() {
first._render()
second._render()
}
}
struct Assembly<Content:Layer>:Layer {
var content: Content
public init(@LayerBuilder content: () -> Content) {
self.content = content()
}
}
struct Circle:Layer, RenderableLayer {
var id:String { "Circle" }
}
struct Square:Layer, RenderableLayer {
var id:String { "Square" }
}
struct Triangle:Layer, RenderableLayer {
var id:String { "Triangle" }
}
let insert = Assembly {
Triangle()
Triangle()
Triangle()
}
let test = Assembly {
Circle()
Square()
insert
Circle()
Circle()
}
print(test)
test._render()
It's impossible to use TupleStack for this purpose because the result type TupleStack<L, R> depends on the size of the array, which is unknown at compile time. You should create a new type like:
Hilariously, that code works for a for loop, which is my main concern, but doesn't actually work for an explicit array. And when adding the buildExpressions back in, it breaks the for loop. (the 'τ_1_0' error returns. )
I've added the tag "compiler" to the post. Was that the right thing to do? Do you recommend also opening a Feedback or a Github Issue?
Yes, of course (...I'll supply the change). I was referring back to the code I had posted before to get arrays to work. I'll admit to finding the names "buildArray" and "buildOptional" initially confusing since they are for implementing for loops and ifs, not Arrays and Optionals. I get it now, but that took me a minute.
Noted that I should use the parameter name "expression" not "components." Fixing in gist.
FWIW: Thank you folks, as I now have almost parity with my Concrete-Type-Array based code!!! A refactor win, even if the road forward looks a bit tricky.
On that note, I found your buildEither examples didn't work in my code, I had to keep the _EitherLayer type (seen in gist). But the buildOptional did and it saved me a _WrappedLayer type! Thanks!
I don't really know how to write a DSL that will work for my purposes without first class for-loops? I hope I'm just missing an implementation error and can get buildExpressions for Arrays to not break them again. I'd rather live without Arrays.
I'll kick the wheels on that approach. My other route was actually entirely the other direction, locking down the allowed primitives in an enum instead of allowing anything that conforms to a protocol.
If the idea would eventually be able to do a very large Stage, I'm worried about all the boxing/unboxing I'd have to do, but it's possible.