(define *alst* '((a b) (c d) (e f)))
上記で生成したリストに対し、
(let ((p (assoc 'e *alst*)))
(set! (cdr p) (cons 'x (cdr p))))
という処理を実行すると、*alst*が更新される("((a b) (c d) (e x f))"となる)のに対し、
(let ((p (cdr (assoc 'e *alst*))))
(set! p (cons 'y p)))
では*alst*が更新されないのはなぜでしょうか。
まず、上の処理
(let ((p (assoc 'e *alst*)))
(set! (cdr p) (cons 'x (cdr p))))
について *alst* 内のリスト (e f) の部分の構造を考えてみます。
let により、変数 p に (e f) へのポインタが代入されます。これは次の図
p → ([A] . [B]) ↓ ↓ 'e ([C] . [D]) ↓ 'f
で表わすような構造の、対 ([A] . [B]) へのポインタとして考えることができます。
(car p) と (cdr p) はそれぞれ箱 [A] と [B] を表わしており、ここでは、[A] には 'e へのポインタ、[B] には対 ([C] . [D]) へのポインタが格納されています。
2行目の (set! (cdr p) (cons 'x (cdr p))) は、[B] の中身を、(cons 'x (cdr p)) に書き換えるということです。
(cons 'x (cdr p)) は、'x と、(cdr p) とで新たに対を作ります。
([E] . [F]) ↓ ↓ 'x ([C] . [D]) ↓ 'f
この新しい対へのポインタが set! により [B] に格納されます。
p → ([A] . [B]) ↓ ↓ 'e ([E] . [F]) ↓ ↓ 'x ([C] . [D]) ↓ 'f
こうして *alst* が ((a b) (c d) (e x f)) に更新されます。
一方、下の処理
(let ((p (cdr (assoc 'e *alst*))))
(set! p (cons 'y p)))
では、初めに let によって次の図のように
p ↓ ([A] . [B]) ↓ ↓ 'e ([C] . [D]) ↓ 'f
変数 p の中身が箱 [B] へのポインタとなります。
次に (set! p (cons 'y p)) の部分ですが、まず cons によって新たな対 ('y . ([C] . [D])) が作られます。
([E] . [F]) ↓ ↓ 'y ([C] . [D]) ↓ 'f
そして、変数 p の中身をこの対へのポインタに書き換えます。
p → ([E] . [F]) ↓ ↓ 'y ([C] . [D]) ↓ 'f
ところが、もともと *alst* から参照されていた (e f) の部分、つまり対 ([A] . [B]) 以下の部分に対しては一度も書き換えが行われていないのです。
これが *alst* の変化しない理由です。
"let" は一時的なオブジェクトを作る関数です。C言語とかで言うところの、ローカル変数です。
ですので、たとえば
(define X 1) (let ((tmp X)) (set! tmp 2))
のように書いても、 X の値は1 のままです。何故なら、これは以下のように評価されるためです。
変数Xに1を代入 一時変数 tmp を用意して、値を変数Xにする 一時変数 tmp の値を 2 に変更
同様に
(let ((p (cdr (assoc 'e *alst*)))) (set! p (cons 'y p)))
と書いてもこれは、一時変数 p の値が "(cons 'y p)" に変わるだけで *alst* の値は変わりません。
一方、元々の式
(let ((p (assoc 'e *alst*))) (set! (cdr p) (cons 'x (cdr p))))
で *alst* の値が変わった理由は、 (set! ..) で (cdr p) を変更しているからです。
p自身は一時変数ですが、(cdr p) は *alst* の一部を指しているため *alst*の値が変わった、ということになります。
(let ((p (cdr (assoc 'e *alst*)))) (set! p (cons 'y p)))
も
(let ((p (cdr (assoc 'e *alst*)))) (set! (cdr p) (cons 'y p)))
などにすれば、 *alst* の値が変わります。試してみてください。
具体的な説明をいただきありがとうございました。
cinquantaさんの説明と併せて、ようやく理解できました。
後者の場合、ポインタの指し示す先を書き換えるのではなく、
局所変数pを書き換えるのみということなので、*alst*は変化しないという事ですね。
よく理解できました。
非常に丁寧にご説明いただきありがとうございました。