@uents blog

Code wins arguments.

SICP 読書ノート#22 - 2.4.3 データ主導プログラミングと加法性(3) (pp.109-110)

「§2.4.3 データ主導プログラミングと加法性」の続き。

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

メッセージパッシング

§2.1.3 データとは何か で見た、 クロージャの特性を利用したアクセサを持つオブジェクトによるconsの実装に似ている。 JavaScripterの自分にとってはこっちの方が馴染みがある。

まずは写経。

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

;;; generic accessors
(define (apply-generic op arg) (arg op))

(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))

テスト。

racket@> (magnitude-part (make-from-real-imag 4 3))
5

問題 2.75

make-from-mag-ang をメッセージパッシングの手法で実装せよ。

(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-part) r)
          ((eq? op 'angle-part) a)
          (else
           (error "Unknown op -- MAKE-FROM-MAG-ANG" op))))
  dispatch)

テスト。

racket@> (real-part (make-from-mag-ang 2 (/ pi 3)))
1.0000000000000002

racket@> (imag-part (make-from-mag-ang 2 (/ pi 3)))
1.7320508075688772

まとめ

これまでの3つの戦略を比較します。

問題 2.76

汎用演算を使った巨大システムが発展すると、新しいオブジェクトの型や、新しい演算が必要になる。

3つの戦略、

  1. 明白な振り分けを持つ汎用演算 (strategies—generic operations with explicit dispatch)
  2. データ主導流 (data-directed style)
  3. メッセージパッシング流 (message-passing-style)

のそれぞれで、新しい型や新しい演算を追加する時、システムに施すべき変更について述べよ。

新しい型が絶えず追加されるシステムには、どの方法が最も適切か。 新しい演算が絶えず追加されるシステムはどうか。

戦略の比較

新しい型、演算の追加に対する修正内容の比較。

戦略 新しい型 新しい演算
1. 明白な振り分け 新しい型に対するアクセサを定義
全てのディスパッチャにそのアクセサを追加
それぞれの型に対する新しい演算手続きおよびディスパッチャを新規に追加
2. データ主導流 新しい型に対するパッケージの定義のみ 全てのパッケージに演算手続きを追加
新しい演算に対するインターフェースを新規に定義
3. メッセージパッシング流 新しい型に対するコンストラクタの定義のみ 全てのコンストラクタに演算手続きを追加
新しい演算に対するインターフェースを新規に定義

適切な戦略は?

○△×の3段階で評価してみた。

新しい型が絶えず追加される
  • 明白な振り分け:×
    • 全てのディスパッチャを変更する必要があるため、修正による既存システムへの影響が大きい
  • データ主導流:○
    • 新規のパッケージの追加のみのため、既存システムへの影響が小さい
  • メッセージパッシング流:○
新しい演算が絶えず追加される
  • 明白な振り分け:△
    • 既存の型への演算手続きの追加および新規ディスパッチャの追加のみのため、既存システムへの影響は局所的
  • データ主導流:×
    • 依存する全ての型(のパッケージ)に演算手続きを追加していく必要あるため、既存システムへの影響が大きい
  • メッセージパッシング:×
    • 依存する全ての型(のコンストラクタ)に演算手続きを追加していく必要あるため、既存システムへの影響が大きい

現時点で学んだ内容だけでは、決定的にどれが有利ということは言えない気がするし、 実際には銀の弾丸はないのかもしれない。

次回は「§2.5 汎用演算のシステム」から。


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