よろずや

CLOS - Lisp でオブジェクト指向

今更ながら CLOS をかじってみる.とりあえず,defclass, defgeneric, defmethod がわかればいーんだろうか? Meta Object Protocl とか云うのはなんだろうか?うー む.

かじった事メモ

Object 指向の条件 Encapsulation, Integrity, Operationality, Inheritance (日 本語で言うと「データの操作と定義に対して抽象データ型と uniform external interface が提供され,それを用いて,ソフトウェア開発者はモジュラーにプログラ ムを作成できる」「データおよびそれらに対する操作の組を再定義する手間を省き, それらを部分共有しながら新しいオブジェクトを定義する」)らしい.しかし CLOS では Encapsulation は強調されない.Meta Object Protocl を使って CLOS 上にそー ゆうシステムを作る事はできるかもしれないが,標準ではそーではない.

クラスの実現形をインスタンスという.このへんはもはや常識の域かもしれない.メ タクラスのインスタンスはクラスだったりする.このへんはあまり聞かないような気 がする.よーするに他の言語では準備しないよーな機能がやっぱりあるという事らし い.defcalss

メタクラス云々はとりあえずほっといて,クラス定義には

といった要素がある.いきありあれだが, CLOS ではメソッドはクラス内に閉じ込め ていない.Encapsulation よりも総称関数というほうに走ったらしい.

CLOS ではクラスの定義に defcalass を使う.

(defclass クラス名 (スーパークラス1 スーパークラス2 ...)
  ((スロット定義1) (スロット定義2) ...)
  クラスオプション)

スーパークラスのリストは多重継承を許すためリストなんである.当然.多重継承時 の優先順位は left-to-right-and-up-to-joins といわれる方式で,まーとりあえず は左側のが優先だと思っておく.スロット定義

スロットというのはクラス固有の局所データである.もちろん,クラスのインスタン ス毎に独立なものと,あるクラスに共通したデータがある.スロット定義には

といろいろ指定できるよーだが順番に見ていく.まず簡単なクラスの作成から.

:initform

(defclass point2d ()
  ((x :initform 0)
   (y :initform 1)))

クラスのインスタンスを生成するには make-instance 関数を使用する.

* (make-instance 'point2d)
#<POINT2D {49B2F3A5}>
* 

また,:reader :writer :accessor 等を用いずに slot-value という関数でスロット にアクセスできる.

* (setf obj (make-instance 'point2d))
#<POINT2D {49B326FD}>
* (slot-value obj 'x)
0
* (slot-value obj 'y)
1

:initarg

スロットには初期値が設定されているが,通常はインスタンス生成時 (make-instance 呼んだ時)にスロットの値を設定したい場合が多いだろう.そこで 使われるのが :initarg オプション.

(defclass point2d ()
  ((x :initform 0 :initarg x)
   (y :initform 1 :initarg y)))

こーすると make-instance 時に初期値を設定できる.

* (setf obj (make-instance 'point2d 'x 10 'y 20))
#<POINT2D {49B4D21D}>
* (slot-value obj 'x)
10
* (slot-value obj 'y)
20
* 

しかし,キーワード引数を使ったほうがそれっぽい.(つーか普通こっち?)

(defclass point2d ()
  ((x :initform 0 :initarg :x)
   (y :initform 1 :initarg :y)))

* (setf obj (make-instance 'point2d :x 10 :y 20))
#%lt;POINT2D {49C1BAED}>
* (slot-value obj 'x)
10

:allocation

クラス定義と実行例を示す.

(defclass point2d ()
  ((x :initform 0 :initarg x :allocation :class)
   (y :initform 0 :initarg y :allocation :instance)))

:allocation を省略すると :allocation :instance が仮定される.

* (setf obj1 (make-instance 'point2d 'x 1 'y 1)
        obj2 (make-instance 'point2d 'x 2 'y 2))
#<POINT2D {49BFFB5D}>
* (slot-value obj1 'x)
2                      ; x はクラス共通なので obj2 生成時に 2 に変更される.
* (slot-value obj2 'x)
2
* (slot-value obj1 'y)
1
* (slot-value obj2 'y)
2
* (eq (slot-value obj1 'x) (slot-value obj2 'x)) ;; 異なるインスタンスでも x は同じ
T
* (eq (slot-value obj1 'y) (slot-value obj2 'y)) ;; もちろん y はインスタンス毎に別
NIL

:reader, :writer, :accessor

:reader, :writer, :accessor キーワードに続けてメソッド名(まだ出てきててない が,defmethod で定義するやつ.総称関数 defgeneric と表裏一体)を書く.: reader は読出し時に,:writer は書込み時に,:accessor は読出し/書込み時にそれ ぞれ指定したメソッドを呼ぶ.:reader, :accessor はメソッドの実体が定義されな い場合はスロットの値をアクセスするためのメソッド自動的に準備される.

ただし :writer は (defmethod y ((p point3d) ..) ...) といったメソッド定義を 要求するだけで自分で定義しないと使えない.(まだ defmethod でてきてないが…). と思ったが CLHS にはそーゆう記述は無いなぁ.うーん,よくわからん.

:accessor は setf でスロットに値を設定できるよーにしてくれる.

(defclass point3d ()
  ((x :initform 0 :initarg :x :reader x)
   (y :initform 0 :initarg :y :writer y)
   (z :initform 0 :initarg :z :accessor z)))

(defmethod y ((p point3d) n)
  ;; ↓ (setf (slot-value p 'y) n) と等価
  (with-slots (y) p
    (setf y n)))

* (setf obj (make-instance 'point3d :x 10 :y 20 :z 5))
#
* (x obj)
10
* (y obj)


Error in function PCL::CACHE-MISS-VALUES:
   The function # requires at least 2 arguments

Restarts:
  0: [ABORT] Return to Top-Level.

Debug  (type H for help)

(PCL::CACHE-MISS-VALUES #
                        (#)
                        PCL::ACCESSOR)
Source: Error finding source: 
Error in function DEBUG::GET-FILE-TOP-LEVEL-FORM:  Source file no longer exists:
  target:pcl/dfun.lisp.
0] q ;; エラー.:writer のメソッドでは読み出しはできない!!
* (y obj 99) ;; 設定に使う
99
* (slot-value obj 'y)
99
* (z obj)
5
* 

:reader, :accessor のメソッドも定義してみる.

(defclass point3d ()
  ((x :initform 0 :initarg :x :reader x)
   (y :initform 0 :initarg :y :writer set-y)
   (z :initform 0 :initarg :z :accessor z)))

(defmethod x ((p point3d))
    (format t "~%[method x]")
    (with-slots (x) p
      x))

(defmethod set-y ((p point3d) n)
    (format t "~%[method set-y]")
    (with-slots (y) p
      (setf y n)))

(defmethod z ((p point3d))
    (format t "~%[method z]")
    (with-slots (z) p
      z))

ただし,accessor の z は読み出しに使われるだけ.setf 時に呼ばれるのは別である.

* (setf obj (make-instance 'point3d :x 0 :y 1 :z 2))
#
* (x obj)
[method x]
0
* (set-y obj 10)
[method set-y]
10
* (z obj)
[method z]
2
* (setf (z obj) 10)
10         ;; この時は (defmethod z ((p point3d)) ...) は呼ばれない.

defgeneric

defmethod