SICPを読み進める上での個人的なメモです。
気になった部分の引用
3.1.1 局所状態変数
(define balance 100)
(define (withdraw amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds"
)
)
実行例:
>(withdraw 25)
75
>(withdraw 25)
50
>(withdraw 60)
"Insufficient funds"
>(withdraw 15)
36式 (withdraw 25) は二回評価されていますが、それぞれ違う値を返しているというところに注意してください。これは、手続きのふるまいとして、これまでになかった種類のものです。これまでは、手続きはすべて数学関数を計算する仕様として捉えることが可能でした。手続きの呼び出しを行うと、与えられた引数を関数に適用したときの値の計算が行われ、同じ手続きを同じ引数で二回呼び出すと、いつでも同じ値が返ってくることになっていました。
(set! <name> <new-value>)
balanceの減算はset!という特殊式を用いる。
変数<name>の値を<new-value>に変更する。
(begin <exp1> <exp2>...<expk>)
ifがTrueの場合、二つの式が評価されるようにするために、beginという特殊形式を用いる。<exp1>~<expk>までの式が順番に評価され、最後の式(<expk>)を評価した結果の値がbegin全体の値として返される。
カプセル化
(define new-withdraw
(let ((balance 100))
(lambda (amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds"
)
)
)
)
実行結果
>(new-withdraw 10)
90
>(new-withdraw 10)
80この局所環境の中で、amount を引数に取り以前の withdraw 手続きと同じようなふるまいをする手続きを lambda を使って作っています。let 式を評価した結果として返されるこの手続きは new-withdraw で、withdraw と正確に同じふるまいをしますが、変数 balance はほかのどの手続きからもアクセスできません。
プログラミング言語の業界用語では、変数 balance は手続き new-withdraw の中に カプセル化 (encapsulated) されていると言います。カプセル化は、隠蔽原理 (hiding principle) として知られる、一般的なシステム設計の原則を反映したものです。隠蔽原理とは、システムの各部品をほかの部品から守ることによって、システムをよりモジュール的で頑健なものにできるという考え方です。これはつまり、“知る必要” のあるシステムの部品にだけ、情報へのアクセス権を与えるということです。
set! を局所変数と組み合わせるというのは、今後局所状態を持つ計算オブジェクトを構築するのに使うことになる一般的なプログラミングテクニックです。残念ながら、このテクニックを使うと深刻な問題が出てきます。
問題は、言語に代入を導入したとたん、置換というのは手続き適用の適切なモデルではなくなるということです (どうしてそうなるのかは3.1.3 節で見ていきます)。
メッセージパッシング
(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"))
)
)
dispatch
)
実行結果:
>(define acc (make-account 100))
>((acc 'withdraw) 60)
40
>((acc 'deposit) 30)
70make-account を呼び出すたびに、局所状態変数 balance を持つ環境が構築されます。この環境の中で、make-account は balance にアクセスする手続き deposit, withdraw と、また “メッセージ” を入力として取り二つの局所手続きのうちひとつを返す dispatch という追加の手続きを定義します。銀行口座オブジェクトを表現する値としては、dispatch 手続きそのものが返されます。これはまさに、2.4.3 節で学んだ メッセージパッシング (message-passing) のプログラミングスタイルですが、ここでは局所変数を変更する能力と合わせて使っ ています。
Exercise
Ex 3.1
初期値に加算していく
(define (make-accumulator init)
(lambda (operand)
(set! init (+ init operand))
init
)
)
実行結果
> (define A (make-accumulator 5))
> (A 10)
15
> (A 10)
25
Ex 3.2
呼び出し回数を数える。特定のメッセージの場合に呼び出し回数を返したり、
カウンタをリセットする。
(define (make-monitored procedure)
(let ((cnt 0))
(define (dispatch args)
(cond ((eq? args 'how-many-calls?) cnt)
((eq? args 'reset-count)
(begin (set! cnt 0)
cnt))
(else (begin
(set! cnt (+ cnt 1))
(procedure args)
)
)
)
)
dispatch
)
)
実行結果:
> (define s (make-monitored sqrt))
> (s 100)
10
> (s 'how-many-calls?)
1
> (s 'reset-count)
0
> (s 'how-many-calls?)
0
Ex 3.3
銀行口座にパスワードをつける。パスワードを間違えるとメッセージを表示
(define (make-account balance password)
(define (withdraw amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds"
)
)
(define (deposit amount)
(set! balance (+ balance amount))
balance
)
(define (incorrect DUMMY)
"Incorrect password"
)
(define (dispatch p m)
(if (eq? p password)
(cond ((eq? m 'withdraw) withdraw)
((eq? m 'deposit) deposit)
(else (error "Unknown request: MAKE-ACCOUNT"))
)
incorrect
)
)
dispatch
)
実行結果:
> (define acc (make-account 100 'secret-password))
> ((acc 'secret-password 'withdraw) 40)
60
> ((acc 'some-other-password 'deposit) 50)
"Incorrect password"
Ex 3.4
パスワードを7回間違えると警察を呼ぶ(call-the-cops)
(define (make-account balance password)
(let ((err-count 0) (ERR-LIMIT 7))
(define (withdraw amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds"
)
)
(define (deposit amount)
(set! balance (+ balance amount))
balance
)
(define (incorrect DUMMY)
(begin (set! err-count (+ err-count 1))
(if (>= err-count ERR-LIMIT)
"call-the-cops!!"
"Incorrect password"
)
)
)
(define (dispatch p m)
(if (eq? p password)
(cond ((eq? m 'withdraw) withdraw)
((eq? m 'deposit) deposit)
(else (error "Unknown request: MAKE-ACCOUNT"))
)
incorrect
)
)
dispatch
)
)
実行結果:
> (define acc (make-account 100 'secret-password))
> ((acc 'secret-password 'withdraw) 40)
60
> ((acc 'some-other-password 'deposit) 50)
"Incorrect password"
> ((acc 'some-other-password 'deposit) 50)
"Incorrect password"
> ((acc 'some-other-password 'deposit) 50)
"Incorrect password"
> ((acc 'some-other-password 'deposit) 50)
"Incorrect password"
> ((acc 'some-other-password 'deposit) 50)
"Incorrect password"
> ((acc 'some-other-password 'deposit) 50)
"Incorrect password"
> ((acc 'some-other-password 'deposit) 50)
"call-the-cops!!"