汎用算術演算手続きを次のように定義する:
(define (add x y) (apply-generic 'add x y)) (define (sub x y) (apply-generic 'sub x y)) (define (mul x y) (apply-generic 'mul x y)) (define (div x y) (apply-generic 'div x y))
通常(ordinary)の, つまりわれわれの言語に基本の数を扱うパッケージの設定から始める. これらに記号scheme-numberのタグをつける. このパッケージの算術演算は基本の算術演算手続きである. (だからタグをはずした数を扱う手続きを定義する必要はない.) これらの演算は二つの引数をとるので, 表にはリスト(scheme-number scheme-number)のキーで設定する:
(define (install-scheme-number-package) (define (tag x) (attach-tag 'scheme-number x)) (put 'add '(scheme-number scheme-number) (lambda (x y) (tag (+ x y)))) (put 'sub '(scheme-number scheme-number) (lambda (x y) (tag (- x y)))) (put 'mul '(scheme-number scheme-number) (lambda (x y) (tag (* x y)))) (put 'div '(scheme-number scheme-number) (lambda (x y) (tag (/ x y)))) (put 'make 'scheme-number (lambda (x) (tag x))) 'done)
Scheme数パッケージの利用者は, (タグつきの)通常の数を手続き:
(define (make-scheme-number n) ((get 'make 'scheme-number) n))を使って作り出す.
汎用算術演算システムの枠組が出来たので, 次の種類の数を取り込もう. これは有理数算術演算を実行するパッケージである. 加法性のおかげで, 2.1.1節の有理数のプログラムを, このパッケージの内部手続きとして, 修正なしに使うことが出来る:
(define (install-rational-package) ;; 内部手続き (define (numer x) (car x)) (define (denom x) (cdr x)) (define (make-rat n d) (let ((g (gcd n d))) (cons (/ n g) (/ d g)))) (define (add-rat x y) (make-rat (+ (* (numer x) (denom y)) (* (numer y) (denom x))) (* (denom x) (denom y)))) (define (sub-rat x y) (make-rat (- (* (numer x) (denom y)) (* (numer y) (denom x))) (* (denom x) (denom y)))) (define (mul-rat x y) (make-rat (* (numer x) (numer y)) (* (denom x) (denom y)))) (define (div-rat x y) (make-rat (* (numer x) (denom y)) (* (denom x) (numer y)))) ;; システムの他の部分へのインターフェース (define (tag x) (attach-tag 'rational x)) (put 'add '(rational rational) (lambda (x y) (tag (add-rat x y)))) (put 'sub '(rational rational) (lambda (x y) (tag (sub-rat x y)))) (put 'mul '(rational rational) (lambda (x y) (tag (mul-rat x y)))) (put 'div '(rational rational) (lambda (x y) (tag (div-rat x y)))) (put 'make 'rational (lambda (n d) (tag (make-rat n d)))) 'done) (define (make-rational n d) ((get 'make 'rational) n d))
タグcomplexを使い, 複素数を扱う同様なパッケージが設定出来る. このパッケージを作るのに, われわれは表から, 直交座標と極座標パッケージで定義された演算make-from-real-imagとmake-from-mag-angを取り出す. 加法性により, 内部手続きとして2.4.1節のadd-complex, sub-complex, mul-complexおよびdiv-complexを使うことが出来る.
(define (install-complex-package) ;; 直交座標と極座標パッケージから取り入れた手続き (define (make-from-real-imag x y) ((get 'make-from-real-imag 'rectangular) x y)) (define (make-from-mag-ang r a) ((get 'make-from-mag-ang 'polar) r a)) ;; 内部手続き (define (add-complex z1 z2) (make-from-real-imag (+ (real-part z1) (real-part z2)) (+ (imag-part z1) (imag-part z2)))) (define (sub-complex z1 z2) (make-from-real-imag (- (real-part z1) (real-part z2)) (- (imag-part z1) (imag-part z2)))) (define (mul-complex z1 z2) (make-from-mag-ang (* (magnitude z1) (magnitude z2)) (+ (angle z1) (angle z2)))) (define (div-complex z1 z2) (make-from-mag-ang (/ (magnitude z1) (magnitude z2)) (- (angle z1) (angle z2)))) ;; システムの他の部分へのインターフェース (define (tag z) (attach-tag 'complex z)) (put 'add '(complex complex) (lambda (z1 z2) (tag (add-complex z1 z2)))) (put 'sub '(complex complex) (lambda (z1 z2) (tag (sub-complex z1 z2)))) (put 'mul '(complex complex) (lambda (z1 z2) (tag (mul-complex z1 z2)))) (put 'div '(complex complex) (lambda (z1 z2) (tag (div-complex z1 z2)))) (put 'make-from-real-imag 'complex (lambda (x y) (tag (make-from-real-imag x y)))) (put 'make-from-mag-ang 'complex (lambda (r a) (tag (make-from-mag-ang r a)))) 'done)
複素数パッケージ外のプログラムは, 実部と虚部とから, または絶対値と偏角とから複素数を構成することが出来る. 元々直交座標や極座標パッケージで定義した基盤になる手続きが, 複素数パッケージへ持ち出され, 更に外の世界に持ち出される方法に注意しよう.
(define (make-complex-from-real-imag x y) ((get 'make-from-real-imag 'complex) x y)) (define (make-complex-from-mag-ang r a) ((get 'make-from-mag-ang 'complex) r a))
ここにあるのは二レベルのタグシステムで, 直交座標の3 + 4iのような普通の複素数は図2.24のように表現される. 外側のタグ(complex)は数を複素数パッケージへ導くのに使われる. 複素数パッケージに入れば次のタグ(rectangular)が数を直交座標パッケージへ導くのに使われる. 大きく複雑なシステムでは, 多くのレベルがあり, 各レベルが汎用演算により次のシステムとインターフェースをとる. データオブジェクトが「下方へ」渡されるにつれ,
適切な手続きへ導くのに使われた外方のタグは, (contentsを作用させて)剥され, 次のレベルのタグが(あるなら)更なる振分けに使えるよう, 見えてくる.
図2.24 直交座標形式での3 + 4iの表現
上のパッケージで, add-ratやadd-complexや他の算術演算の手続きは, 元々書いた通りに使った. しかしこれらの定義が異る実装手続きの内部になると, それらは相互に違う名前である必要はない: それらのパッケージの中で, 単にadd, sub, mulおよびdivと名づけることが出来る.
問題 2.77
Louis Reasonerはzを図2.24に示すオブジェクトとして, 式(magnitude z)を評価しようとした. 驚いたことに答の5の代りに, 型(complex)には演算magnitudeは対応する手続きはないというapply-genericのエラーメッセージを得た. この対話をAlyssa P. Hackerに見せると, 彼女は「complex数に対して複素数の選択子は定義されていず,
polarとrectangularだけ定義されているのが問題である. これが働くようにするには, complexパッケージに次のものを追加しなければならない」という:
(put 'real-part '(complex) real-part) (put 'imag-part '(complex) imag-part) (put 'magnitude '(complex) magnitude) (put 'angle '(complex) angle)どうしてこれが働くか詳しく述べよ. 例えばzが図2.24に示すオブジェクトとして, 式(magnitude z)を評価する時, 呼び出されるすべての手続きをトレースせよ. 特にapply-genericは何回呼び出されるか. それぞれの場合どの手続きが振り分けられるか.