SICP 読書ノート#14 - 2.2.4 図形言語(4) (pp.73-83)
引き続き「§2.2.4 図形言語」。いよいよ終盤。
ペインタの変換と組み合わせ
transform-painterというペインタのフレーム変換を行う手続きを定義する。
(define (transform-painter painter origin corner1 corner2) (lambda (frame) (let ((m (frame-coord-map frame))) (let ((new-origin (m origin))) (painter (make-frame new-origin (sub-vect (m corner1) new-origin) (sub-vect (m corner2) new-origin)))))))
これを使えば、上下を反転させたペインタを返す flip-vertという手続きは、フレームの頂点の入れ替えだけで定義できる。
(define (flip-vert painter) (transform-painter painter (make-vect 0.0 1.0) ; new origin (make-vect 1.0 1.0) ; new end of edge1 (make-vect 0.0 0.0))) ; new end of edge2
実行結果。
racket@> (draw (flip-vert wave))
素晴らしい。
左右に分割して並べたペインタを返す、beside手続き。
(define (beside painter1 painter2) (let ((split-point (make-vect 0.5 0.0))) (let ((paint-left (transform-painter painter1 (make-vect 0.0 0.0) split-point (make-vect 0.0 1.0))) (paint-right (transform-painter painter2 split-point (make-vect 1.0 0.0) (make-vect 0.5 1.0)))) (lambda (frame) (paint-left frame) (paint-right frame)))))
実行結果。
racket@> (draw (beside wave wave))
問題 2.50
flip-horiz、rotate180、rotate270を実装する。フレームの頂点を入れ替えるだけ。
(define (flip-horiz painter) (transform-painter painter (make-vect 1.0 0.0) (make-vect 0.0 0.0) (make-vect 1.0 1.0))) (define (rotate180 painter) (flip-vert (flip-horiz painter))) (define (rotate270 painter) (flip-vert (flip-horiz (rotate90 painter))))
問題 2.51
2つのペインタをフレームの上下に配置するbelowを実装する。
(define (below painter1 painter2) (let ((split-point (make-vect 0.0 0.5))) (let ((paint-bottom (transform-painter painter1 (make-vect 0.0 0.0) (make-vect 1.0 0.0) split-point)) (paint-up (transform-painter painter2 split-point (make-vect 1.0 0.5) (make-vect 0.0 1.0)))) (lambda (frame) (paint-bottom frame) (paint-up frame)))))
ここまで来ると、テキストのflipped-pairsが動かせる。
racket@> (draw (flipped-pairs wave))
問題 2.44-45
(define right-split (split beside below)) (define up-split (split below beside))
が与えられているものとしてsplitを実装し、right-splitおよびup-splitを完成させる。
(define (split op1 op2) (define (split-proc painter n) (if (= n 0) painter (let ((smaller (split-proc painter (- n 1)))) (op1 painter (op2 smaller smaller))))) (lambda (painter n) (split-proc painter n)))
これでテキストの全ての例が動かせるようになった!
racket@> (draw (corner-split wave 4))
racket@> (draw (square-limit wave 4))
頑健な設計のための言語レベル
「図形言語」で言いたいことがおおよそここでまとめられている。
僕なりのまとめとして、
レイヤー構造
- アプリケーション (ペインタの変換/組み合わせ)
- ペインタ
- フレーム
- セグメント
- ベクトル
- プリミティブ (キャンバスなど描画回り)
でレイヤー化されており、それぞれの構造が独立性を保ったまま組み合わされている。
ロバスト性
加えてそれぞれのレイヤーのロバスト性も担保されており、 他のレイヤーに影響を与えることなく、レイヤーの内部構造を変更することができる。
(セグメントやベクトルのコンストラクタ・アクセサの作り変え等がその例)
手続きオブジェクト
ペインタは、リスト構造ではなく手続きによるオブジェクトとしたため、 ペインタの変換/組み合わせは、ペインタのデータ構造や描画方法の詳細を知らずとも、 それらが実装できるようになったことは、この章で最も学ぶべきことだろう。
その他
その他、フレーム変換や異なる座標系のマッピングなどを CG的な要素も理解できればよいのではないかと思う。
とにかくやたら時間がかかったけど、頑張ったかいはあったと思います。
ソースコードはGitHubにアップしているので、良かったら参考にしてみてください。
次は「§2.3 記号データ」から。