Common Lisp Object System
概要
Common Lisp Object System(コモン リスプ オブジェクトシステム、略称 CLOS)は、ANSI Common Lisp (CL) の一部をなすオブジェクト指向プログラミング機能であり、他の類似の言語(EuLisp や Emacs Lisp)にも導入されている[1]。当初アドオンとして提案され、ANSIの標準に組み込まれた。CLOS は動的オブジェクトシステムであり、C++やJavaのような静的なオブジェクト指向言語とは大きく異なる。初期のLISPオブジェクトシステム(MIT Flavors や Common LOOPS)に影響されているが、より汎用的である。この汎用性を持たせることが難しい点である。
LISPにオブジェクト指向を導入することは簡単である。2ページ程度のコードがあれば実現できる(Graham, 1994)。オブジェクト指向LISPを柔軟で拡張性に富んだものにするのはもっと難しい。CLOS は完全なオブジェクトシステムであり、オブジェクト指向風に実装されている。CLOS のオブジェクト指向実装は CLOS Metaobject Protocol (MOP) と呼ばれ、これによってカスタマイズや拡張が可能となっている。[2]
特徴
多重ディスパッチ
CLOS は多重ディスパッチシステムである。すなわち、引数の データ型によってメソッドを用意できる。多 くのオブジェクト指向言語は単一ディスパッチであり、メソッドは第一引数の データ型でしか多重化できない。CLOS のメソッドは総称関数にグループ 化される。総称関数は同じ名前と引数構造を持つ(ただし個々の引数のデータ 型が異なる)メソッドを集めたものである。後の例によりこのことをより良く 説明する。
弱いカプセル化
多くの動的オブジェクト指向言語(Pythonなど)と同様、CLOS では
カプセル化が行われない。任意のデータ(スロット)に
slot-value
関数でアクセス可能である。
データ構造や関数管理にあたっては、CL のプログラマはパッケージ機能を用いる。 その意味で、カプセル化はファイル単位やクラス単位ではなくパッケージ単位で行われる。 具体的には、スロットの名前を外部に公開するか否かを選ぶことによって 未知のプログラムからオブジェクトの中身を保護する。
;;;; パッケージaでa-classを定義する ;;;;
(in-package :a)
(defclass a-class ()
((a-slot :initarg :a)))
(export '(a-class))
;;;; パッケージbでb-classを定義する ;;;;
(in-package :b)
(defclass b-class ()
((b-slot :initarg :b)))
(export '(b-class b-slot))
;;;; パッケージcでパッケージaとbをインポートする。
;;;; その中からa-slotとb-slotを呼び出してみる。
(in-package :c)
(use :a)
(use :b)
(slot-value (make-instance 'a-class :a 1) 'a-slot)
;; --> slot 'a-slot is not accessible
(slot-value (make-instance 'b-class :b 2) 'b-slot)
;; --> 2
多重継承
CLOS は多重継承を許している。菱形継承問題は、メソッド結合度の 戦略によって異なる解決法を選ぶことができる。
動的クラス変更
CLOSでのクラスは動的であり、オブジェクトの内容だけでなく「構造」を実行時に変更できる。
インスタンスが属するクラスを変更する関数は (change-class INSTANCE NEW-CLASS-NAME &rest INITARGS)
である。
また、CLOS は実行時に(既にそのクラスがインスタンスを持っていても)クラス定義を変更できる。
具体的には、defclass
を再度評価してクラス定義を変更した際、CLOSは(make-instances-obsolete CLASS)
を呼び出す。make-instances-obsolete
は指定されたクラスのすべてのインスタンスに対してupdate-instance-for-redefined-class
を呼び出す。
update-instance-for-redefined-class
は新たに追加されたスロットや削除されたスロットの情報を引数として受けとり、同じく引数として受け取ったインスタンスから新しい定義に基づいて新たに作ったインスタンスへ値をコピーする。値をコピーした後、元のインスタンスをガベージコレクタに掛け、かつ元のポインタが新たなインスタンスを指すように変更する。
クラスベース
CLOS はプロトタイプベースではない。インスタンスをあるクラスのメンバーとして作成するには事前にdefclass
によってそのクラスを定義しなければならない。
MOP : Meta Object Protocol
ANSI 標準の範囲外だが、CLOS の実装に広く採用されている拡張としてメタオブジェクトプロトコル(MOP)がある。MOP は CLOS 実装基盤に標準インタフェースを定義し、クラスをメタクラスのインスタンスとして扱い、新たなメタクラスを定義したり、基底クラスの振る舞いを修正したりできる。CLOS MOP はアスペクト指向プログラミングの先取りとも言え、実際同じ技術者(Gregor Kiczales など)が関わっている。
MOPは実装系によって扱いが異なるが、その重要性のため、 可搬性を担保する試みが行われている。結果、現在では、 インターフェースを共通にするライブラリ Closer-MOP が デファクトスタンダードとなっている。
実行メソッドの形成[3]
実行する内容は実行時に決定される。
コンパイラによる最適化がある場合もある。
declare
宣言によってタイプをコンパイラに指定しないばあい、
すべての方判定とメソッドの形成が実行時に行われるため、速度は非常に遅く
なる。
適用可能メソッドを並べる
引数の型と継承関係に応じて、適用してよいメソッドを集める。
クラスperson
がクラスanimal
を継承し
ているとする。p1とp2は共にperson
のインスタンスである。
総称関数reaction
の以下のような呼び出し
(reaction p1 p2)
について、メソッドが以下のように定義されている場合、
(defmethod reaction ((a1 animal) (a2 animal)) (foo)) ; 1
(defmethod reaction ((a1 person) (a2 animal)) (bar)) ; 2
(defmethod reaction ((a1 animal) (a2 person)) (baz)) ; 3
(defmethod reaction ((a1 person) (a2 person)) (bab)) ; 4
上の4つはすべて適用可能メソッドである。
クラス継承順によるメソッドの並び替え
これら4つのメソッドは、メソッド適用度に従ってソートされる。 メソッド適用度は、クラスの継承順にしたがって計算される。
上の例では、呼び出す際のp1とp2が共にクラスpersonのインスタンスであるた め、それに最もマッチしている4番目のメソッドが最も高い適用度を持つ。
また、適用度は引数の順序でも並び替えられる。上の例では、2番目と3番目 を比べた場合、第一引数の適用度が優先されるため、2番目の適用度のほうが 高くなる。
結果的に、メソッドらは4,2,3,1の順でソートされる。
メソッド結合(メソッド・コンビネーション)
CLOSでは、上のソートによって作られたメソッドのリストに 一定の戦略を適用することで、最終的に実際に行う動作を決定する。 この戦略は自由に変えられる多様なものであり、メソッド結合と呼ば れる。いくつかのバリエーションが標準で定義されている。
他の言語で、あるインスタンスのメソッドを呼び出すとき、
そのクラスが継承を持つときの動作について考えてみよう。
Javaのような言語においては、継承されたメソッドは上書きされてしまう。
ときにsuper
という特殊なメソッドが許され、これにより
継承している親のメソッドを呼ぶことができ、柔軟にしようと努力している。
一方,CLOSではそのような通常の「上書き」戦略だけにとどまらず、多種多様 な戦略をデフォルトで持ち、かつ自由に定義できる。
すべてのメソッド結合は :around メソッド結合を持たなくてはならない。
standard メソッドコンビネーション / 標準メソッド結合
これはjavaのもつ継承戦略と同様である。 菱形継承問題については、継承親を記述した順序に従ってメソッドを適用する。
標準メソッド結合は、指定しない場合上書き戦略を用いるが、
その他に:around
,:before
,:after
メソッドを持つ。
:around
メソッドでは、(call-next-method)
とい
う特殊な関数を呼ぶことができ、これによって適用度が次に大きいメソッドを
呼び出すことができる。
javaのような言語では子がsuperを呼んで親メソッドを呼び出すが、CLOSでは 逆に親が子を呼び出すことになっている。
+ メソッド結合
これは、すべての適用可能メソッドを実行し、それらの返した値を足し合わせ て全体の値として返すという結合方法である。
同様のメソッド結合として標準で定義されているものに、
list
,progn
append
などがある。
max メソッド結合
これは、すべての適用可能メソッドを実行し、それらの返した値の最大値を 全体の値として返すという結合方法である。
同様のメソッド結合として標準で定義されているものにmin
がある。
例
以下に、CLOSを用いて複数のクラスでメソッドを適用する例を示す.
クラス定義
動物、野生の犬、ペットの犬、人間、ステュワーデス、男というクラスを定義 した。
(defclass animal ()
((sex :reader sex-of :initarg :sex)))
;; 初期値を設定するためのキーワード引数を :sex に指定する
(defclass named-mixin ()
((name :type string :reader name-of :initarg :name)))
;; 名前を持つオブジェクトのmixin
;; :reader 指定により、スロット読み出し用の関数が自動で定義される
(defclass wild-dog (animal)
((food :initform :anything)))
(defclass pet-dog (animal named-mixin)
((food :initform :dog-food)))
;; :initformにより、インスタンス作成時に値を設定しない場合の初期値を定める
(defclass person (animal named-mixin)
((address :accessor adress-of)))
;; accessor指定により、読み書き両方の関数が作られる
(defclass male-mixin ()
((sex :initform :male)))
(defclass female-mixin ()
((sex :initform :female)))
(defclass stewardess (person female-mixin)
())
スタンダード・メソッドコンビネーション
それぞれの継承順に基づいて、何かを言わせてみる。
(defun say (sound)
(format t sound))
;; 標準出力に書き込む
(defgeneric saysomething (animal))
(defmethod saysomething ((ani animal))
(say "aaaaa!"))
;; 通常、動作は継承により上書きされる
(defmethod saysomething :before ((p person))
(say "hi!"))
;; :before 指定により、子孫クラスのメソッドをあとに続いて実行できる
(defmethod saysomething ((st stewardess))
(say "welcome aboard!"))
;; stewardess は person クラスを継承するので、:before 指定に従い
;; hi! と言った後に welcome aboard! という
list メソッドコンビネーション
多重ディスパッチとlist
メソッドコンビネーションを用いて、
何かが何かに出会った時の反応をリストにして書き出させてみる。
(defgeneric reaction (of to)
(:method-combination :list))
;; 第一引数がとる第二引数への反応を返す。
;; メソッド結合をstandardからlistへ変更する。
;; そのため、それぞれのメソッドの返り値がlistにまとめられて返る
(defmethod reaction list ((a1 animal) (a2 animal))
:look)
(defmethod reaction list ((a1 wild-dog) (a2 wild-dog))
(if (in-same-group a1 a2)
:sniff ;; くんくん嗅ぐ
:bark)) ;; 吠える
(defmethod reaction list ((wild wild-dog) (pet pet-dog))
:bark-strongly)
(defmethod reaction list ((pet pet-dog) (wild wild-dog))
:run-away)
(defmethod reaction list ((pet pet-dog) (p person))
:escort)
(defmethod reaction list ((p person) (pet pet-dog))
:go-for-a-walk)
(defmethod reaction list ((p1 person) (p2 person))
:greetings)
(defmethod reaction list ((st stewardess) (p person))
:welcome-aboard)
(defmethod reaction list ((f1 female-mixin) (p person))
:watch-clothes)
(defmethod reaction list ((m male-mixin) (f female-mixin))
:watch-hip-tits-and-waist)
(reaction (make-instance 'animal)
(make-instance 'animal))
;; --> (:LOOK)
(reaction (make-instance 'man)
(make-instance 'stewardess))
;; --> (:GREETINGS :LOOK :WATCH-HIP-TITS-AND-WAIST)
(reaction (make-instance 'stewardess)
(make-instance 'man))
;; --> (:WELCOME-ABOARD :GREETINGS :LOOK :WATCH-CLOTHES)
(reaction (make-instance 'wild-dog)
(make-instance 'man))
;; --> (:LOOK)
(reaction (make-instance 'pet-dog)
(make-instance 'man))
;; --> (:ESCORT :LOOK)
(reaction (make-instance 'pet-dog)
(make-instance 'wild-dog))
;; --> (:RUN-AWAY :LOOK)
参考文献
- "CommonLoops: merging Lisp and object-oriented programming", by Daniel G. Bobrow, Kenneth Kahn, Gregor Kiczales, Larry Masinter, Mark Stefik, Frank Zdybel. 1986, Portland, Oregon, United States. Pages 17 - 29 of the Conference on Object Oriented Programming Systems Languages and Applications, ISSN 0362-1340.
- "A History and Description of CLOS", by Jim Veitch. Pages 107-158 of Handbook of Programming Languages, Volume IV: Functional and Logic Programming Languages, ed. Peter H. Salus. 1998 (1st edition), Macmillian Technical Publishing; ISBN 1-57870-011-6
- Gregor Kiczales, Jim des Rivieres, and Daniel G. Bobrow, The Art of the Metaobject Protocol, 1991, MIT Press. ISBN 0-262-61074-4
- Sonya Keene, Object-Oriented Programming in Common Lisp: A Programmer's Guide to CLOS, 1988, Addison-Wesley. ISBN 0-201-17589-4.
脚注
- ^ 「CLOS は標準規格である。複数のベンダーがCLOSを提供している。CLOS やその一部は他のLISP系言語である EuLisp や EmacsLisp にオブジェクト指向を導入するのに使われている」 p. 110 (Veitch 1998)
- ^ p. 108 (Veitch 1998)
- ^ Hyperspec, Determining the Effective Method : http://www.lispworks.com/documentation/HyperSpec/Body/07_ffa.htm
外部リンク
- The Common Lisp Object System: An Overview by Richard P. Gabriel and Linda DeMichiel
- Common Lisp HyperSpec, Chapter 7: Objects