[ 目次, 前節, 次節, 索引 ]

3.1.1 局所状態変数



時と共に変化する状態をとる計算オブジェクトがあるという意味を説明するのに, 銀行口座からお金を払い出す状況をモデルにしよう. そのために払い出すamountを引数にとる手続きwithdrawを使う. 払出しに応じられるほど十分なお金が口座にあれば, withdrawは払出し後の残高を返す. そうでなければwithdrawInsufficient funds(残高不足)というメッセージを返す. 例えば口座に100ドルあるところから始めると, withdrawを使い次の一連の応答を得る:
(withdraw 25)
75

(withdraw 25)
50

(withdraw 60)
"Insufficient funds"

(withdraw 15)
35
(withdraw 25)は二度評価して, 異る値が出たことに注意しよう. これは手続きの新しい種類の振舞いである. これまでわれわれの手続きは, 数学的関数を計算するための仕様のように見えた. 手続きの呼出しは, 与えられた引数に作用させた関数の値を計算したので, 同じ手続きを同じ引数で二度呼び出しても, 同じ結果が出てきた.1

   withdrawを実装するには, 口座のお金の残高を示す変数balance を使い, withdrawbalanceにアクセスする手続きとして定義する. withdraw手続きは, balanceが少なくとも要求された amountより大きいかを調べる. そうであれば, withdraw balanceからamountを減じ, balanceの新しい値を返す. そうでなければwithdrawInsufficient fundsのメッセージを返す. balancewithdrawの定義はこうである:

(define balance 100)


(define (withdraw amount)
  (if (>= balance amount)
      (begin (set! balance (- balance amount))
             balance)
      "Insufficient funds"))
balanceの減算は式
(set! balance (- balance amount))
で達成される. これは set!の特殊形式を使っている. その構文は
(set! ⟨name⟩ ⟨new-value⟩)
で, ここに⟨name⟩は記号, ⟨new-value⟩は任意の式である. set! は⟨name⟩の値を, ⟨new-value⟩を評価して得た結果のものに変更する. ここの例ではbalanceの新しい値をbalanceの以前の値からamountを引いた結果に変更する. 2

   withdrawはまたifのテストが真の時, ---まずbalanceを引き, 次にbalanceの値を戻すという---二つの式が評価出来るよう, begin特殊形式を使っている. 一般に式

(begin exp1⟩  ⟨exp2⟩  ...  ⟨expk )

の評価は⟨exp1⟩から⟨expk⟩まで順に評価し, 最後の式⟨expk⟩の値をbegin形式全体の値として返す. 3

   withdrawは思い通りに動くけれども, balanceに問題がある. 上で指定したように, balanceは大域環境で定義した名前であり, どの手続きからも自由にアクセスし, 修正出来る. 何とかして, withdrawだけが直接balanceにアクセス出来, 他の手続きはbalanceには(withdrawを介して)間接にしかアクセス出来ないようbalance withdrawの内部に置けたら, 遥かによいに違いない. この方がbalancewithdrawがある口座の状態を覚えておくのに使う局所状態変数であるという考えをより正確にモデル化する.

   定義を次のように書き替え, balancewithdrawの内部とすることが出来る:


(define new-withdraw
  (let ((balance 100))
    (lambda (amount)
      (if (>= balance amount)
          (begin (set! balance (- balance amount))
                 balance)
          "Insufficient funds"))))
われわれがここでやったのは, letを使って, 初期値100に束縛した局所変数balanceを持つ環境を作ったことである. この局所環境で, lambdaを使い, amountを引数としてとり, 前のwithdraw手続きのように振舞う手続きを作り出す. この---let式を評価した結果として返される---手続きはwithdrawと全く同じように振舞う new-withdrawであるが, その変数balanceは他の手続きからはアクセス出来ない. 4

   set!を局所変数と組み合せるのは, 局所状態を持つ計算オブジェクトを構成するのにわれわれが使う一般的プログラム技法である. しかし, 困ったことにこの技法の使用は重大な問題を惹き起す. 初めに手続きを説明した時, 手続き作用の意味の解釈の用意として, 評価の置換えモデルを説明した(1.1.5節). 手続きの作用とは, 仮パラメタをその値で取り替え, 手続きの本体を評価することだといった. 問題は言語に代入を取り入れると置換えは最早手続き作用の適切なモデルにはならないことだ. (なぜそうかは3.1.3節で説明する.) 従ってここではnew-withdraw手続きが上で主張したように振舞うかを理解する方法が技術的にはない. new-withdrawのような手続きを本当に理解するには, 手続き作用の別のモデルを開発しなければならない. 3.2節でそういうモデルをset!と局所変数の説明とともに紹介しよう. しかしまず, new-withdrawが持ち込んだ話題をいろいろ調べてみよう.

   次の手続きmake-withdrawは「払出し器」を作り出す. make-withdrawの仮パラメタbalanceは口座の初期の金額を規定する. 5


(define (make-withdraw balance)
  (lambda (amount)
    (if (>= balance amount)
        (begin (set! balance (- balance amount))
               balance)
        "Insufficient funds")))
make-withdrawは次のように二つのオブジェクトW1W2を作り出すのに使える:
(define W1 (make-withdraw 100))
(define W2 (make-withdraw 100))

(W1 50)
50

(W2 70)
30

(W2 40)
"Insufficient funds"

(W1 40)
10
W1W2は, それぞれが自分の局所状態変数balanceを持つ, 完全に独立なオブジェクトであることを見よう. 一方からの払出しは, 他方に影響を与えない.

   われわれは払出しと同様に, 預入れを扱うオブジェクトを作り出すことも出来, 単純な銀行口座が表現出来る. 次の手続きは, 指定した初期残高に対する「銀行口座オブジェクト」を返す手続きである:



(define (make-account balance)
  (define (withdraw amount)
    (if (>= balance amount)
        (begin (set! balance (- balance amount))
               balance)
        "Insufficient funds"))
  (define (deposit amount)
    (set! balance (+ balance amount))
    balance)
  (define (dispatch m)
    (cond ((eq? m 'withdraw) withdraw)
          ((eq? m 'deposit) deposit)
          (else (error "Unknown request -- MAKE-ACCOUNT"
                       m))))
  dispatch)
make-accountを呼び出す度に, 局所変数balanceを持った環境を設定する. その環境では, make-accountbalanceにアクセスする手続きdepositwithdrawと, 入力として「メッセージ」をとり, 二つの局所手続きの一つを返すもう一つの手続きdispatchを定義する. dispatch自身は, 銀行口座オブジェクトの表現する値として返される. ここではそれをその局所変数を修正する能力とともに使っているが, これは正に2.4.3節で見た メッセージパッシング(message-passing)流のプログラミングである.

   make-accountは次のように使える.

(define acc (make-account 100))

((acc 'withdraw) 50)
50

((acc 'withdraw) 60)
"Insufficient funds"

((acc 'deposit) 40)
90

((acc 'withdraw) 60)
30
accを呼び出す度に局所的に定義したdepositwithdraw 手続きが返され, それらが指定したamountに作用させられる. make-withdrawの場合と同様に, make-account
(define acc2 (make-account 100))
をもう一度呼び出すと, 自分の局所的balanceを保持する全く別の口座オブジェクトが出来る.

問題 3.1


アキュムレータ(accumulator)は一つの数値引数で繰り返し呼び出される手続きで, 引数をsumに足し込む. 呼び出される度に, 現在sum に足し込まれている合計を返す. それぞれが独立な合計を保持するアキュムレータを生成する手続き make-accumulatorを書け. make-accumulatorの入力は,合計の初期値を規定するものとする; 例えば
(define A (make-accumulator 5))

(A 10)
15

(A 10)
25


問題 3.2


ソフトウェアテストの応用では, ある手続きが計算の過程で呼び出された回数が数えられると有用である. それ自身が一入力をとる手続きfを入力としてとる手続き make-monitoredを書け. make-monitoredが返す結果は三番目の手続きで, それをmfとすると, mfは自分が呼び出された回数の内部カウンタに覚えている. mfへの入力が特別な記号 how-many-calls?だと, mfはカウンタの値を返す. 入力が特別な記号 reset-countだと, mfはカウンタを零にリセットする. それ以外の入力では, mffをその入力で呼び出した時の結果を返し, カウンタを増やす. 例えばsqrt手続きのモニタ版を作ることが出来る.
(define s (make-monitored sqrt))

(s 100)
10

(s 'how-many-calls?)
1


問題 3.3


make-accountを修正し, パスワードで保護された口座を作り出すようにせよ. つまりmake-accountはもう一つの引数として記号をとる. 例えば
(define acc (make-account 100 'secret-password))
結果の口座オブジェクトは, 口座を作り出した時のパスワードのついている要求だけを処理し, それ以外は文句をいう:
((acc 'secret-password 'withdraw) 40)
60

((acc 'some-other-password 'deposit) 50)
"Incorrect password"


問題 3.4


問題3.3のmake-accountを, もう一つの局所状態変数を加えるように修正し, 口座が不正なパスワードで連続七回アクセスされると, 手続き call-the-copsを起動するようにせよ.



1 実際には少し違う. 一つの例外は1.2.6節の 乱数発生器であった. もう一つは2.4.3節で説明した 演算対型の表で, 同じ引数でgetを二度呼び出した値は, その途中のputの呼出しに依存する. 他方, 代入を説明するまでは, われわれ自身にはそういう手続きを作り出す方法はなかった.

2 set!式の返す値は実装に依存する. set!はその値ではなく, 効果のために使うべきである.

   set!という名前はSchemeの名前づけの便法に従っている: 変数の値を変更する演算(または3.3節で見るように, データ構造を変更する演算)は感嘆符で終る名前とする. これは述語を疑問符で終る名前で区別するのと似た便法である.

3 Schemeでは手続きの本体が一連の式であってよいので, すでにbeginを暗黙の内に使っている. またcond式の各節の帰結部は単一の式でなく, 一連の式でよい.

4 プログラム言語の業界用語では, 変数balance new-withdraw手続きに カプセル化(encapsulated)されたという. カプセル化は 隠蔽原理(hiding principle)という一般的システム設計原理を反映している: システムの部分を他の部分から保護することで,つまり情報アクセスを「知る権利」を持つ部分だけに用意することで, システムをより部品化力を持ち, 頑健にしうる.

5 上のnew-withdrawと違って, 仮パラメタは既に局所的なので, balanceを局所変数にするためのletは使わない. このことは3.2 節の評価の環境モデルの議論の後で更に明らかになるであろう. (問題3.10も参照)

[ 目次, 前節, 次節, 索引 ]