encore
encore copied to clipboard
RFC: Boxing Variables on the Stack
RFC: Boxing Variables on the Stack
This is motivated by desugaring involving closures needing to capture variables on the stack to update, which we do not want to allow. Canonical example:
var sum = 0
for v <- [1,2,3] do
sum += v
end
println(sum) -- prints 6
This is desugared (roughly) into
var sum = 0
[1,2,3].map_do(fun (v) => sum += v)
println(sum) -- prints 0, or program does not compile
which will not work because sum
inside the closure is a copy of the outer sum
.
The main idea is to allow a closure and a stack frame to communicate by lifting the variable into a box which can be shared. Essentially:
var sum = 0
var sum_box = new Box[int](sum)
[1,2,3].map_do(fun (v) => sum_box.value += v)
sum = sum_box.value
println(sum) -- prints 6
The vanilla box can be defined thus (simplified):
class VanillaBox[t]
var value:t
def init(value:t) : unit
this.value = value
end
end
It is easy to imagine a family of standard boxes, like this:
class ReduceBox[t]
var value : t
val adder : (t, t) -> t
def init(value:t, adder : (t, t) -> t) : unit
this.value = value
this.adder =
end
def add(value:t) : unit
this.value = this.adder(this.value, value)
end
def get() : t
this.value
end
end
Using the ReduceBox, we can rewrite the initial for loop thus:
var sum = new ReduceBox[int](0, fun (a:int, b:int) => a + b)
for v <- [1,2,3] do
sum.add(v)
end
println(sum.get()) -- prints 6
This RFC proposes that all variables captured and updated by a closure in a for loop be implicitly boxed so that:
id = ... -- write to captured variable
... = id -- read from capture variable written to elsewhere
is turned into
id_box.value = ...
... = id_box.value
With the vanilla box, this will serialise all for loops. If a user wants to use a more fancy, hot (lockfree) reduce box (for example), code can be easily rewritten to take advantage of this without too much clutter:
var sum = new AtomicInt(0)
for v <- [1,2,3] do
sum.add(v)
end
println(sum.get()) -- prints 6
This ofc does not work so well with the whole inversion story, but this RFC still proposes to get something simple working as a first increment.
Seems like a very natural proposal — Scala uses this implementation, at least for normal variables. It would be nice to avoid having the programmer think explicitly in terms of objects, like AtomicInts, but this is probably unavoidable if you want flexibility ... and we don't want a series of new keywords to cover the finite number of cases we dream up.