プログラミング再入門

プログラミングをもう一度ちゃんと勉強する読書ノート

SICP 2.1.2 Abstraction Barriers

インターフェースを介して実装の詳細を隠すカプセル化に通じるお話。

ノート

barrierは囲い、防護壁。抽象化の境界線、と言った感じか。

the underlying idea of data abstraction is to identify for each type of data object a basic set of operations in terms of which all manipulations of data objects of that type will be expressed, and then to use only those operations in manipulating the data.

意外とややこしい文章。データ抽象化の根底にある概念は、あるデータに対する全ての操作を表現出来る操作の基本的なセットを挙げて、その操作を使ってデータを取り扱う事。

インターフェースは境界線になっていて、それを使うプログラムからそれよりも下の部分を隠す。カプセル化の概念。でも随分階層化している。

有理数をdotted pairで表現している事、あるいはdotted pairがマシン上でどの様に実現されているかはそれよりも上位のプログラムには関係無い。

演算の関数(add-rat, sub-rat etc.)と、コンストラクタとアクセッサの階層を分けている。オブジェクト指向に慣れているとデータの操作はそのクラスに入れてしまいそうだが、確かにアクセッサを使って実現出来る関数はクラスの外にあっても良いし、実装に依存したコードを最少にすると言う観点では操作はクラス外部に実装する方が良い事もある。

gcdを計算するタイミングが異なる実装が例として挙げられているが、ポイントはこのレベルの実装を変更しても、演算のレベル(add-rat, sub-rat, etc.)の実装には影響しない所。

Constraining the dependence on the representation to a few interface procedures helps us design programs as well as modify them, because it allows us to maintain the flexibility to consider alternate implementations.

実装方法への依存を最少にする事で設計やその変更の役に立つ。それによって実装方法を変更する自由が保たれるから。

Exercise 2.2

線分の中間点を求める関数を各。
まず点を表すデータ構造を定義。constructorとselectorのみ。

(define (make-point x y) (cons x y))
(define (x-point p) (car p))
(define (y-point p) (cdr p))

print-pointは例示されている通り。点を作ってみる。

> (make-point 1 2)
(1 . 2)
> (print-point (make-point 1 2))
(1, 2)
> 

次に線分を表すデータ構造。これもconstructorとselectorのみ。

(define (make-segment a b) (cons a b))
(define (start-segment s) (car s))
(define (end-segment s) (cdr s))

簡単に動作を確認。

> (print-point (start-segment (make-segment (make-point 1 2) (make-point 3 4))))
(1, 2)
> (print-point (end-segment (make-segment (make-point 1 2) (make-point 3 4))))
(3, 4)
> 

本題の線分の中間点を返す関数。

(define (mid-point s)
  (make-point
   (/ (+ (x-point (start-segment s)) (x-point (end-segment s))) 2)
   (/ (+ (y-point (start-segment s)) (y-point (end-segment s))) 2)))

動作確認。

> (print-point (mid-point (make-segment (make-point 1 2) (make-point 3 4))))
(2, 3)
> (print-point (mid-point (make-segment (make-point 1 0) (make-point 0 1))))
(1/2, 1/2)
> (print-point (mid-point (make-segment (make-point 0 0) (make-point 1.0 1.0))))
(0.5, 0.5)
> 
Exercise 2.3

長方形を表現するクラスを2種類定義し、それぞれの表現形式で周囲の長さと面積を計算する関数が共通で使える事を示す。ここの問題では周囲と面積の計算だけなのでselectorとしては幅と高さが得られれば良いので長方形そのものも幅と高さで表現するのが最も簡単。でもちょっと芸が無いので

  1. 各辺が座標軸に対して平行か垂直である長方形に限定して対角の2点で表現
  2. 底辺の線分とその直線上に無いもうひとつ点(あるいは3点)で表現

を考えてみる。

まず簡単な実装、その1。

(define (make-rectangle point1 point2) (cons point1 point2))
(define (width r) (abs (- (x-point (car r)) (x-point (cdr r)))))
(define (height r) (abs (- (y-point (car r)) (y-point (cdr r)))))

(define (perimiter r)
  (+ (* 2 (width r)) (* 2 (height r))))
(define (area r)
  (* (width r) (height r)))

実行結果。

> (define r (make-rectangle (make-point 5.0 5.0) (make-point 15.0 10.0)))
> (perimiter r)
30.0
> (area r)
50.0
> 

違う表現方法でもwidthとheightのAPIを提供出来ればそこにbarrierを築ける。
実装方法その2。2点間の距離の公式からwidth、2点を通る直線の方程式および直線と点の距離の方程式からheightを定義。

(define (make-rectangle segment point) (cons segment point))
(define (base-segment r) (car r))
(define (point r) (cdr r))
(define (width r)
  (let ((x1 (x-point (start-segment (base-segment r))))
        (y1 (y-point (start-segment (base-segment r))))
        (x2 (x-point (end-segment (base-segment r))))
        (y2 (y-point (end-segment (base-segment r)))))
  (sqrt (+ (square (- x2 x1)) (square (- y2 y1))))))
(define (height r)
  (let ((x1 (x-point (start-segment (base-segment r))))
        (y1 (y-point (start-segment (base-segment r))))
        (x2 (x-point (end-segment (base-segment r))))
        (y2 (y-point (end-segment (base-segment r))))
        (x3 (x-point (point r)))
        (y3 (y-point (point r))))
    (/ (abs (+ (* (- y2 y1) x3) (* (- x1 x2) y3) (* (- x2 x1) y1) (* (- y1 y2) x1)))
       (sqrt (+ (square (- y2 y1)) (square (- x1 x2)))))))

perimiterとareaの定義は同じにしたまま、実行してみる。

> (define r (make-rectangle (make-segment (make-point 5.0 5.0) (make-point 15.0 5.0)) (make-point 15.0 10.0)))
> (perimiter r)
30.0
> (area r)
50.0
> (define r (make-rectangle (make-segment (make-point 0.0 0.0) (make-point 4.0 3.0)) (make-point 0.0 (/ 25.0 3))))
> (perimiter r)
23.333333333333336
> (area r)
33.333333333333336
> 

width、heightと言うインターフェースでバリアが出来ている事が示せた。