Ex. 4.70
まず、THE-ASSERTIONSの中身は
THE-ASSERTIONS = ((son Adam Cain) . (delay ((son Cain Enoch) . (delay ((son Enoch Irad) ... )))
これの格納の様子を図にするとこんな感じ。
図のように、Schemeはリストを変数に代入するとき、リストのコピーはとらない。ポインタだけ格納する。promiseは delay で作られるオブジェクトである。(残念ながら、promiseオブジェクトの内部のデータの持ち方は不明。多分これもオブジェクトへのポインタを持つだけと思う。)
局所変数 old-assertion を使わずに set! するとどうなるか見てみよう。
(set! THE-ASSERTIONS (cons-stream '(son abc xyz) THE-ASSERTION))
ストリームの中のTHE-ASSERTIONSも新しい番地を指すだけである。前回のTHE-ASSERTIONSの番地を誰も覚えていない。この場合
(car THE-ASSERTIONS) → (son abc xyz) (car (force (cdr THE-ASSERTIONS))) → (son abc xyz) ...
と、いくら cdr を繰り返しても、(son abc xyz) しか返ってこない。
局所変数 old-assertions を使った場合は
(let ((old-assetions THE-ASSERTIONS)) (set! THE-ASSERTIONS (cons-stream '(son abc xyz) old-assertions)))
こんな感じになる。前回のTHE-ASSERTIONSの番地をold-assertions が覚えている。これだと
(car THE-ASSERTIONS) → (son abc xyz) (car (force (cdr THE-ASSERTIONS))) → (son Adam Cain) ...
と、cdr で次々に表明を取り出せる。
ある言語の変数の保存方法は、こうでなければならないというロジカルな理由はない。若しくは、どんな格納の仕方でもロジカルに正しいと言える。CとかFORTRANの代入と違う動作だといって文句は言えない。
Scheme で cons-stream と set! の組み合わせが上の図のように変数を格納するのは、単に仕様である。
しかし、一見なんの意味もない、省略可能な中間変数にしか見えない old-assertions が実は必須だった。というのは罠以外の何者でもない。
この問題の意図は、cons-stream 使ったグローバル変数の set! には、こういう落とし穴があるので注意しましょう。ということである。
念のため、もし delay を使わないで、cons だけで THE-ASSERTIONS を作ったらどうなるであろうか?
この場合は、局所変数 old-assertions は要らない。CとかFORTRANの代入と同じように振る舞う。
(set! THE-ASSERTIONS (cons '(son abc xyz) THE-ASSERTION))
ということで、cons と cons-stream で set! の振る舞いが異なる。非常に間違い易い、、、だが、仕様である!我々は黙って注意するしかない。
(そもそも、THE-ASSERTIONSで cons-stream を使うメリットがないような気がする。consで十分ではないか?)