クロージャの実装の続きで、代入もサポートする方法を調べた。今回もThree implementation models for Schemeの、4.5節を参考にする。

4.4節で、自由変数の参照だけで代入がない場合について、値のコピーをクロージャに持たせてやることで実現していた。代入が含まれる場合にはどうするかとういうと、ボックス化ということをする。ボックス化というのは、ヒープ上に値を保持して、そのヒープへのポインタを変数として持つという方法。ポインタの指している先の中身を書き換えれば、参照しているすべての箇所でその変更が反映されることになる。

論文では、例によってSchemeなのでパースが終了しているのでlambda式の本体を見ていって代入があるかどうか調べているが、自作スクリプト言語でパースを自ら行う場合にはパースを行なっている時に自由変数への代入が検出できるので、それをスコープの情報として記録しておく。パースがすんで関数のコンパイルの段階で、下のスコープで自分のローカル変数への代入がある変数についてボックス化を行うコードを入れる。

変数を参照する箇所でもその変数がボックス化されているかどうかがコンパイル時にわかっているので、そうであればボックスから取り出す命令を入れるようにする。

ひとつ注意する必要があるのは、Schemeだとローカル変数というのは関数のパラメータだけだけど、一般のスクリプト言語などではスコープにローカル変数が定義できる。そしてそういうローカル変数は必ず代入文がある(そうじゃないと未定義の初期値が使われることになってしまう)。そのような、スコープで宣言されたローカル変数への代入は、別にボックス化する必要はない。

しかし注意しなければいけないのは、下のスコープでは参照だけで代入していなくても、クロージャを作ったあとにその変数への代入がある場合にはやっぱりボックス化する必要がある。例えば

function test() {
  var i = 0;
  var f = function() {
    window.console.log(i);
  };
  i = 1234;
  f();  //=> 1234
}

クロージャfでは自由変数iへの参照しかしてないけど、クロージャが作られたあとでiへの代入があるので、ボックス化する必要がある。

論文の4.5節の改善案はまさにこういうことで、変数に対する代入があるけど、継続がない言語でクロージャに閉じ込められない変数はボックス化する必要がなくて省ける、ということが書いてある。

論文では追加する命令コードは、indirect(ボックス化された変数からの値の取り出し)、box n(n番目の引数をボックス化)、assign-local n(ローカル変数への代入、変数は必ずボックス化されている)、assign-free n(自由変数への代入、同様にボックス化されている)、となっている。

改善案を適用する場合には、代入のあるローカル変数でもボックス化されない変数の可能性があるので、assign-local命令はボックス化されているもの用とされてないもの用の2種類用意する必要がある。自由変数への代入は常にボックス化されるので1種類で問題ない。