To clarify, if we defend the behavior as it is now, this can never be type-stable:
julia> genf(a) = () -> (a += 1)
f = genf(0)
f(), f(), f()
(1, 2, 3)
to achieve type-stability, we must manually type-stabilize a
with a Ref
:
julia> geng(a) = let a=Ref(a); () -> (a[] += 1) end
g = geng(0)
g(), g(), g()
(1, 2, 3)
(as shown here, simple type-annotation is an inferior solution. Edit: per this proposal, type-annotation will be equivalent)
If we defend the current behavior, what we gain for it is the ability to accommodate a function’s return type changing during the life of a closure whose capture type depends on it:
julia> q() = 0
genh(a) = () -> (a += q() + 1)
h = gen(0)
h(), h(), h()
(1, 2, 3)
julia> q() = 0.0
h(), h(), h()
(4.0, 5.0, 6.0)
The only reason that this latter code functions, is because the box h.a
is type-unstable. If we stabilize it based on knowledge of q
’s return type at the time h
is instantiated, then if q
’s return type changes, h
will throw an error.
In such a future where a
is stabilized, if you wish for h
to accommodate arbitrary changes in q
to include changes of its return type, you would have to write this:
genx(a) = let a=Ref{Any}(a); () -> (a[] += q() + 1) end
In other words, currently type-instability is the default, and you have to add a Ref
for type-stability. I suggest making type-stability the default instead—considering how much more useful type-stability is than the ability to accommodate arbitrary changes in functions’ return types, this seems like a good trade.
Agree. All roads lead back to a “Julia Standard Linter™.” Even still, sometimes you really have to rip up your code to get type stability.
Yes, this has become apparent without looking at the code
Boxing may be a bit more complicated if we want a consistent approach to memory management in Julia. … asserting new rules for one part of the system in the absence of standardizing other parts may be premature
Can you elaborate your concerns? Is simply type-parameterizing Core.Box
harmful somehow?