It is certainly intended that information only flows "forward" in a function builder closure. Function builders are implemented this way (with the "unidirectional constraints" noted in the implementation progress thread) for two reasons. The first is to mimic the type inference behavior of the syntactic rewrite of function builder closures into set of let
declarations (one per expression), as @John_McCall noted earlier in this thread. The second is to eliminate exponential type checker behavior that came from considering all of the expressions simultaneously, such that (e.g.) any one expression could radically change the type-checking behavior of any other expression. The first is malleable (we could describe function builders some other way), but the second is not: the type-checking performance benefits we gained from unidirectional constraints were massive, and we cannot give those back; I also don't think we can achieve them without continuing to enforce unidirectional flow through closures.
Fluent builder APIs are effectively unidirectional by construction. When you have something like this:
a.f().g().h()
You have to resolve the type of a
before you can meaningfully look up f
; then resolve the type of that call to f
before you can meaningfully look up g
, and so on. There is some limited back-propagation to (e.g.) fill in generic arguments if they weren't known before, but the fact that we cannot perform member lookup until we have a concrete-ish type provides mostly unidirectional type flow that curbs exponential behavior.
Yes, I believe this is be possible. One could imagine adding some kind of buildFold
operation to the function builder that the current result and "folds in" a new buildExpression
. This example from earlier in the thread:
Sequential {
Conv2D<Float>(...)
AvgPool2D(...)
Flatten()
Dense(...)
Dense(...)
}
Could be translated into, effectively:
Sequential {
let a = LayerBuilder.buildFoldInit(Conv2D<Float>(...))
let b = LayerBuilder.buildFold(a, AvgPool2D(...))
let c = LayerBuilder.buildFold(b, Flatten())
let d = LayerBuilder.buildFold(c, Dense(...))
let e = LayerBuilder.buildFold(d, Dense(...))
return LayerBuilder.buildBlock(e)
}
Note that this allows you to take the output of the prior expression and feed it into the next expression, but type information is still flowing mostly in one direction. You get to use the type from the prior fold as part of type-checking each expression as input to the fold, but that should still be limited enough to be efficient.
Doug
EDIT: Dropped inferred <Float>
s from examples.