BlockContext

sumimさんちのcopy fixTemps 祭りの後、umejava さんち経由で タイミング良くやってきた BlockContext の説明。

Cog Blog :: Closures Part I

とっても解りやすかったので、せっかくだから翻訳風 + 猫蛇足 なエントリをこさえてみました。


* * *

ブロックが クロージャじゃない 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分もオーバー。・・・う、サボりゴメン。