2.4 Multiple Representations for Abstract Data

斜め読みしてたらどこのコード使えばいいか分からなくなった。
データを複数のやり方で表現するよ。ちょうど複素数の直交座標・極座標表示が身近なのでその例を使います。

前準備

put,getは予めあるものとして進めているので(ずいぶん不親切だ)、適当に定義しておく。

(define-values
  (put get)
  (let ((*table* '()))
    (values
      (lambda (op type proc)
        (set! *table* (cons (cons (cons op type) proc) *table*)))
      (lambda (op type)
        (let ((key (cons op type)))
          (let ((item (assoc key *table*)))
            (and item (cdr item)))))
      )))

2.73 (data-directed style)

(define (deriv e var)
  (cond ((number? e) 0)
        ((variable? e) (if (same-variable? e var) 1 0))
        (else ((get 'deriv (operator e)) (operands e) var))))

(define (operator e) (car e))
(define (operands e) (cdr e))

(define (variable? x) (symbol? x))
(define (same-variable? v1 v2)
  (and (variable? v1) (variable? v2) (eq? v1 v2)))
(define (=number? e num)
  (and (number? e) (= e num)))

(define (install-deriv-package)
  (define first car)
  (define second cadr)
  (define (addend s) (cadr s))
  (define (augend s) (caddr s))
  (define (make-sum a1 a2)
    (cond ((=number? a1 0) a2)
          ((=number? a2 0) a1)
          ((and (number? a1) (number? a2) (+ a1 a2)))
          (else (list '+ a1 a2))))
  (define (make-product m1 m2)
    (cond ((or (=number? m1 0) (=number? m2 0)) 0)
          ((=number? m1 1) m2)
          ((=number? m2 1) m1)
          ((and (number? m1) (number? m2)) (* m1 m2))
          (else (list '* m1 m2))))
  (define (make-exponentation base exponent)
    (cond ((=number? exponent 0) 1)
          ((=number? exponent 1) base)
          ((=number? base 1) 1)
          (else (list '** base exponent))))
  (put 'deriv '+ (lambda (operands var)
                   (make-sum (deriv (first operands) var)
                             (deriv (second operands) var))))
  (put 'deriv '* (lambda (operands var)
                   (make-sum
                     (make-product (first operands)
                                   (deriv (second operands) var))
                     (make-product (deriv (first operands) var)
                                   (second operands)))))
  (put 'deriv '** (lambda (operands var)
                    (make-product
                      (make-product
                        (second operands)
                        (make-exponentation
                          (first operands)
                          (make-sum (second operands) -1)))
                      (deriv (first operands) var))))
  )

(install-deriv-package)
(deriv '(+ x 10) 'x)
(deriv '(* x y) 'x)
(deriv '(** x 100) 'x)

2.75 (message-passing style)

(define (make-from-real-imag x y)
  (define (dispatch op)
    (cond ((eq? op 'real-part) x)
          ((eq? op 'imag-part) y)
          ((eq? op 'magnitude)
           (sqrt (+ (square x) (square y))))
          ((eq? op 'angle) (atan y x))
          (else
           (error "Unknown op -- MAKE-FROM-REAL-IMAG" op))))
  dispatch)

(define (make-from-mag-ang r a)
  (define (dispatch op)
    (cond ((eq? op 'real-part) (* r (cos a)))
          ((eq? op 'imag-part) (* r (sin a)))
          ((eq? op 'magnitude) r)
          ((eq? op 'angle) a)
          (else
           (error "Unknown op -- MAKE-FROM-MAG-ANG" op))))
  dispatch)

2.76

新しい型がたくさん増えるようなときは、一つの型でメソッドがまとまってるmessage-passingスタイルの方が効率良さそう。じゃあ逆にメソッドの方が大量に増えるようなときはメソッドが一箇所にまとまってるdata-directedスタイルの方がいいのかな?