プログラミング再入門

プログラミングをもう一度ちゃんと勉強する読書ノート

SICP 2.4 Multiple Representations for Abstract Data

data-directed programmingと言う言葉は聞いた事はなかったけど、状態マシンの実装の様に取るべきアクションが表になっていて、これに応じて動作するプログラムの話かと思いきや、実行時型情報的な話から総称関数、message passingに繋がり、いつのまにかオブジェクト指向な話に。

ノート

前節までにデータの実装とそのデータを使ったプログラムを分離する事を勉強した。
次に同じデータに対して複数の表現(実装)方法を使い分けるのが都合が良いケースを考える。使い分けるとは開発途中に要求が変わってしまったり、既存のコードを変更する様なケースを想定している。
ここでは総称関数(generic procedure)を扱う。データにはタグで明示的にそのタイプが記されている方式を取る。
data-directed programmingは『データ主導プログラミング』と訳されている様だが、SICP以外では見かけない呼び方。詳細は後で出て来るが、表の様なデータに基づいて動作を決める様なプログラミング方法の事を指している様に思える。操作と引数の型から呼び出すべき関数を引き出す表を用いて関数呼び出しをするディスパッチャの例が出て来る。
複素数を例に総称的セレクタを用いて実装が直交座標系でも極座標系でも動作可能な演算を定義する。

2.4.1 Representations for Complex Numbers

複素数の加算は直交座標系の方が簡単で、乗算は極座標系の方が簡単。状況によってどちらの方が適しているかは変わる。直交座標系で実装しているか、極座標系で実装しているかによってセレクタの実装方法が変わって来る。
DrRacketのschemeモードで実際に動かしてみる。squareだけ定義が無いので追加しておく。
まずはBenの実装。

> (make-from-real-imag 1 0)
(1 . 0)
> (real-part (make-from-real-imag 1 0))
1
> (imag-part (make-from-real-imag 1 0))
0
> (add-complex (make-from-real-imag 1 2) (make-from-real-imag 3 4))
(4 . 6)
> (sub-complex (make-from-real-imag 3 4) (make-from-real-imag 1 2))
(2 . 2)
> (mul-complex (make-from-real-imag 1 2) (make-from-real-imag 3 4))
(-5.0 . 10.0)
> (div-complex (make-from-real-imag 1 2) (make-from-real-imag 3 4))
(0.44 . 0.07999999999999999)
> (make-from-mag-ang 1 pi)
(-1.0 . 1.2246467991473532e-16)
> 

次にAlyssaの実装。

> (make-from-real-imag 1 2)
(2.23606797749979 . 1.1071487177940904)
> (make-from-mag-ang 1 (/ pi 4))
(1 . 0.7853981633974483)
> (add-complex (make-from-mag-ang 1 (/ pi 4)) (make-from-mag-ang 2 (/ pi 6)))
(2.9771972230868875 . 0.6106424421303817)
> (sub-complex (make-from-mag-ang 1 (/ pi 4)) (make-from-mag-ang 2 (/ pi 6)))
(1.0659721829596336 . -2.8632460280389096)
> (mul-complex (make-from-mag-ang 1 (/ pi 4)) (make-from-mag-ang 2 (/ pi 6)))
(2 . 1.308996938995747)
> (div-complex (make-from-mag-ang 1 (/ pi 4)) (make-from-mag-ang 2 (/ pi 6)))
(1/2 . 0.26179938779914946)
> 

データの実装方法が異なっていても同じ演算ルーチンがちゃんと動く。
ただし問題はこの二つの実装は同じコンストラクタセレクタの名前を使っているので同居出来ない。

2.4.2 Tagged data

タグ付きデータ。
Principle of least commitment:最小責任原則と訳されているらしい。
BenとAlyssaの実装を同居させて場合に応じて使い分けるにはどうするか。
データにシンボルでrectangulrarとかpolarとタグを付けて、どちらのデータなのか区別出来る様にし、直交座標系用と極座標系用のセレクタをそれぞれ区別出来る様にポストフィックスを付ける。
その上で共通の入り口となるセレクタを定義する。その中では与えられたデータのタグによって直交座標系用か極座標系用のセレクタを呼び分ける。

セレクタのみ新しい実装にして、演算ルーチンは2.4.1のまま実際に動かしてみる。

> (add-complex (make-from-real-imag 1 2) (make-from-real-imag 3 4))
(rectangular 4 . 6)
> (mul-complex (make-from-mag-ang 1 (/ pi 4)) (make-from-mag-ang 1 (/ pi 4)))
(polar 1 . 1.5707963267948966)
> 

これでBenとAlyssaの実装は同居出来て、最初にどちらの実装でオブジェクトを作っても共通の演算ルーチンで動作可能となった。

2.4.3 Data-Directed Programming and Additivity

additivityの訳は難しい。加成性とかあるが、ここでは型の追加の容易性の事を指している。
ここまでの実装ではあるデータの新しい実装方法を導入するにはセレクタを書き換える必要がある。つまりセレクタはサポートしている全ての方を知っている必要がある。
またひとつの目的のセレクタであってもデータの実装方式によって別々の名前を付けなければならず不便。
この問題の解決策として、操作と引数の型から呼び出すべき関数が取り出せる表を使って正しい関数を呼び出すディスパッチャを導入する。新しい型を増やした時にはこの表にエントリーを追加するだけで既存のコードを変更する必要は無い。
登録する関数は例えばそれを登録する関数の内部関数として定義すると、他の実装方法と同じ名前であっても名前の衝突は生じない。
apply-genericは突然色々と難しい記法が出て来る。まずは可変長引数args。opより後ろの引数全てがargsにリストとして入って来る。applyは最初の引数を手続きとし、リストである第2引数に適用する。apply-genericではargsのタイプをmapを使って取り出すので、type-tagsはリストになる。これはC言語シグニチャの様にそれぞれの引数の型情報となる。オペレーションのタグと引数の型リストから呼び出す手続きを見つける。最後にapplyは脚注42に書いてある通り第2引数のリストのそれぞれの要素を引数として第1引数を適用する。第1引数の手続きに引数としてひとつのリストが渡るのではなく、第1引数の手続きの複数の引数にリストの要素がそれぞれ渡される。

コードを実際に動かしてみる。テキストにある通り3.3.3節のmake-table、put、getを使うが、これらを使う為にはset-cdr!が有効でなければならないのでracketではR5RSモードを使う。ただしそうするとerrorが未定義になってしまう。試行錯誤の結果scheme互換モードで

#lang scheme
(require r5rs)
以下省略

とすると、error関数も定義されていてset-cdr!も使える事が分かった。

さて、ここでコードを実行すると更に問題が生じる。

. module: identifier is already imported in: real-part
> 

R5RSに既にreal-partは定義されていてmake-rectangularかmake-polarで作った複素数実装のセレクタと衝突を起こしている。

> (make-rectangular 1 2)
1+2i
> (real-part (make-rectangular 1 2))
1
> (make-polar 1 (/ pi 4))
0.7071067811865476+0.7071067811865475i
> (real-part (make-polar 1 (/ pi 4)))
0.7071067811865476
> 

なのでここではmy-を付けて衝突を避ける。これに伴い演算ルーチンで使うセレクタの名前も変更する。これ以外のローカルな定義のreal-part等は変更の必要は無い。

(define (my-real-part z) (apply-generic 'real-part z))
(define (my-imag-part z) (apply-generic 'imag-part z))
(define (my-magnitude z) (apply-generic 'magnitude z))
(define (my-angle z) (apply-generic 'angle z))

(define (add-complex z1 z2)
  (make-from-real-imag (+ (my-real-part z1) (my-real-part z2))
                       (+ (my-imag-part z1) (my-imag-part z2))))
(define (sub-complex z1 z2)
  (make-from-real-imag (- (my-real-part z1) (my-real-part z2))
                       (- (my-imag-part z1) (my-imag-part z2))))
(define (mul-complex z1 z2)
  (make-from-mag-ang (* (my-magnitude z1) (my-magnitude z2))
                     (+ (my-angle z1) (my-angle z2))))
(define (div-complex z1 z2)
  (make-from-mag-ang (/ (my-magnitude z1) (my-magnitude z2))
                     (- (my-angle z1) (my-angle z2))))

動作確認。

> (install-rectangular-package)
done
> (install-polar-package)
done
> (add-complex (make-from-real-imag 1 2) (make-from-real-imag 3 4))
{rectangular 4 . 6}
> (mul-complex (make-from-mag-ang 1 (/ pi 4)) (make-from-mag-ang 1 (/ pi 4)))
{polar 1 . 1.5707963267948966}
> 

実は良く見ると返って来た答えの表記が丸括弧ではなく中括弧になってRacketの(?)表現になっている模様。

Exercise 2.73

a.
sum?とproduct?はそれぞれ

(define (sum? x)
  (and (pair? x) (eq? (car x) '+)))
(define (product? x)
  (and (pair? x) (eq? (car x) '*)))

だった。どちらも最初のシンボルを取り出して、それが何かを判断している。
getは最初の引数は何に対する関数を引き出すのかを決めるシンボル、2番目の引数がそれぞれの関数を区別する為のシンボル。ここではderivに関する関数で、式の演算子が+か*かで関数を引き出す事になる。
ここで引き出す関数の引数は式の残りの引数と微分変数。
getを呼ぶ時に(operator exp)を呼んでいるが、これは実際には(car exp)であり、expが式(リスト)ではなくただの数字であった場合にはエラーになってしまう。variable?も同様。
same-variable?の場合引数がexpだけでなくvarも必要なので、これらを含めてgetの式に纏める事は出来ない。
b.
元のderivの内部に定義されていた加算と乗算の部分を独立の関数として定義してテーブルに登録する。関数は演算子+か*のシンボルで振り分ける。元々演算子も含んだexpは演算子を除いたoperandsで渡されるのでaddend、augend、multiplier、multiplicandを書き換える必要がある。

(define (install-deriv-package)
  (define (deriv-sum args var)
    (make-sum (deriv (addend args) var)
              (deriv (augend args) var)))
  (define (deriv-product args var)
    (make-sum
     (make-product (multiplier args)
                   (deriv (multiplicand args) var))
     (make-product (deriv (multiplier args) var)
                   (multiplicand args))))
  ;
  (put 'deriv '+ deriv-sum)
  (put 'deriv '* deriv-product))
(define (addend s) (car s))
(define (augend s) (cadr s))
(define (multiplier p) (car p))
(define (multiplicand p) (cadr p))

2.3.2節の定義を使って実行。

done
> (install-deriv-package)
ok
> (deriv '(* (* x y) (+ x 3)) 'x)
{+ {* x y} {* y {+ x 3}}}
> 

c.
Exercise 2.56の答えから借用して。

(define (install-deriv-package)
  (define (deriv-sum args var)
    (make-sum (deriv (addend args) var)
              (deriv (augend args) var)))
  (define (deriv-product args var)
    (make-sum
     (make-product (multiplier args)
                   (deriv (multiplicand args) var))
     (make-product (deriv (multiplier args) var)
                   (multiplicand args))))
  (define (deriv-exponent args var)
    (make-product
     (make-product (exponent args)
                   (make-exponentiation (base args) (make-sum (exponent args) -1)))
     (deriv (base args) var)))
  ;
  (put 'deriv '+ deriv-sum)
  (put 'deriv '* deriv-product)
  (put 'deriv '** deriv-exponent))

(define (base s) (car s))
(define (exponent s) (cadr s))

再びschemeモードでr5rsを取り込んで実行。

done
> (install-deriv-package)
ok
> (deriv '(** 2 3) 'x)
0
> (deriv '(** x 3) 'x)
{* 3 {** x 2}}
> (deriv '(** x y) 'x)
{* y {** x {+ y -1}}}
> (deriv '(* (+ 3 y) (+ y (** x 4))) 'x)
{* {+ 3 y} {* 4 {** x 3}}}
> (deriv '(** (* 2 (** x 2)) (* 4 x)) 'x)
{* {* {* 4 x} {** {* 2 {** x 2}} {+ {* 4 x} -1}}} {* 2 {* 2 x}}}
> (deriv '(+ (** x 2) (* 3 x)) 'x)
{+ {* 2 x} 3}
> 

d.
2次元の表の行と列を入れ替えるだけなので基本的には何ら変わらない。表に登録する時のキーの順番を入れ替えて、呼び出す時のキーの順番を入れ替えるのみ。

(define (install-deriv-package)
中略
  ;
  (put '+ 'deriv deriv-sum)
  (put '* 'deriv deriv-product)
  (put '** 'deriv deriv-exponent))

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

実行結果。

done
> (install-deriv-package)
ok
> (deriv '(* (* x y) (+ x 3)) 'x)
{+ {* x y} {* y {+ x 3}}}
> (deriv '(+ (** x 2) (* 3 x)) 'x)
{+ {* 2 x} 3}
> 
Exercise 2.74

入力となるpersonnel fileそのものが与えられていないので如何様にも設計出来るのだが、レコードのフォーマットだけでなく全体のフォーマットも部署毎に異なるものとする。名前をキーにすると言うのは現実にはあり得ないが、ここでは名前は一意である事とする。折角なので前節で登場した順序無しリストと二分木を使ってレコードを持っている二つの部署があるとする。これまでこの本でデータ型としては登場していないが名前は文字列で持つ事とする。文字列の比較にはstring

; division a
; (list name age address salary)
(define (make-record-a name age address salary)
  (list name age address salary))
(define (get-name-a record)
  (car record))
(define (get-salary-a record)
  (cadddr record))

(define (element-of-set-a? record set)
  (cond ((null? set) #f)
        ((string=? (get-name-a record) (get-name-a (car set))) #t)
        (else (element-of-set-a? record (cdr set)))))

(define (adjoin-set-a record set)
  (if (element-of-set-a? record set)
      set
      (cons record set)))

(define (find-record-a name set)
  (cond ((null? set) #f)
        ((string=? name (get-name-a (car set))) (car set))
        (else (find-record-a name (cdr set)))))

(define personnel-file-a
  (adjoin-set-a (make-record-a "John" 73 "New York" 10000)
                (adjoin-set-a (make-record-a "Paul" 71 "London" 11000)
                              (adjoin-set-a (make-record-a "George" 70 "Los Angels" 12000)
                                            (adjoin-set-a (make-record-a "Ringo" 73 "London" 13000) '())))))

; division b
; (list address phone salary name)
(define (make-record-b address phone salary name)
  (list address phone salary name))
(define (get-name-b record)
  (cadddr record))
(define (get-salary-b record)
  (caddr record))

(define (entry tree) (car tree))
(define (left-branch tree) (cadr tree))
(define (right-branch tree) (caddr tree))
(define (make-tree entry left right)
  (list entry left right))

(define (element-of-set-b? record set)
  (if (null? set) #f
      (let ((name1 (get-name-b record))
            (name2 (get-name-b (entry set))))
        (cond ((string=? name1 name2) #t)
              ((string<? name1 name2) (element-of-set-b? record (left-branch set)))
              ((string>? name1 name2) (element-of-set-b? record (right-branch set)))))))

(define (adjoin-set-b record set)
  (if (null? set) (make-tree record '() '())
      (let ((name1 (get-name-b record))
            (name2 (get-name-b (entry set))))
            (cond ((string=? name1 name2) set)
                  ((string<? name1 name2) (make-tree (entry set) 
                                                     (adjoin-set-b record (left-branch set))
                                                     (right-branch set)))
                  ((string>? name1 name2) (make-tree (entry set)
                                                     (left-branch set)
                                                     (adjoin-set-b record (right-branch set))))))))

(define (find-record-b name set)
  (cond ((null? set) #f)
        ((string=? name (get-name-b (entry set))) (entry set))
        ((string<? name (get-name-b (entry set))) (find-record-b name (left-branch set)))
        ((string>? name (get-name-b (entry set))) (find-record-b name (right-branch set)))))

(define personnel-file-b
  (adjoin-set-b (make-record-b "London" "123-4567" 14000 "Mick")
                (adjoin-set-b (make-record-b "Liverpool" "234-5678" 15000 "Kieth")
                              (adjoin-set-b (make-record-b "Paris" "345-6789" 16000 "Charlie")
                                            (adjoin-set-b (make-record-b "Rome" "456-7890" 17000 "Ron") '())))))

一応以下の様な簡単なテストをしておく。

(make-record-a "John" 73 "New York" 10000)
(if (string=? "John" (get-name-a (make-record-a "John" 73 "New York" 10000))) 'Pass 'Fail)
(if (= 13000 (get-salary-a (make-record-a "Ringo" 73 "London" 13000))) 'Pass 'Fail)
(define test1 (list (make-record-a "John" 73 "New York" 1000)
                    (make-record-a "Paul" 71 "London" 11000)))
(if (element-of-set-a? (make-record-a "John" 73 "New York" 10000) test1) 'Pass 'Fail)
(if (element-of-set-a? (make-record-a "George" 71 "Los Angels" 10000) test1) 'Fail 'Pass)
personnel-file-a
(find-record-a "George" personnel-file-a)
(let ((r (find-record-a "Paul" personnel-file-a)))
  (if (and r (= (get-salary-a r) 11000)) 'Pass 'Fail))

(make-record-b "London" "123-4567" 15000 "Mick")
(if (= (get-salary-b (make-record-b "London" "123-4567" 15000 "Mick")) 15000) 'Pass 'Fail)
(if (string=? (get-name-b (make-record-b "London" "123-4567" 15000 "Mick")) "Mick") 'Pass 'Fail)
(define test2 (make-tree (make-record-b "London" "123-4567" 15000 "Mick") (make-tree (make-record-b "London" "234-5678" 16000 "Kieth") '() '()) '()))
(if (element-of-set-b? (make-record-b "London" "123-4567" 15000 "Mick") test2) 'Pass 'Fail)
(if (element-of-set-b? (make-record-b "London" "234-5678" 15000 "Kieth") test2) 'Pass 'Fail)
(if (element-of-set-b? (make-record-b "London" "345-6789" 15000 "Charlie") test2) 'Fail 'Pass)
personnel-file-b
(let ((r (find-record-b "Kieth" personnel-file-b)))
  (if (and r (= (get-salary-b r) 15000)) 'Pass 'Fail))

テスト結果

{"John" 73 "New York" 10000}
Pass
Pass
Pass
Pass
{{"John" 73 "New York" 10000} {"Paul" 71 "London" 11000} {"George" 70 "Los Angels" 12000} {"Ringo" 73 "London" 13000}}
{"George" 70 "Los Angels" 12000}
Pass
{"London" "123-4567" 15000 "Mick"}
Pass
Pass
Pass
Pass
Pass
{{"Rome" "456-7890" 17000 "Ron"}
 {{"Paris" "345-6789" 16000 "Charlie"} () {{"Liverpool" "234-5678" 15000 "Kieth"} () {{"London" "123-4567" 14000 "Mick"} () ()}}}
 ()}
Pass
> 

a.
レコードを取って来る手続きfind-record-aとfind-record-bをテーブルに登録。それぞれのファイルにタグ付けをしたデータを定義。取り敢えずそのままの形のレコードを返すget-recordを定義する。

(define (install-division-a)
  (put 'get-record 'division-a find-record-a))

(define (install-division-b)
  (put 'get-record 'division-b find-record-b))

(define tagged-personnel-file-a (cons 'division-a personnel-file-a))
(define tagged-personnel-file-b (cons 'division-b personnel-file-b))
(define (get-division tagged-file)
  (car tagged-file))
(define (get-personnel-file tagged-file)
  (cdr tagged-file))

(define (get-record name tagged-file)
  ((get 'get-record (get-division tagged-file)) name (get-personnel-file tagged-file)))

動作確認

> (get-division tagged-personnel-file-a)
division-a
> (get-personnel-file tagged-personnel-file-a)
{{"John" 73 "New York" 10000} {"Paul" 71 "London" 11000} {"George" 70 "Los Angels" 12000} {"Ringo" 73 "London" 13000}}
> (get-division tagged-personnel-file-b)
division-b
> (get-personnel-file tagged-personnel-file-b)
{{"Rome" "456-7890" 17000 "Ron"}
 {{"Paris" "345-6789" 16000 "Charlie"} () {{"Liverpool" "234-5678" 15000 "Kieth"} () {{"London" "123-4567" 14000 "Mick"} () ()}}}
 ()}
> (install-division-a)
ok
> (install-division-b)
ok
> (get-record "John" tagged-personnel-file-a)
{"John" 73 "New York" 10000}
> (get-record "Ringo" tagged-personnel-file-a)
{"Ringo" 73 "London" 13000}
> (get-record "Mick" tagged-personnel-file-b)
{"London" "123-4567" 14000 "Mick"}
> (get-record "Ron" tagged-personnel-file-b)
{"Rome" "456-7890" 17000 "Ron"}
> 

必要な情報は各ファイルの種類を区別する為のタグと、各ファイル用のレコードを取り出す関数、それらを関連づける表。

b.
a.では元のレコードをそのままの形で返していたので、このままではそれぞれのget-salaryを定義してテーブルに登録する必要がある。get-recordが共通のフォーマットのレコートを返してしまえば、get-salaryは共通のひとつを定義すれば良い。

(define (install-division-a)
  (put 'get-record 'division-a find-record-a)
  (put 'get-salary 'division-a get-salary-a))

(define (install-division-b)
  (put 'get-record 'division-b find-record-b)
  (put 'get-salary 'division-b get-salary-b))

(define (get-record name tagged-file)
  (let ((record ((get 'get-record (get-division tagged-file)) name (get-personnel-file tagged-file))))
    (list name ((get 'get-salary (get-division tagged-file)) record))))

動作確認

> (install-division-a)
ok
> (install-division-b)
ok
> (get-record "Paul" tagged-personnel-file-a)
{"Paul" 11000}
> (get-record "Charlie" tagged-personnel-file-b)
{"Charlie" 16000}
> 

こうなっていれば、get-salaryは

(define (get-salary record)
  (cadr record))

でOK。動作確認

> (install-division-a)
ok
> (install-division-b)
ok
> (get-salary (get-record "George" tagged-personnel-file-a))
12000
> (get-salary (get-record "Kieth" tagged-personnel-file-b))
15000
> 

c.
表を順番に探して行くと言う事は、find-record-a、find-recodr-bが失敗してfalseが返る可能性があるので、get-recordをそれように変更する必要がある。

(define (get-record name tagged-file)
  (let ((record ((get 'get-record (get-division tagged-file)) name (get-personnel-file tagged-file))))
    (if record
        (list name ((get 'get-salary (get-division tagged-file)) record))
        #f)))

動作確認

> (get-record "Ringo" tagged-personnel-file-b)
#f
> 

次にfind-employee-recordを定義する。

(define all-tagged-personnel-files (list tagged-personnel-file-a tagged-personnel-file-b))

(define (find-employee-record name list-of-personnel-files)
  (if (null? list-of-personnel-files)
      #f
      (let ((record (get-record name (car list-of-personnel-files))))
        (if record
            record
            (find-employee-record name (cdr list-of-personnel-files))))))

動作確認

> (find-employee-record "John" all-tagged-personnel-files)
{"John" 10000}
> (find-employee-record "Ron" all-tagged-personnel-files)
{"Ron" 17000}
> 

d.
新たに会社を買収した時の既存コードへの変更は基本的には要らない。上記のall-tagged-personnel-filesがimmutableであればこれを再定義する必要があるが、mutableであれば新しい会社用のtagged-personnel-fileを定義して追加するのみ。

加えて、新しい会社用のget-recordとget-salaryを表に登録するinstall手続きを定義して実行すれば良い。

Message passing

ここまでは共通の表を使って操作(operation)と型(type)から呼び出すべき手続きを決めていたが、この作業をデータそのものにも行わせる事が出来る(intelligent data object)。

apply-genericが呼び出すべき手続きを探すのではなく、その作業をデータであるargに行わせる。make-from-real-imagから返って来るのは実は手続き(実際にはx、yを取り込んだクロージャ)。なのでmake-from-real-imagで作ったオブジェクトを手続きとして呼び出す事が出来る。ここに引数として渡す操作がオブジェクトに渡すメッセージで、オブジェクトがそのメッセージに対応する手続き(メソッド)を呼び出す。

Exercise 2.75

殆どそのまま応用して定義出来る。

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

動作確認。

> (define x (make-from-mag-ang 10 (/ pi 6)))
> (x 'real-part)
8.660254037844387
> (x 'imag-part)
4.999999999999999
> (x 'magnitude)
10
> (x 'angle)
0.5235987755982988
> 
Exercise 2.76

generic operations with explicit dispatch
明示的に書かれる総称関数。引数の型を検出して、その型に応じて実際の関数を呼び出す。
型が増えた時:型による条件分岐を増やし、型固有の関数を書く。
手続きが増えた時:新規の総称関数を定義し、その手続きをサポートする型に対する条件分岐を書く。必要な型に対して新規の型固有関数を書く。

data-directed style
型固有の関数を各データから統一的な手段(表から取り出す)で取得出来るので、総称関数内の条件分岐は無くなる。
型が増えた時:総称関数は変わらない。型固有の関数を必要な操作の分だけ作成し、表に登録する。
手続きが増えた時:その手続きをサポートする型に固有の関数を作成し、表に登録する。

message-passing-style
各データがディスパッチをするので、既存のコードに変更は生じない。
型が増えた時:増えた型用のディスパッチャ、型固有関数群を作成する。
手続きが増えた時:そのその手続きをサポートする型に固有の関数を作成し、それぞれのディスパッチャに登録する。

data-directed styleとmessage-passing-styleはどちらにしても、型が増えた場合には新規の型用のコードだけが必要であり、既存のコードには変更の必要は生じない。また手続きが増えた場合にはその手続きをサポートする型にはそれぞれメソッドを定義する必要がある店に於いても本質的には変わらない。
但し、data-directed styleとmessage-passing-styleをもう少しだけ広く比較した時には、data-directed styleはグローバルな表が必要な点に於いてmessage-psssing-styleよりも不利と言えそう。