Common Lisp Object SystemCommon Lisp Object System(コモン リスプ オブジェクトシステム、略称 CLOS)は、ANSI Common Lisp (CL) の一部をなすオブジェクト指向プログラミング機能であり、他の言語(EuLisp や Emacs Lisp、Scheme、Dylan)にも類似のシステムが導入されている[1]。1984年のCommon Lisp仕様ではオブジェクト指向システムは時期尚早として組み込まれなかったが、1994年のANSI標準では組み込み機能となった。CLOS は強い型付けをもつ動的(実行時に定義を変更できる)オブジェクトシステムであり、C++やJavaのような静的なオブジェクト指向言語とは大きく異なる。1970年代に始まる初期のLISPオブジェクトシステム(MIT Flavors や LOOPS)に影響されているが、より汎用的である。 LISPにオブジェクト指向を導入することは簡単である。2ページ程度のコードがあれば実現できる(Graham, 1994)。一方、オブジェクト指向LISPを柔軟で拡張性に富んだものに設計するのはより困難であった。CLOS は完全なオブジェクトシステムであり、オブジェクト指向システム自体がオブジェクト指向プログラミングによって拡張可能である。拡張のプロトコルは CLOS Metaobject Protocol (MOP) と呼ばれる。[2] 特徴多重ディスパッチCLOS は多重ディスパッチシステムである。すなわち、引数のデータ型によってメソッドを用意できる。多くのオブジェクト指向言語は単一ディスパッチであり、メソッドは第一引数のデータ型でしかディスパッチできない。CLOS のメソッドは総称関数にグループ化される。総称関数は同じ名前と引数構造を持つ(ただし個々の引数のデータ型が異なる)メソッドを集めたものである。後の例によりこのことをより良く説明する。 弱いカプセル化多くの動的オブジェクト指向言語(Pythonなど)と同様、CLOS ではカプセル化が行われない。任意のデータ(スロット)に データ構造や関数管理にあたって、CL のプログラマはパッケージというモジュール管理機能を用いることが多い。 多重継承CLOS は多重継承機能をもち、プログラミングスタイルとしてもMixinとして活用されてもいる。標準ではクラス優先順位リストをトポロジカルソートする戦略に加え、メソッドの起動順序のカスタマイズ等が可能であり、C++等にみられる菱形継承問題等は顕在化しない。また、クラス優先順位リストのソートの戦略もCLOS MOPによりクラスごとにユーザーがカスタマイズが可能である。 動的クラス変更CLOSでのオブジェクトのクラスは動的であり、オブジェクトの内容だけでなく「構造」を実行時に変更できる。インスタンスが属するクラスを変更する関数は また、CLOS は実行時に(既にそのクラスがインスタンスを持っていても)クラス定義を変更できる。具体的には、 クラスベースCLOS はプロトタイプベースではない。インスタンスをあるクラスのメンバーとして作成するには事前に MOP : Meta Object ProtocolANSI 標準の範囲外だが、CLOS の実装に広く採用されている拡張としてメタオブジェクトプロトコル(The Common Lisp Object System MetaObject Protocol)がある。MOP は CLOS 実装基盤に標準インタフェースを定義し、クラスをメタクラスのインスタンスとして扱い、新たなメタクラスを定義したり、基底クラスの振る舞いを修正したりできる。CLOS MOP はアスペクト指向プログラミングの先取りとも言え、実際同じ技術者(Gregor Kiczales など)が関わっている。代表的な機能は MOPは実装系によって扱いが異なるが、その重要性のため、可搬性を担保する試みが行われている。結果、現在では、インターフェースを共通にするライブラリ Closer to MOP がデファクトスタンダードとなっている[3]。 実行メソッドの形成総称関数を呼び出した時、実行する手続きの内容は、多くの動的オブジェクト指向プログラミングシステムと同様、その実行時に動的に決定されるが、メソッドキャッシュやコンパイル時に決定できる最適化を用いて高速化を図っている処理系が多い。 適用可能メソッドを並べる引数の型と継承関係に応じて、適用してよいメソッドを集める。 クラス (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つのメソッドは、引数オブジェクトの継承順序(Precedence Order)に従ってソートされる。 上の例では、呼び出す際のp1とp2が共にクラスpersonのインスタンスであるため、それに最もマッチしている4番目のメソッドが最も高い継承順序を持つ。 継承順序は通常、左側の引数から順に計算される。従って上の例では、2番目と3番目のメソッドを比べた場合、第一引数の適用度が優先されることから、2番目の適用度のほうが高くなる。[4][5] 結果的に、メソッドらは4,2,3,1の順でソートされる。 メソッド結合(メソッド・コンビネーション)CLOSでは、上のソートによって作られたメソッドのリストに一定の戦略を適用することで、最終的に実際に行う動作を決定する。この戦略のことをメソッド結合と呼ぶ。いくつかのバリエーションが標準で定義されている。 他の言語において、あるインスタンスのメソッドを呼び出すときの動作について考えてみよう。そのインスタンスのクラスが親クラスを持つとき、例えばJavaのような言語においては、継承されたメソッドは上書きされてしまう。一方,CLOSではそのような通常の上書き(オーバーライド)戦略だけにとどまらず、多種多様な戦略がANSI標準で定義され、かつ自由に定義できる。
(ただし、javaは メソッド結合法則は総称関数の定義ごとに指定する。標準メソッド結合以外のメソッド結合法則を指定した場合、メソッド定義の際にはメソッド指定子(Qualifier)を指定して、結合法則に固有の機能を使うことを指定しなくてはいけない。それぞれのメソッド結合は複数のメソッド指定子を持つ。なお、すべてのメソッド結合は standard メソッドコンビネーション / 標準メソッド結合これはjavaのもつ継承戦略と共通点がある。標準メソッド結合では、指定子を指定しない( unspecified method qualifier )メソッドのことをプライマリ・メソッド ( Primary method )と呼び、これには上書き戦略を用いる。また、その他に:around,:before,:afterメソッド結合を持つ。 メソッド定義の中では、(call-next-method)という特殊な関数を呼ぶことができ、これは次に適用すべきメソッドを呼び出す。このページの上部に、この関係を記した図がある。 + メソッド結合これは、すべての適用可能メソッドを実行し、それらの返した値を足し合わせて全体の値として返すという結合方法である。同様のメソッド結合として標準で定義されているものに、 max メソッド結合これは、すべての適用可能メソッドを実行し、それらの返した値の最大値を全体の値として返すという結合方法である。
同様のメソッド結合として標準で定義されているものに 新たなメソッド結合の定義
例以下に、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 メソッドコンビネーション多重ディスパッチと (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)
参考文献
脚注
外部リンク
|