ITコンサルの日常

ITコンサル会社に勤務する普通のITエンジニアの日常です。

「Ansi Common Lisp」11章練習問題

1. 図11.2に定義されたクラスに対して、アクセサ、スロットの初期値、スロット初期化引数を定義せよ(:accessor, :initform, :initargを指定する)。また、slot-valueを呼ばずにすむように関連コードを書き換えよ。

図11.2

(defclass rectangle ()
  (height width))

(defclass circle ()
  (radius))

(defmethod area ((x rectangle))
  (* (slot-value x 'height) (slot-value x 'width)))

(defmethod area ((x circle))
  (* pi (expt (slot-value x 'radius) 2)))

修正後

(defclass rectangle ()
  ((height :accessor rectangle-height
           :initform 0
	   :initarg  :height)
   (width  :accessor rectangle-width
           :initform 0
	   :initarg  :width)))

(defclass circle ()
  ((radius :accessor circle-radius
           :initform 0
		   :initarg  :radius)))

(defmethod area ((x rectangle))
  (* (rectangle-height x) (rectangle-width x)))

(defmethod area ((x circle))
  (* pi (expt (circle-radius x) 2)))

(let ((r (make-instance 'rectangle :height 2 :width 3)))
  (area r))

(let ((c (make-instance 'circle :radius 4)))
  (area c))

結果はこう。

* 
#<STANDARD-CLASS RECTANGLE {5802AABD}>
* 
#<STANDARD-CLASS CIRCLE {5804CA7D}>
* 
#<STANDARD-METHOD AREA (RECTANGLE) {580F4205}>
* 
#<STANDARD-METHOD AREA (CIRCLE) {5817F805}>
* 
6
* 
50.26548245743669d0
* 

2. 図9.5のコードを書き換えて、球と点がクラスで、intersectとnormalが総称関数となるようにせよ。

図9.5は読み飛ばしてるので、スルー。

3. 多くのクラスが以下のように定義されているとする。

(defclass a (c d) ...) (defclass e () ...)
(defclass b (d c) ...) (defclass f (h) ...)
(defclass c () ...) (defclass g (h) ...)
(defclass d (e f g) ...) (defclass h () ...)

(a) aをボトムとする階層構造を表現するネットワークを描け。また、aのインスタンスが所属するクラスを特定性が最大から最小の順番になるようにリストアップせよ。
(b) 同様のことをbについて行え。

まずは階層構造。

aとbの違いは、cとdの優先度の違いですね。


次に特定性が最大から最小の順番ですが、
a: c, d, e, f, g, h, standard-object, t
b: d, e, f, g, h, c, standard-object, t
ですね。

4. 以下の関数が既にあるものとする。これらの関数を使って(compute-applicable-methodsやfind-methodは使わずに)、most-spec-app-methという関数を定義せよ。この関数は、総称関数とその引数のリストを引数として、最も特定的な適用可能メソッドを(それがあれば)返す。

  • precedence:1つのオブジェクトを引数として、特定性が最大のものから順にクラスを並べた優先度リストを返す。
  • method:総称関数を引数として、そのメソッド全てのリストを返す。
  • specializations:メソッドを引数としてパラメータを特定化したリストを返す。返り値のリストの要素はクラス、(eql x)という形のリスト、t(パラメータは特定化されていないことを示す)のいずれかである。

うーん、パス。

5. 総称関数のarea(図11.2)を、関数が呼び出されるたびに大域カウンタが増加するように(しかもそれ以外では動作が変化しないように)書き換えよ。

(defclass rectangle ()
  ((height :accessor rectangle-height
           :initform 0
		   :initarg  :height)
   (width  :accessor rectangle-width
           :initform 0
		   :initarg  :width)))

(defclass circle ()
  ((radius :accessor circle-radius
           :initform 0
		   :initarg  :radius)))

(defparameter *global-counter* 0)

(defmethod area ((x rectangle))
  (incf *global-counter*)
  (* (rectangle-height x) (rectangle-width x)))

(defmethod area ((x circle))
  (* pi (expt (circle-radius x) 2)))

*global-counter*

(let ((r (make-instance 'rectangle :height 2 :width 3)))
  (area r))

*global-counter*

(let ((r (make-instance 'rectangle :height 2 :width 3)))
  (area r))

*global-counter*

(let ((c (make-instance 'circle :radius 4)))
  (area c))

(incf *global-counter*)を入れただけ。
結果はこう。

* 
0
* 
6
* 
1
* 
6
* 
2
* 
50.26548245743669d0
* 

6. 総称関数において特定化できるのが第1引数のみであった場合、どのような解決困難な問題があるか。その例を挙げよ。

うーん、分からないだらけだなあ。
答えをみる。
http://www.shido.info/lisp/pacl2.html#clos

複数のインスタンス間の動作を定義するとき困難になる。 例えば本文 p.163 の combine はメッセージ伝達モデルでは定義できない。

そうなのかあ。typep使えばいいじゃんってのは、困難なうちに入るのだろうか。