(define (make-withdraw balance) (lambda (amount) (if (>= balance amount) (begin (set! balance (- balance amount)) balance) "Insufficient funds")))を呼び出して作り出した「払出し器」を考えよう.
(define W1 (make-withdraw 100))の評価とそれに続く
(W1 50) 50を述べよう. 図3.6は大域環境でmake-withdraw手続きを定義した結果を示す. これは大域環境へのポインタを持つ手続きオブジェクトを作る. ここまでは, 手続きの本体自身がlambda式である以外に, これまで見た例と違いはない.
興味ある計算の部分は, 手続きmake-withdrawを引数に作用させた時に起きる:
(define W1 (make-withdraw 100))いつものように仮パラメタbalanceが引数100に束縛された環境E1を設定するところから始めよう. この環境の中でmake-withdrawの本体, つまりlambda式を評価する. ここでそのコードがlambdaであり, その環境がE1, つまりこの手続きを作るためにlambdaが評価された環境である新しい手続きオブジェクトが出来る. 結果の手続きオブジェクトは make-withdrawを呼び出して返される値である. define自身は大域環境で評価されたので, これが大域環境でW1に束縛される. 図3.7が結果の環境構造を示す.
そこでW1が引数に作用させられた時, 何が起きるか解析出来る:
(W1 50) 50W1の仮パラメタamountが引数50に束縛されるフレームを作ることから始める. 注意すべき重要な点は, このフレームは外側の環境として, 大域環境ではなく, 環境E1を持つことだ. それがW1手続きオブジェクトの指定する環境だからである. この新しい環境の中で手続きの本体:
(if (>= balance amount) (begin (set! balance (- balance amount)) balance) "Insufficient funds")を評価する. 結果の環境構造を図3.8に示す. 評価されている式は amountとbalanceの両方を参照する. amountは環境の最初のフレームで見つかり, balanceはE1への外側の環境ポインタを辿ることで見つかる.
set!が実行されると, E1でのbalanceの束縛が変る. W1の呼出しが終ると, balanceは50で, balanceを含むフレームは手続きオブジェクトW1からまだ指されている. amountを束縛しているフレーム(そこでbalanceを変更したコードを実行した)は, これを作った手続き呼出しは終了し, 環境の他の部分からそのフレームへのポインタもないので, 最早無効である. 次回W1が呼び出されると, amountを束縛する新しいフレームが出来, その外側の環境がE1である. E1は手続きオブジェクトW1の局所状態変数を保持する「場所」として役立つのが分る. 図3.9はW1呼出し後の状況を示す.
図3.9 W1呼出し後の環境
make-withdraw つまり
(define W2 (make-withdraw 100))
をもう一度呼び出し, 第二の「払出し」オブジェクトを作り出すと, 何が起きるか見よう. これは図3.10の環境を作り, それはW2が手続きオブジェクト, つまりあるコードと一つの環境の対であることを示す. W2の環境E2
はmake-withdrawの呼出しで作り出された. そこにはbalanceに対する自分の局所束縛のあるフレームを含む. 他方W1とW2は同じコード: make-withdrawの本体のlambda式で規定したコードを持つ.15
これでW1と
W2が独立のオブジェクトとして振舞う理由が分る. W1の呼出しは, E1
に格納された状態変数balanceを参照し, W2の呼出しはE2に格納されたbalanceを参照する. このように一つのオブジェクトの局所状態の変更は他のオブジェクトに影響しない.
図3.10 (define W2 (make-withdraw 100))を使い二番目のオブジェクトを作り出す.
問題 3.10
make-withdraw手続きで, 局所変数balanceは
make-withdrawのパラメタとして作り出された. 局所変数はまた, let
を使い, 次のように明示的に作り出すことが出来る:
(define (make-withdraw initial-amount) (let ((balance initial-amount)) (lambda (amount) (if (>= balance amount) (begin (set! balance (- balance amount)) balance) "Insufficient funds"))))1.3.2節で, letは単に手続き呼出しの構文シュガーであったことを思い出そう:
(let ((〈var〉 〈exp〉)) 〈body〉)は
((lambda (〈var〉) 〈body〉) 〈exp〉)のもう一つの構文と解釈される. 環境モデルを使い, make-withdrawのこのもう一つの版を解析し, 対話
(define W1 (make-withdraw 100)) (W1 50) (define W2 (make-withdraw 100))を示す上のような絵を描け. make-withdrawの二つの版は, 同じ振舞いのオブジェクトを作り出すことを示せ. 二つの版で環境構造はどう違うか.
15
W1とW2が計算機内に格納された同じ物理的コードを共有するか, それぞれがコードの複製を持つかは, 実装の細部のことである. 4章で実装する解釈系では, コードは実は共有している.