@uents blog

Code wins arguments.

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

image

素晴らしい。

左右に分割して並べたペインタを返す、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))

image

問題 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))

image

問題 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))

image

racket@> (draw (square-limit wave 4))

image

頑健な設計のための言語レベル

「図形言語」で言いたいことがおおよそここでまとめられている。

僕なりのまとめとして、

レイヤー構造

  • アプリケーション (ペインタの変換/組み合わせ)
  • ペインタ
  • フレーム
  • セグメント
  • ベクトル
  • プリミティブ (キャンバスなど描画回り)

でレイヤー化されており、それぞれの構造が独立性を保ったまま組み合わされている。

ロバスト

加えてそれぞれのレイヤーのロバスト性も担保されており、 他のレイヤーに影響を与えることなく、レイヤーの内部構造を変更することができる。

(セグメントやベクトルのコンストラクタ・アクセサの作り変え等がその例)

手続きオブジェクト

ペインタは、リスト構造ではなく手続きによるオブジェクトとしたため、 ペインタの変換/組み合わせは、ペインタのデータ構造や描画方法の詳細を知らずとも、 それらが実装できるようになったことは、この章で最も学ぶべきことだろう。

その他

その他、フレーム変換や異なる座標系のマッピングなどを CG的な要素も理解できればよいのではないかと思う。

とにかくやたら時間がかかったけど、頑張ったかいはあったと思います。

ソースコードGitHubにアップしているので、良かったら参考にしてみてください。

次は「§2.3 記号データ」から。


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