Result builder expressiveness and type inference

Result builders are designed to be expressive, and also performant, but this second requirement can sometimes impact the first. As brought up previously on the forums:

This thread does a great job explaining the design of one-way constraints, and why this generic must be specified on each line and is not inferred. The thread also suggests a workaround:

But in practice, this does not seem to work as soon as you nest builders (a common thing to do!). To take the thread's example further, parameterization can allow this to build just fine:

@LayerBuilder<Float>
let model = Sequential { // ✅ Inference works great, and quickly!
  Conv2D(...)
  AvgPool2D(...)
  Flatten()
  Dense(...)
  Dense(...)
}

But Float is not propagated to any nested builders:

@LayerBuilder<Float>
let model = Sequential {
  Sequential {
    Conv2D(...) // 🛑 Inference is broken here, and slow :(
    AvgPool2D(...)
    Flatten()
    Dense(...)
    Dense(...)
  }
}

My hope would be that the desugared builder would look something like this:

let e0 = LayerBuilder<Float>.buildExpression(
  // `buildExpression` is constrained so that `Sequential`'s `LayerBuilder`
  // must be generic over `Float`:
  Sequential {
    let e0 = LayerBuilder<Float>.buildExpression(Conv2D(...))
    let e1 = LayerBuilder<Float>.buildExpression(AvgPool2D(...))
    let e2 = LayerBuilder<Float>.buildExpression(Flatten())
    let e3 = LayerBuilder<Float>.buildExpression(Dense(...))
    let e4 = LayerBuilder<Float>.buildExpression(Dense(...))
    let e5 = LayerBuilder<Float>.buildBlock(e0, e1, e2, e3, e4)
    return e5
  }
)

But despite the constraint on buildExpression, the inner LayerBuilder is not properly constrained.

Is this a bug or simply a limitation of the design of result builders?

13 Likes

@Douglas_Gregor Since you worked on the one-way constraints I was curious if you could weigh in on this limitation? Is it something that could be fixed?