SICP §2.5(汎用演算のシステム その2[異なる型のデータの結合])

さぁ、やっと「異なるデータ型」の取扱いにきたぞと。このセクションでは、このエントリで「わからん」と言っていたことと、見習いschemerさんに教えていただいたことの核心を説明している。
要するに、「異なる型データが混在した場合どのように扱うべきか」、あるいは「異なるデータを処理する手続きはどこに記述すべきか」について。このテーマは20年前も今も、最適な方法が存在していない難問らしい。
ひょっとしたらピント外れかもしれんが、要は現代の言語が「継承(多重継承、単一継承)」「mixin」「compositeパターン」とかでやりくりしてることなんじゃないかと思った。
で、もう一つ「強制型変換」て話があった。これはできる。今までのエントリでやってきた、汎用演算システムで考えると、こういう継承図ができる。


整数 → 有理数 → 実数 → 複素数
これはつまり、
  • 「整数」は分母が1の「有理数」として表現でき、
  • 有理数」は少数表現で「実数」として表現でき、
  • 「実数」は虚部が0の「複素数」として表現できる。
ということだ。つまりある型は、矢印が向いている方向における、一つ上の型へ強制的に表現を変更することができるわけだ。これをapply-genericで実装するとこんな感じにできる。
ちなみに下記コード中で出現するput-coercionとget-coercion手続きは、既に実装されているモンとしてちょうだい。

;scheme-number型データを、complex型に変換する手続き
(define (scheme-number->complex n)
  (make-complex-from-real-imag (contents n) 0))

;型変換登録手続きで登録しちゃう。
(put-coercion 'scheme-number 'complex scheme-number->complex)

;引数2つに限定すると仮定すると、実際に呼び出される場合apply-genericはこんな感じになる。
(define (apply-generic op . args)
  (let* ((type-tags (map type-tag args))
	 (proc (get op type-tags)))
    (if proc
	(apply proc (map contents args))
	;引数は2つに限定。
	(if (= (length args) 2)
	    ;2つの引数の型タグ、実際のデータ、変換手続きを取得しておいて、
	    (let* ((type1 (car type-tags))
		   (type2 (cadr type-tags))
		   (a1 (car args))
		   (a2 (cadr args))
		   (t1->t2 (get-coercion type1 type2))
		   (t2->t1 (get-coercion type2 type1)))
	      ;状況に合わせて型変換手続きを適用して次のステージの手続きを適用させる。
	      (cond (t1->t2
		     (apply-generic op (t1->t2 a1) a2))
		    (t2->t1
		     (apply-generic op a1 (t2->t1 a2)))
		    (else
		     (error "No method for these types"
			    (list op type-tags)))))
	    (error "No method for these types"
		   (list op type-tags))))))

引数が複数存在する場合の手続きはこの幾つかあとの問題で出てくるらしいのでお楽しみに♪