問2.80 は 2.79 と同じなのでパス。
本文
2.5.2 異なるデータの統合
整数と複素数の加算のように、異なる型間で演算をおこなう方法を定義する。
一つの方法は、型の組み合わせ毎に手続きを設計する方法。→非常に煩わしい。
強制型変換
型の組み合わせ毎に手続きを定義するのではなく、ある型を別の型に変換する方法を用意して、型変換を試みるようにする
Philosophy of Hiroshi
問2.80 は 2.79 と同じなのでパス。
2.5.2 異なるデータの統合
整数と複素数の加算のように、異なる型間で演算をおこなう方法を定義する。
一つの方法は、型の組み合わせ毎に手続きを設計する方法。→非常に煩わしい。
強制型変換
型の組み合わせ毎に手続きを定義するのではなく、ある型を別の型に変換する方法を用意して、型変換を試みるようにする
大いにハマった問2.77も済んで、やっと前に進めます。
問 2.78
(define (attach-tag type-tag contents) (if (eq? type-tag 'scheme-number) contents (cons type-tag contents))) (define (type-tag datum) (cond ((number? datum) datum) ((pair? datum) (car datum)) (else (error "Bad tagged datum -- TYPE-TAG" datum)))) (define (contents datum) (cond ((number? datum) datum) ((pair? datum) (cdr datum)) (else (error "Bad tagged datum -- CONTENTS" datum))))
この方法は場当たり的な感じがしてちょっと嫌な感じがするかな。
問 2.79
equ? の大元を定義。
(define (equ? x y) (apply-generic 'div x y))
install-scheme-number-package に追加
(put 'equ? '(scheme-number scheme-number) (eq? x y))
install-rational-package に追加
(define (equ? x y) (if (not (eq? (numer x) (numer x))) #f (if (not (eq? (denom x) (denom x))) #f #t))) (put 'equ? '(rational rational) (lambda (x y) (equ? x y)))
※先人の知恵と比較したら、±の判別が甘かった
(define (equ?-rat x y) (or (and (= (numer x) (numer y)) (= (denom x) (denom y))) (and (= (numer x) (- (numer y))) (= (denom x) (- (denom y))))))
install-rectangular-package に追加
(define (equ? x y) (if (not (eq? (magnitude x) (magnitude y))) #f (if (not (eq? (angle x) (angle y))) #f #t))) (put 'equ? '(rectangular rectangular) (lambda (x y) (equ? x y)))
install-complex-package に追加
(define (equ? x y) (if (not (eq? (real-part x) (real-part y))) #f (if (not (eq? (imag-part x) (imag-part y))) #f #t))) (put 'equ? '(complex complex) (lambda (x y) (equ? x y)))
以上。自分の解答では if をネストしているのがダサい。 and や or を使うべし。
問題解答以前にコードの動作確認ができなくてハマっている問題 2.77。今日こそは片をつけたいものです。
やっぱり、こうなったときにデバッガが無いのは辛いですね、、、
問 2.77
さんざん悩んだ挙句、解決。
(define op-table (make-hash-table 'equal?)) (define (put op type item) (if (not (hash-table-exists? op-table op)) (hash-table-put! op-table op (make-hash-table 'equal?))) (let ((type-table (hash-table-get op-table op))) (hash-table-put! type-table type item))) (define (get op type) (if (not (hash-table-exists? op-table op)) (hash-table-put! op-table op (make-hash-table 'equal?))) (let ((type-table (hash-table-get op-table op))) (hash-table-get type-table type)))
値の比較に、デフォルトの eq? を使っていたのがダメだったようです。
gosh> (equal? '(complex) '(complex)) #t gosh> (eq? '(complex) '(complex)) #f
これはわからないです、、、ぐぬぬぬ。
その他にも typo でハマったりしたものの、やっと動作確認ができました。長かった、、、、、
そしてやっと本題。
(magnitude z) の評価される順番。
(define (magnitude z) (apply-generic 'magnitude z))
で apply-generic が呼ばれる。
(define (apply-generic op . args) (let ((type-tags (map type-tag args))) (let ((proc (get op type-tags))) (if proc (apply proc (map contents args)) (error "No method for these types -- APPLY-GENERIC" (list op type-tags))))))
z は (complex rectangular 3.4) なので、type-tags は (complex)。
proc は complex package で定義した
(put 'magnitude '(complex) magnitude)
で、最初に呼び出されたのと同じ magnitude が抽出される。
二度目の magnutude への引数(apply-generic への引数も同じ)は (map contents args) でタグを一つ剥がされたものなので (rectangular 3 . 4)。
apply-generic では type-tags が (rectangular)、 proc が rectangular package で定義された magnitude。
グローバルで定義されている magnitude との区別がわかりにくいので、便宜上 rectangular.magnitude としておく。
apply-generic で (rectangular.magnitude (3 . 4)) が呼ばれて無事に 5 が返ってくる。
よって、 apply-generic が呼ばれるのは 2 回。
昨日に引き続き問題 2.77 を解いていきます。
問2.77
必要なプログラムを全部用意して、図 2.24 の z を用意するところまでは動作確認完了。
次は。。。ということで magnitude の動作確認をしようとしてエラー。どうやら Gauche 標準で用意されている magnitude が呼ばれて、期待したものが呼ばれていないっぽい。→2.4 にあった apply-generic まわりの定義をしていなかったのが原因。
。。。。そして、結局今日はうまく magnitude が実行できないところでハマって時間切れ。
やっぱり、わかってるからってヒョイヒョイ飛ばしてしまうと詰まった時に手戻りが辛いですね、、、、反省。
仕事の締切やらその後の溜まったタスクの消化やら集中力が続かないやら(これがほとんど)で気づけば一週間ぶりの SICP です。ここで途切れないように粛々と続けていきます。
今やっているあたりはプログラムを書いていれば当たり前な内容なので、問題をこなすためにモロモロの準備をするのが結構ダルいというのもあって微妙に乗り気になれないのがテンション低めな原因です。かと言って、問題をこなしたらこなしたでいろいろ発見があるので飛ばすわけにはいかないんですね。
問 2.77
いい加減 put/get が手元のないとどうしようもないので先人の知恵を借りよう。。。。とおもったらひげぽんさんの記事につきあたる。同じ所まで同じ教科書で勉強してみてわかるけど、この人すごいな、、、、数倍のスピードで数倍以上の習得度があるorz。これだけの差をガツンと目の前に突きつけられると凹む。→記事を最後まで読んだら、問題2.74 をやるのに3時間くらいって書いてありました。成果とかかった時間がわかるとすごくいい目安になります。
この問題がおわったら、これまでのふりかえりともあわせてここまでのひげぽんさんの記事と自分の理解した内容を突き合わせよう。
ひげぽんさんの作った put/get はこちら。
(define op-table (make-hash-table)) (define (put op type item) (if (not (hash-table-exists? op-table op)) (hash-table-put! op-table op (make-hash-table))) (let ((type-table (hash-table-get op-table op))) (hash-table-put! type-table type item))) (define (get op type) (if (not (hash-table-exists? op-table op)) (hash-table-put! op-table op (make-hash-table))) (let ((type-table (hash-table-get op-table op))) (hash-table-get type-table type)))
get って、op がなかったら結局 type を取得する時にエラーになるから
(define (get op type) (let ((type-table (hash-table-get op-table op))) (hash-table-get type-table type)))
これでも同じなんじゃないだろうか?
とにかく、これで put/get が手に入ったので問題にとりかかります。
(put 'real-part '(complex) real-part)
これが get されたときの挙動を確認。”‘(complex)” の部分がよくわからない。
そもそも complex がどこで定義されていたっけ? →単に (complex) というリストを作っているだけだった。このレベルで忘れているとは、、、、
そもそも (make-complex-from-real-imag 3 4) が正常に動かない。調べると op-table に ‘make-from-real-imag は登録されているものの、その先が空っぽいんだけど何故!?
ちょっと時間がかかりすぎたので今日はここまで。明日再チャレンジ。
2.5 汎用演算のシステム
異なる種類の引数に対して汎用的な演算を定義する。
2.5.1 汎用算術演算
既存のコードを流用しつつ各種演算に対応していく。
問2.74
パス
メッセージパッシング
手続き名毎に内部で実際に呼び出す処理を振り分ける。
データを生成すると実態は手続きが帰ってきて、そのデータ(手続き)に対して必要な演算の名前を渡すことで必要な結果を得ることができる
問2.75
手の運動なのでパス。
問2.76
汎用演算 :
型・演算の追加共にシステム全体に対して影響を及ぼす。
データ主導流 :
新しい型が追加された場合に、その型に対応したパッケージを用意する。新しい演算が追加された場合、全てのパッケージに対して新しい演算を定義する。そのため、型の追加に対しては全体への影響は少ないが演算の追加に対してはシステム全体に影響を及ぼす。
メッセージパッシング流 :
新しい型が追加された場合に、全てのパッケージに対して新しい型を定義する。新しい演算が追加された場合、その演算に対応した手続きを定義する。そのため、型の追加に対してはシステム全体に影響を及ぼすが、演算の追加に対してはシステム全体に影響が少ない。
ここまで来たけど、イマイチ用語が肌に馴染んでいない感がある。構成子とかパッケージとか演算とか、SICP 的な用語の感覚が何だか曖昧。
コードの入力と内容の確認がおわったので問題に戻ります。
問2.73
b.
和の微分は (u+v)’ = u’+v’
積の微分は (uv)’ = uv’+u’v
(define (deriv-package) (define (deriv-sum exp var) (make-sum (deriv (addend exp) var) (deriv (augend exp) var))) (define (deriv-product exp var) (make-sum (make-product (multiplier exp) (deriv (multiplicand exp) var)) (make-product (deriv (multiplier exp) var) (multiplicand exp)))) (put 'deriv '+ deriv-sum) (put 'deriv '* deriv-product))
こんな感じ? put/get がないのでちゃんと動作確認ができない。
ちょっと(かなり)手抜きで、答えを見ながら完全に動作するコードをコピペして動作を確認。
2.73 を解くために必要なコードの打ち込みが完了。
そして時間切れ。
何やかんやで余計なことをいろいろしてしまってなかなか集中できない。。。。
問題の解答をするためには本文で解説されているコードを一通り打ち込まないとやっぱりダメですね。
めんどくさいけどこれも練習なので今更ながら打ち込み。
汎用複素数システムのぶぶんまでの打ち込みが完了して、今日は終了。