NGSCM
4. 住所録の製作 その 2: ファイル版
9.1 自動販売機のシミュレート
本実習では局所状態を持ったデータ
(
オブジェクト)
による、プログラミング技法の基礎 を学びます。 オブジェクトとは、いくつかのデータとそれを操作する基本的な手続き群を 封じ込めたものです。これまでに紹介したプログラムでは、(
一時的な作業用の局所変数を 除いては)
データはトップレベルの変数に保持されていました。このため、他の手続きが 同一の名前の変数を別の目的で使っていて、その変数の値が他の手続きによって書き換え られてしまう可能性がありました。ここで紹介する技法では、オブジェクトの中にいくつかの固有のデータと、それを操作 する手続き群を封じ込めています。データの値を変更するには、オブジェクトにメッセー ジを送ります。メッセージ
(message)
とはオブジェクトに対する指令で、何らかの動作を 要求するものです。メッセージを受けとったオブジェクトは 、メッセージが正しい(
許される
)
ときだけメッセージに対応した手続きを実行し 、必要に応じて内部データを変更し ます。データはオブジェクトの中に封じ込められているために、データを変更するにはオブジェ クトにメッセージを送るしか方法がなく、他の手続きが勝手にデータを書き換えることは できません。このために、プログラムの誤りによるデータの破壊を防ぐことができます。
9.1.1 固有の局所データを持つオブジェクト
まずは局所データを持つオブジェクトを作る方法から学んでゆきましょう。次の例は「財 布」オブジェクトを作る手続きです。
(define (make-wallet money) (lambda (amount)
(if (< money amount)
"Not enough money"
(begin
(set! money (- money amount)) money))))
手続き make-wallet は 、ひとつの引数 money を持ちます。式 (make-wallet 100)を 評価すると、(lambda (amount) ) の評価結果が (make-wallet 100)の値として返さ れます。
返される値は「手続き」と呼ばれる型のデータです。
(
そのため、手続き make-wallet は、「手続きを作る」手続きです。)
作られたデータは、手続きと同じように呼び出すことが できます。この手続きは (lambda (amount) )となっているように、1
つの引数を持っています。では、試してみましょう。
> (define w (make-wallet 100))
#<unspecified>
> (w 10) 90
> (w 10) 80
> (w 10) 70
変数moneyはwだけが参照できる変数です。このように参照できるものが一部に限られ
ていることから、変数moneyは wの局所変数と呼ばれます。
(
詳しくは6.3
ページの説明を読み返して下さい。
)
局所変数は同一名のトップレベルの変数にはまったく作用しません。> (define money 10000)
#<unspecified>
> (define w (make-wallet 100))
#<unspecified>
> (w 10) 90
> (w 10) 80
> money 10000
手続きw は、もし財布に十分なお金があれば 、変数 money から w の引数に与えられた
amount を引き、その結果を moneyに代入しています。そして財布に残った金額 moneyを
実行結果として返しています。上の実行例では
2
度続けて (w 10) を評価していますが 、 (set! money (- money amount)) によって財布の残金を代入しているので、(w 10)の 値は呼び出すごとに違っています。変数 money が財布の状態(
残金)
を表す変数で 、時々刻々移り変わる財布の状態を保持しています。
(
図9.1
参照を参照して下さい。)
wの値となっている手続きの定義を見ると (lambda (amount)
(if (< money amount)
"Not enough money"
(begin
(set! money (- money amount)) money)))
W .
..
. .
. .
. .
. .
..
.
...
..
.
. .
. .
. .
. .
. .
..
. .
. .
. .
. .
..
. .
. .
..
.
. ..
. .
.
. .
. .
. .
. .
. .
. .
. .
..
. .
. .
..
. .
..
. .
.
. ..
. .
.
. .
..
. .
. .
..
. .
. .
..
. .
..
. .
. .
..
. .
.
. . .
. .
.
. . .
.
. .
. .
..
. .
..
. .
. .
..
. .
..
. .
. .
.
. . .
. .
.
...
.
. .
..
. .
. .
..
. .
..
. .
. .
..
. .
. .
.
...
..
.
...
.
. .
..
. .
..
. .
. .
..
. .
. .
. .
. .
..
.
...
..
.
.. .
...
..
..
..
...
..
..
... . . . . . . . . . . . . . . . . . . q
q
q q
q
q q
q q
q
q q q
q
q q
q q
q q
q q
q q
q
q q q
q
q q
q
q q q
q
q q
q
q q q
q
q q
q
q q q
q
q q
q
q q q
q
q q
q
q q q
q q
q
q q
q
q q q
q q
q
q q
q q
q q
q
q q
q
q q q
q
q q
q
q q q
q
q q
q q
q q
q q
q q
q q
q
q q
q q
q q
q q
q
q q
q q
q q
q q
q
q q
q q
q q
q q
q q
q
q q q
q
q q
q
q q q
q q
q
q q
q
q q q
q
q q
q q
q q
q q
q
q q
q q
q q
q
q q
q q
q q
q q
q
q qq
q
q q
q q
q q
q
q q q
q
q q q
q
q q q
q
q q
q q
q q
q
q q q
q q
q q
qqq
q q
q qq
q q q
q qq
qqq
q qqq
qqq
qqq
qqqqq
qqqqqq
qqqqqqqq
q q qqqqq
q q qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq
q qqqqqqqqqqqqqqqqqqqqqqqqqqq
q qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq
q qqqqqqqqqqqqqqqqqqqqqqqqqq
q qqqqqqqqqqqqqqqqqq
q q qqqqqqqqqqqq
qqqqqqqqqqqqqqqqqqqq
q qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq
qqqqqqqqqqqqqqqqqqqq q qqqqqqqq
qqqqqqqqqqq qqqqqq
qqqqqqq qqqqqq
qqq q qqqq q q qq qqqqq q q qqq q qq qqqq q q q q q q q q q qq qq q q q q q qq q q q q q q q q q qq q q q q qq q q q q q q qq q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q qq q q q q q q q q q q q q q q q q qq q q q q q q q q q q q q q q qq q q q q q q qq q q q q q q q q q q q q q q q q q q q q q qq q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q qq q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q
q q q q q q q q q q q q q q q q q
q q q q q q q q q q
q q q q q q
q q q q q q
q q q q q q
q q q q q q q
q q q q q q q q q q
q q q q q q q
q q q q q q q q q
q q q q q q q q
q q q q q q q q q q
q q q q q q q q q
q q q q q q q q q q q q
q q q q q q q q q q
q q q q q q q q q q q q q
q q q q q q q q q q q q
q q q q q q q q q q
q q q q q q q q q q
q q q q q q q
q q q q q q q q
q q q q q
q q q q q
q q q q
q q q q
q q q q q
q q q q
q q
q q
q q q q
q q
q q
q q
q q q
q q q
q
q q
q q
q q q
q
q q
q q
q q
q q
q q
q q
q
q q q
q
q
q
手続き
> (w 10)
(lambda (amount) (if (< money ...
....)
money: 100 局所変数
. . . . . . . . . . . . . . . . . . . . .
.
. .
. .
.
. .
. .
.
. .
. .
.
.
図
9.1:
局所変数を持つ手続きオブジェクトとなっていて、変数moneyは一見トップレベルで定義された変数のように見えます。です
が money はトップレベルで定義された変数ではなく、手続き make-wallet の引数として
用いられた変数です。手続きの引数として使われる変数は局所的な変数で、その手続きの 内部でしか使えません。式 (lambda (amonut) )は手続きmake-wallet の内側に置か れているので、wが set! で代入をする変数 moneyは手続き make-walletの引数です。こ のことより、代入をする変数は make-wallet の内側だけでしか参照できない局所変数で、
トップレベル変数ではないことが分かります1。
これまでの説明で想像つくように、変数 moneyは make-walletが呼ばれるたびに新し いものが用意されます。すなわち最初に評価された(make-wallet 100)での 変数 money と、次に評価された(make-wallet 100)での変数moneyでは、 名前は同じでも実体が違 います。この手法を使うことで、「同じしゅ種の、異なった個体」をいくつも作ることが可能と なります。
> (define w1 (make-wallet 100))
#<unspecified>
> (define w2 (make-wallet 100))
#<unspecified>
> (w1 20) 80
> (w2 10) 90
1(make-wallet 100)が評価されたときに、新たにmoneyの値が
100
である環境が作られ 、その環境の もとで (lambda (amount) )が評価されます。式が評価されるとその評価値である手続きデータには、評価された時の環境と式の定義
(
(lambda (amount) ))
が含まれています。そしてその手続きデータが評 価されるときには、make-walletが評価されたときの環境に、手続きデータに対する引数に対する環境を加 えて拡大された環境のもとで、ラムダ式の本体が評価されます。このような理由のため、単なる式を局所変 数を持ったオブジェクトとすることができる訳です。> (w1 40) 40
以上で手続きデータに固有のデータを保持させるための、基本技法を学びました。
9.1.2 自動販売機オブジェクト
次は上で学んだ技法を使って、缶ジュースの自動販売機
(vending machine)
の真似をするプログラムを作ります。自動販売機には数種類の缶ジュースが 、それぞれの種類ごとに たくさん入れられていて、売れるたびに残りのジュースは
1
つづつ減ってゆき、売上のお 金がたまって行きます。本実習で作るプログラムは、簡単化のために取り扱うジュースの 種類を1
つに限定します。(
多くの種類を取り扱うことのできるプログラムは、練習問題で 取り上げます。)
ジュースの在庫数は販売機の状態ですので、これを局所データとして保持します。ジュー スを買うにはお金を入れ 、選んだジュースのボタンを押さないといけません。ジュースが 売り切れているかど うかを調べる機能も必要です。以上のことより、本実習では次のよう な機能を持つ自動販売機プログラムを作ります。
販売機を動かし始めるとき
(
販売機オブジェクトを作るとき)
、最初に入れるジュース の総数を指定します。販売機のボタンが押されたときに
(
販売機オブジェクトに「ボタン」メッセージが送ら れるとき)
、ジュースがあれば売ります。もし売り切れなら、そのことを知らせます。ジュースの残りを表示します。
(
販売機オブジェクトに「残り」メッセージを送ると、在庫数を返します。
)
ここでは自動販売機をオブジェクトとみなしていますが、財布の例の場合と違ってジュー スを買うとか、在庫を調べるなど 、ひとつのオブジェクトがいろいろな動作をします。こ のために、どの動作をするかを指定する必要があり、動作の指定は引数で与えるようにし ます。
作ろうとしているプログラムの大まかな構造は、次のようにすれば良いでしょう
:
(define (new-vend stock) (lambda message
(cond
(h buttonメッセージであるi
hジュースを売るi ) (hstock メッセージであるi
h在庫数を返すi)
(h それ以外なら
,
エラーi ))))手続きnew-vend は 、最初に販売機に入れるジュースの数stock を引数に持っています。
この変数は局所的な変数で、販売機の持つジュースの数を憶えるのに使います。
これを基にして自動販売機オブジェクトを生成する手続きを作ると、次のようになります。
(define (new-vend stock) (lambda message
(cond
((eq? (car message) 'button) (if (= stock 0)
"Sold out"
(begin
(set! stock (- stock 1)) 'juice)))
((eq? (car message) 'stock) stock)
(else
(error "Unknown message" message)))))
(lambda messages ) で分かるよう、販売機オブジェクトは引数 message を持ちま す。
(
引数のならびが (message)ではなく messageであり、括弧で囲まれていない点に注 意して下さい。)
仮引数がひとつで括弧に囲まれていない場合は任意個の実引数を与える ことができ、それらはリストになって仮引数message に渡されるようになっています。もし メッセージ
(
つまり messageの第1
要素)
が button なら、ジュースを販売する動 作をし ます。もしジュースがなければ(
つまり (= stock 0) のとき)
、"Sold out" を返 し 、売り切れを知らせます。まだジュースがあるときは在庫の数を1
つ減らし 、販売した商品を渡します。販売機オブジェクトへのメッセージが stock のときは、ジュースの残り
数
(
stock の値)
を返します。もしそれ以外のメッセージが渡されたらエラーにします。では
2
つのジュースを入れた販売機を作り、実際に試してみましょう。> (define v (new-vend 2))
| 2
つのジュースを持つ販売機を作る#<unspecified>
> (v 'button)
|
ジュースを1
つ買うjuice
> (v 'stock)
|
残りは?
1
> (v 'button)
|
もう1
つジュースを買うjuice
> (v 'button)
|
更にジュースを買う"Sold out"