読者です 読者をやめる 読者になる 読者になる

@uents blog

Code wins arguments.

SICP 読書ノート#23 - 2.5.1 汎用算術演算 (pp.110-113)

「§2.5 汎用演算システム」から。

この章は2章でこれまで学んだことの応用問題といった感じ。データ主導を使って汎用演算システムを構築して行きます。

全体のソースコードGitHubに置いています。

汎用算術演算

まずは写経。

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 異なる型のデータの統合」から。


※「SICP読書ノート」の目次はこちら