BlockContext
sumimさんちのcopy fixTemps 祭りの後、umejava さんち経由で タイミング良くやってきた BlockContext の説明。
とっても解りやすかったので、せっかくだから翻訳風 + 猫蛇足 なエントリをこさえてみました。
* * *
ブロックが クロージャじゃない Squeak Smalltalk では、 再帰するようなブロックは作れません。 例えば、階乗 を計算する以下のコード
| factorial | factorial := [:n| n = 1 ifTrue: [1] ifFalse: [(factorial value: n - 1) * n]]. (1 to: 10) collect: factorial
は、"Attempt to evaluate a block that is already being evaluated." (評価しようとしたブロックは、もう評価しちゃってるよ。ざーんねん)というエラーが 発生して実行出来ません。 どうしてそうなるか、というと、Blue Book VM では、 ひとつの 未評価ブロックは ひとつのアクティブレコードだからです。
じゃあ、評価する前にコピーして、 それぞれ(同時に)一個づつ実行すればいいじゃん、というのが以下のコード
| factorial | factorial := [:n| n = 1 ifTrue: [1] ifFalse: [(factorial copy value: n - 1) * n]]. (1 to: 10) collect: factorial copy
が、これを実行した結果は、
#(1 1 1 1 1 1 1 1 1 1)
という泣きたいような結果になってしまう...orz.
何故かというと、BlockContext のテンポラリ変数は、ホームコンテキストのテンポラリフレームに 作られるから。だから、再帰したブロックは、それぞれで独立のテンポラリを持てないんですにゃにゃ、これが。
(青木さんの図のパクリ)
factorial を explore するとこんなんが出ます。
今回問題になるのは、ブロック引数 :n。 図の選択部分―― 2:nil となっているところに :n が入っちゃうわけです。 全部の BlockContext がここを見ているから、 自身(じゃないけど)を再帰呼び出ししたとたん、今のコンテキストで受け取った値を 上書きしてしまいます。
factorial value: 3 は、
(((1) * 2) * 3)
をしたい、という意図なのだけれど、 () で囲んだ値を得るためには それぞれ fanctional value: n しなくちゃならない。 で、(1) * 2 する為には、 factorial value: 1 するのだけれど、そのとたん、仮引数 n は 1に上書きされてしまうため、 結局、
(((1) * 1) * 1)
となってしまう、というわけです...orz そういう原理ならば、これを回避するには、
(3 * (2 * (1)))
と、順番を入れ替えれば、通りそうなモノで、 やってみると、
| factorial | factorial := [:n| n = 1 ifTrue: [1] ifFalse: [n * (factorial copy value: n - 1)]]. (1 to: 10) collect: factorial copy
は、求める結果
#(1 2 6 24 120 720 5040 40320 362880 3628800)
が 得られちゃうんですね(突然この挙動に遭遇したらわけわかんにゃいな、きっと)。
* * *
――と、ここまでが、翻訳風味 な部分。この問題を回避するもう一つの方法として、 BlockContext >> fixTemps を使う方法もあって、
| factorial | factorial := [:n| n = 1 ifTrue: [1] ifFalse: [(factorial copy fixTemps value: n - 1) * n]]. (1 to: 10) collect: factorial copy fixTemps
の結果は、ちゃんと
#(1 2 6 24 120 720 5040 40320 362880 3628800)
となります。で、fixTemps が 何をやっているかというと、
BlockContext >> fixTemps "Fix the values of the temporary variables used in the block that are ordinarily shared with the method in which the block is defined." home _ home copy. home swapSender: nil
と言うこと。コピー三昧。
* * *
て、昼休み中サクっとエントリのつもりが、15分もオーバー。・・・う、サボりゴメン。