SICP 読書ノート#23 - 2.5.1 汎用算術演算 (pp.110-113)
「§2.5 汎用演算システム」から。
この章は2章でこれまで学んだことの応用問題といった感じ。データ主導を使って汎用演算システムを構築して行きます。
汎用算術演算
まずは写経。
scheme number パッケージ
(define (install-scheme-number-package) ;; interface (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) ;; constructor (define (make-scheme-number n) ((get 'make 'scheme-number) n)) (install-scheme-number-package)
有理数パッケージ
(define (install-rational-package) ;; internal (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)))) ;; interface (define (tag x) (attach-tag 'rational x)) (put 'numer 'rational (lambda (r) (numer r))) (put 'denom 'rational (lambda (r) (denom r))) (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) ;; constructor (define (make-rational n d) ((get 'make 'rational) n d)) (install-rational-package)
複素数パッケージ
まず直交座標形式、極座標形式のパッケージを定義する。
(define (install-rectangular-package) ;; internal (define (real-part z) (car z)) (define (imag-part z) (cdr z)) (define (magnitude-part z) (let ((x (real-part z)) (y (imag-part z))) (sqrt (+ (* x x) (* y y))))) (define (angle-part z) (atan (imag-part z) (real-part z))) (define (make-from-real-imag x y) (cons x y)) (define (make-from-mag-ang r a) (cons (* r (cos a)) (* r (sin a)))) ;; interface (define (tag x) (attach-tag 'rectangular x)) (put 'real-part '(rectangular) real-part) (put 'imag-part '(rectangular) imag-part) (put 'magnitude-part '(rectangular) magnitude-part) (put 'angle-part '(rectangular) angle-part) (put 'make-from-real-imag 'rectangular (lambda (x y) (tag (make-from-real-imag x y)))) (put 'make-from-mag-ang 'rectangular (lambda (r a) (tag (make-from-mag-ang r a)))) 'done) (define (install-polar-package) ;; internal (define (magnitude-part z) (car z)) (define (angle-part z) (cdr z)) (define (make-from-mag-ang r a) (cons r a)) (define (real-part z) (* (magnitude-part z) (cos (angle-part z)))) (define (imag-part z) (* (magnitude-part z) (sin (angle-part z)))) (define (make-from-real-imag x y) (cons (sqrt (+ (* x x) (* y y))) (atan y x))) ;; interface (define (tag x) (attach-tag 'polar x)) (put 'real-part '(polar) real-part) (put 'imag-part '(polar) imag-part) (put 'magnitude-part '(polar) magnitude-part) (put 'angle-part '(polar) angle-part) (put 'make-from-real-imag 'polar (lambda (x y) (tag (make-from-real-imag x y)))) (put 'make-from-mag-ang 'polar (lambda (r a) (tag (make-from-mag-ang r a)))) 'done) ;; constructors (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)) (install-rectangular-package) (install-polar-package)
続いて複素数パッケージ。
(define (install-complex-package) ;; internal (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-part z1) (magnitude-part z2)) (+ (angle-part z1) (angle-part z2)))) (define (div-complex z1 z2) (make-from-mag-ang (/ (magnitude-part z1) (magnitude-part z2)) (- (angle-part z1) (angle-part z2)))) ;; interface (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) ;; constructors (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)) (install-complex-package)
その他
演算テーブル、型タグシステム、汎用演算手続きは前回までと同じ。
演算テーブル。
(define *op-table* (make-hash)) (define (put op type item) (if (not (hash-has-key? *op-table* op)) (hash-set! *op-table* op (make-hash)) true) (hash-set! (hash-ref *op-table* op) type item)) (define (get op type) (define (not-found . msg) (display msg (current-error-port)) (display "\n") false) (if (hash-has-key? *op-table* op) (if (hash-has-key? (hash-ref *op-table* op) type) (hash-ref (hash-ref *op-table* op) type) (not-found "Bad key -- TYPE" type)) (not-found "Bad key -- OPERATION" op)))
型タグシステム。
(define (attach-tag type-tag contents) (cons type-tag contents)) (define (type-tag datum) (if (pair? datum) (car datum) (error "Bad tagged datum -- TYPE-TAG" datum))) (define (contents datum) (if (pair? datum) (cdr datum) (error "Bad tagged datum -- CONTENTS" datum)))
汎用演算手続き。
(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)))))) (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)) (define (real-part z) (apply-generic 'real-part z)) (define (imag-part z) (apply-generic 'imag-part z)) (define (magnitude-part z) (apply-generic 'magnitude-part z)) (define (angle-part z) (apply-generic 'angle-part z)) (define (numer r) (apply-generic 'numer r)) (define (denom r) (apply-generic 'denom r))
問題 2.77
これまでの実装だけでは、複素数のmagnitudeは得られない。
racket@> (define z (cons 'complex (cons 'rectangular (cons 3 4)))) racket@> (magnitude-part z) No method for these types -- APPLY-GENERIC (magnitude-part ((complex))) context...: /Applications/Racket6.0.1/collects/racket/private/misc.rkt:87:7
ログの通り、複素数(complex)型に対するmagnitude-partのインターフェースがないため、 複素数パッケージに以下を追加する。
(put 'real-part '(complex) real-part) (put 'imag-part '(complex) imag-part) (put 'magnitude-part '(complex) magnitude-part) (put 'angle-part '(complex) angle-part)
apply-genericにtraceを仕込んで実行。
racket@> (require racket/trace) racket@> (trace apply-generic) racket@> (magnitude-part z) >(apply-generic 'magnitude-part '(complex rectangular 3 . 4)) >(apply-generic 'magnitude-part '(rectangular 3 . 4)) <5 5
apply-genericは2回呼び出される。
問題 2.78
type-tag、contents および attach-tag を修正し、通常の数も扱えるようにする。
修正前の場合、
racket@> (add (make-scheme-number 3) (make-scheme-number 4)) '(scheme-number . 7) racket@> (add 3 4) Bad tagged datum -- TYPE-TAG 3 context...: /Users/uents/work/sicp/ch2.5.1.scm:61:0: apply-generic /Applications/Racket6.0.1/collects/racket/private/misc.rkt:87:7
scheme-number型は問題ないが、通常の数ではエラーする。
よって、type-tag、contentsを以下のように修正。
o(define (type-tag datum) (cond ((number? datum) 'scheme-number) ((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))))
修正後の実行結果。
racket@> (add 3 4) '(scheme-number . 7)
問題 2.79
2つの数が透過かどうかをテストする述語手続き equ? を定義する。
scheme-numberパッケージに以下を追加。
(put 'equ? '(scheme-number scheme-number) (lambda (x y) (= x y)))
有理数パッケージに以下を追加。
(put 'equ? '(rational rational) (lambda (x y) (= (* (numer x) (denom y)) (* (numer y) (denom x)))))
複素数パッケージに以下を追加。
(put 'equ? '(complex complex) (lambda (x y) (and (= (magnitude-part x) (magnitude-part y)) (= (angle-part x) (angle-part y)))))
equ? 汎用演算手続きを定義。
(define (equ? x y) (apply-generic 'equ? x y))
テスト。
racket@> (equ? 3 3) #t racket@> (equ? 3 (make-scheme-number 3)) #t racket@> (equ? (make-rational 2 3) (make-rational 6 9)) #t racket@> (equ? (make-complex-from-real-imag 0 1) (make-complex-from-mag-ang 1 (/ pi 2))) #t
ただし異なる型同士の比較はできない。
racket@> (equ? (make-scheme-number 3) (make-rational 6 2)) (Bad key -- TYPE (scheme-number rational)) No method for these types -- APPLY-GENERIC (equ? (scheme-number rational)) context...: /Applications/Racket6.0.1/collects/racket/private/misc.rkt:87:7
このトピックについては次節で登場する。
問題 2.80
引数が0かどうかをテストする述語 =zero? を追加する。
scheme-numberパッケージに以下を追加。
(put '=zero? '(scheme-number) (lambda (x) (= x 0)))
有理数パッケージに以下を追加。
(put '=zero? '(rational) (lambda (x) (= (numer x) 0)))
複素数パッケージに以下を追加。
(put '=zero? '(complex) (lambda (x) (= (magnitude-part x) 0)))
=zero? 汎用演算手続きを追加。
(define (=zero? x) (apply-generic '=zero? x))
テスト。
racket@> (=zero? 0) #t racket@> (=zero? (make-scheme-number 0)) #t racket@> (=zero? (make-rational 0 3)) #t racket@> (=zero? (make-complex-from-mag-ang 0 pi)) #t
次回は「§2.5.2 異なる型のデータの統合」から。