encore icon indicating copy to clipboard operation
encore copied to clipboard

RFC: Boxing Variables on the Stack

Open TobiasWrigstad opened this issue 7 years ago • 1 comments

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.

TobiasWrigstad avatar Mar 17 '17 03:03 TobiasWrigstad

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.

supercooldave avatar Mar 19 '17 06:03 supercooldave