SICP §2.4(抽象データの多重表現 その2[タグつきデータ])
前エントリでは、複素数を直交座標形式と極座標形式の2種の表現で表してみた。が、この2種は一つのシステム内で混在させることはできない。なぜなら、ある複素数オブジェクトを渡されたとしてもそれがどちらの概念で生成された複素数オブジェクトなのか判断できないからだ。(だって実態はどちらの表現でも cons で連結された要素が2つの単純なリストだからね。)
統合の為の前準備
複数の表現を混在させるには「タグつきデータ」という概念を使う。要するに、「このオブジェクトは○○の形式のオブジェクトですよ」と教えてやる情報を、オブジェクト自体に持たせてやるってこと。具体的にどうするかというと、
こんな感じ。
直交座標形式の複素数:('rectangular 2 3)
極座標形式の複素数 :('polar 3 (* 1/4 pi))
要するに判別できるシンボルをオブジェクトの先頭に負荷してやるだけ。
ま、それによって前エントリで定義した構成子、選択子は変更が求められ、更にどちらの表現で生成された複素数なのか調べる述語も必要になってくる。
ではそれがどんな風に定義されるかを。。
;============================== ;構成子(データにタグをつける) ;============================== (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))) ;============================== ;述語 ;============================== ;直交座標形式の複素数データか? (define (rectangular? z) (eq? (type-tag z) 'rectangular)) ;極座標形式の複素数データか? (define (polar? z) (eq? (type-tag z) 'polar))
これで2つの表現での複素数オブジェクトを一つのシステム内に共存させるための準備ができたので、前エントリの各形式での手続きを修正してみる。といっても、直交座標形式と極座標形式で、それぞれ行いたい処理の手続き名称がまったく同じなので、このままだと後評価勝ち(あらかじめ読まれていた同名の手続きが上書きされてしまう)になってしまう。
なので、手続き名を区別するために、それぞれ rectangular、polar という添字をつけよう。
直交座標形式
;============================== ;構成子 ;============================== ;実部と虚部から複素数を生成 (define (make-from-real-imag-rectangular x y) (attach-tag 'rectangular (cons x y))) ;半径と角度から複素数を生成 (define (make-from-mag-ang-rectangular r a) (attach-tag 'rectangular (cons (* r (cos a)) (* r (sin a))))) ;============================== ;選択子 ;============================== ;実部 (define (real-part-rectangular z) (car z)) ;虚部 (define (imag-part-rectangular z) (cdr z)) ;半径 (define (magnitude-rectangular z) (sqrt (square (real-part-rectangular z)) (square (imag-part-rectangular z)))) ;角度 (define (angle-rectangular z) (atan (imag-part-rectangular z) (real-part-rectangular z)))
極座標形式
;============================== ;構成子 ;============================== ;実部と虚部から複素数を生成 (define (make-from-real-imag-polar x y) (attach-tag 'polar (cons (sqrt (square x) (square y)) (atan y x)))) ;半径と角度から複素数を生成 (define (make-from-mag-ang-polar r a) (attach-tag 'polar (cons r a))) ;============================== ;選択子 ;============================== ;実部 (define (real-part-polar z) (* (magnitude-polar z) (cos (angle-polar z)))) ;虚部 (define (imag-part-polar z) (* (magnitude-polar z) (sin (angle-polar z)))) ;半径 (define (magnitude-polar z) (car z)) ;角度 (define (angle-polar z) (cdr z))
汎用構成子・選択子
データに適した手続きを呼び出すことでデータの実装を意識せずに使用できる手続きを汎用手続きというらしいが、既述の rectanglar、polar という添字がついた手続き等を使って、汎用構成子、汎用選択子を実装してみよう。
;============================== ;汎用構成子 ;============================== ;生成する際はそもそも好きな表現を選択できる。 ;なので、渡される引数に合致した表現を採用すべき。 ;直交座標形式で生成 (define (make-from-real-imag x y) (make-from-real-imag-rectanglar x y)) ;極座標形式で生成 (define (make-from-mag-ang r a) (make-from-mag-ang-polar r a)) ;============================== ;汎用選択子 ;============================== ;実部 (define (real-part z) (cond ((rectangular? z) (real-part-rectangular (contents z))) ((polar? z) (real-part-polar (contents z))) (else (error "Unknown type -- REAL-PART" z)))) ;虚部 (define (imag-part z) (cond ((rectangular? z) (imag-part-rectangular (contents z))) ((polar? z) (imag-part-polar (contents z))) (else (error "Unknown type -- IMAG-PART" z)))) ;半径 (define (magnitude z) (cond ((rectangular? z) (magnitude-rectangular (contents z))) ((polar? z) (magnitude-polar (contents z))) (else (error "Unknown type -- MAGNITUDE" z)))) ;角度 (define (angle z) (cond ((rectangular? z) (angle-rectangular (contents z))) ((polar? z) (angle-polar (contents z))) (else (error "Unknown type -- ANGLE" z))))
以上で、前エントリで定義しておいた複素数の四則演算システムをそのまま使用可能な状態になる。
しかし・・・じゃあ複素数の表現が3つ、4つと増えていったらその都度汎用手続きを変更しなきゃならんのか?それはあまりに面倒だしバグの入り込む温床になるまいか?
という問題を解消する手法を紹介するのが次のサブセクション「データ主導プログラミングと加法性」のお話になる。