SICPのお勉強
;;;3.1.3 代入を取り入れた代価 ; ;既に見たようにset!演算により局所状態を持つオブジェクトがモデル化出来る。 ;しかし、この利点はタダではない。 ;我々のプログラム言語は最早、手続き作用の置き換えモデルを使っては解釈出来ない。 ;その上「素敵な」数学的性質を持った単純なモデルで、プログラミング言語のオブジェクトや代入を扱う適切な枠組みとなり得る物はない。 ; ;代入を使わないうちは、同じ引数での同じ手続きを2回評価すると、2回とも同じ結果を生じた。 ;手続きは数学的関数を計算すると見ることができた。 ;本書の初めの2章でやったように、代入を使わないプログラミングは、従って関数型プログラミング(functional programming)という。 ; ;make-withdrawの簡易版 (define (make-simplified-withdraw balance) (lambda (amount) (set! balance (- balance amount)) balance)) (define W (make-simplified-withdraw 25)) ;set!を使わないmake-decrementer (define (make-decrementer balance) (lambda (amount) (- balance amount))) (define D (make-decrementer 25)) ;((make-decrementer 25) 20) ;-> ;((lambda (amount) (- 25 amount)) 20) ;((make-simplified-withdraw 25) 20) ;-> ;((lambda (amount) (set! balance (- 25 amount)) 25) 20) ;-> ;(set! balance (- 25 20)) 25 ; ;ここで置き換えモデルに執着すれば、手続き作用の意味は、まずbalanceに5をセットし、式の値として25を返すことだといわなければならないだろう。 ;これは間違った答えである。 ;正しい答えを得るためには、balanceの最初の出現と2回目の出現を区別しなければならない。 ;set!の効果の前と後だ。 ;しかし、置き換えモデルにはこれはできない。 ; ;問題は我々の言語では、置き換えは究極的には、記号は本質的に値に対する名前であるとの考えに基づいていることである。 ;しかし、set!と、変数の値は変わり得るという考えを取り入れると、変数は最早単なる名前ではない。 ;変数は何かの値が格納される場所を指し、この場所に格納される値は変化出来るのだ。 ;;同一と変化 ; ;ここで浮かび上がった論点は、計算の特定なモデルの単なる崩壊よりも遙かに奥深いものである。 ;計算モデルに変更を取り込むと同時に、前は直裁だった多くの概念が問題となった。 ; (define D1 (make-decrementer 25)) (define D2 (make-decrementer 25)) ;D1とD2は同じだろうか? ;D1とD2は同じ計算上の振る舞いをする-それぞれが25から入力を引く手続きである-から、答えはイエスである。 (define W1 (make-simplified-withdraw 25)) (define W2 (make-simplified-withdraw 25)) ;W1とW2は同じだろうか? ;次の一連の対話で見るように、W1とW2の呼び出しは異なる効果を持つから、確実に違う。 ;W1とW2は(make-simplified-withdraw 25)の評価で作り出されたという意味では「等しい」が、任意の式で、W1をW2に、式の評価の結果を変えることなく置き換えられるということはない。 ; ;式の評価の結果を変えることなく、式の中で「等しい物は等しい物で置き換えられる」という概念の成り立つ言語を参照透明(referentially transparent)であるという。 ;->この場合、D1とD2かな。 ; ;参照透明性は我々の言語にset!を入れたときに破られた。 ;そのため、式をいつ等価な式で置き換えて単純化出来るかを決めることが微妙になってきた。 ;従って代入を使うプログラムについて、推論することが難しくなる。 ; ;参照透明性を一旦捨てると、計算オブジェクトが「同じ」であるとは何を意味するのかの概念を形式的に捕らえるのが難しくなる。 ;実は我々のプログラムがモデル化している実世界の「同じ」の意味もあまりはっきりしない。 ;一般に二つの見かけ上同じなオブジェクトが本当に「同じもの」であるかは、一つのオブジェクトを変えてみて、もうオブジェクトが同じように変わっているかを見て決める。 ;この方法以外にオブジェクトが「変わった」ことは解らない。 ;つまり、何か先験的な「同一」という概念なしに「変化」を決めることは出来ず、変化の効果を観測することなしに同一性を決めることは出来ない。 ; ;->オブジェクトが同一であるかは、オブジェクトの性質をまず観測し、オブジェクトAの性質を変えてみて、もう一度オブジェクトBの性質を観測し、 ; その性質が同じように変わっているか観測することが必要になる。 ; ;この論点がどうプログラミングに現れるかの例としては ; ;(define peter-acc (make-account 100)) ;(define paul-acc (make-account 100)) ; ;とモデル化するのと、 ; ;(define peter-acc (make-account 100)) ;(define paul-acc peter-acc) ; ;とモデル化するのでは、本質的な相違がある。 ;初めの例では二つの銀行口座は別々であるが、2番目の例では同じ口座である。 ;この似ているが違う二つの状況は、計算モデルを構築するのに混乱をもたらす。 ; ;;命令型プログラムの落とし穴 ; ;関数型プログラミングと反対に、代入を多用するプログラミングは、命令型プログラミング(imperative programming)という。 ;計算モデルの複雑性を高める上に、命令型の流儀で書いたプログラムには、関数型プログラムには起こり得ぬバグを入れやすい。 ; ;factorialの例は、関数の返値を用いてiterに引数を渡しているので、counterの値が (iter ...) の中で変化することはない。 ;しかし、命令型の流儀だとcounterを先にset!してしまうとproductに渡される(* ... ) のcounterの値が欲しい値よりも1大きくなってしまう。 ;これが命令型に入り込みやすいバグだ。 ; ;;Q3.7 (print "******************* Q3.7 *******************") (define (make-joint acc pass new-pass) (define (partner? p m) (if (eq? new-pass p) (acc pass m) (error "new-password mismatch"))) partner?) (define (make-account balance pass) (define (withdraw amount) (if (>= balance amount) (begin (set! balance (- balance amount)) balance) "Insufficient funds")) (define (deposit amount) (set! balance (+ balance amount)) balance) (define (dispatch p m) (if (eq? pass p) (cond ((eq? m 'withdraw) withdraw) ((eq? m 'deposit) deposit) (else (error "Unknown request -- MAKE-ACCOUNT" m))) (error "Password mismatch"))) dispatch) (define peter-acc (make-account 100 'open-sesame)) (print "peter-acc") (print ((peter-acc 'open-sesame 'withdraw) 40)) (print ((peter-acc 'open-sesame 'deposit) 20)) ;(print ((peter-acc 'rosebud 'deposit) 20)) (define paul-acc (make-joint peter-acc 'open-sesame 'rosebud)) (print "paul-acc") (print ((paul-acc 'rosebud 'withdraw) 50)) (print ((paul-acc 'rosebud 'deposit) 70)) ;(print ((paul-acc 'open-sesame 'deposit) 70)) ;;Q3.8 (print "******************* Q3.8 *******************") (define (lr-plus f1 f2) (define (dispatch m) (define (plus l r) (+ (force l) (force r))) (cond ((eq? m 'left) (plus f1 f2)) ((eq? m 'right) (plus f2 f1)) (else (error "please left or right.")))) dispatch) (define f (let ((x 1)) (lambda (n) (cond ((eq? n 0) (delay (begin (set! x 0) x))) ((eq? n 1) (delay x)) (else (set! x 1)))))) ;reset (print "left:" ((lr-plus (f 0) (f 1)) 'left)) (f 2) ;reset (print "right:" ((lr-plus (f 0) (f 1)) 'right))