コンテンツにスキップ

Common Lisp Object System

出典: フリー百科事典『ウィキペディア(Wikipedia)』

これはこのページの過去の版です。Guicho w (会話 | 投稿記録) による 2012年12月30日 (日) 05:29個人設定で未設定ならUTC)時点の版 (多重継承)であり、現在の版とは大きく異なる場合があります。

Common Lisp Object System(コモン リスプ オブジェクトシステム、略称 CLOS)は、ANSI Common Lisp (CL) の一部をなすオブジェクト指向プログラミング機能であり、他の類似の言語(EuLispEmacs Lisp)にも導入されている[1]。当初アドオンとして提案され、ANSIの標準に組み込まれた。CLOS は動的オブジェクトシステムであり、C++Javaのような静的なオブジェクト指向言語とは大きく異なる。初期のLISPオブジェクトシステム(MIT FlavorsCommon 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 がデファクトスタンダードとなっている。

実行メソッドの形成

実行する内容は実行時に決定される。コンパイラによる最適化がある場合もある。declare宣言によってタイプをコンパイラに指定しない場合、すべての型判定とメソッドの形成が実行時に行われるため、速度は非常に遅くなる。[3]

適用可能メソッドを並べる

引数の型と継承関係に応じて、適用してよいメソッドを集める。

クラス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つのメソッドは、引数オブジェクトの継承順序(Precedence Order)に従ってソートされる。 上の例では、呼び出す際のp1とp2が共にクラスpersonのインスタンスであるため、それに最もマッチしている4番目のメソッドが最も高い継承順序を持つ。

継承順序は通常、左側の引数から順に計算される。従って上の例では、2番目と3番目を比べた場合、第一引数の適用度が優先されるため、2番目の適用度のほうが高くなる。ただし、このオプションはdefgeneric:argument-precedence-orderというオプションによって逆順に変更できる[4]

結果的に、メソッドらは4,2,3,1の順でソートされる。

メソッド結合(メソッド・コンビネーション)

CLOSでは、上のソートによって作られたメソッドのリストに一定の戦略を適用することで、最終的に実際に行う動作を決定する。この戦略は自由に変えられる多様なものであり、メソッド結合と呼ばれる。いくつかのバリエーションが標準で定義されている。

他の言語において、あるインスタンスのメソッドを呼び出すときの動作について考えてみよう。そのインスタンスのクラスが親クラスを持つとき、例えばJavaのような言語においては、継承されたメソッドは上書きされてしまう。一方,CLOSではそのような通常の「上書き」戦略だけにとどまらず、多種多様な戦略をデフォルトで持ち、かつ自由に定義できる。 (ただし、javaはsuperという特殊なメソッドを備えているため、子クラスのメソッドから親のメソッドを呼ぶこととができ、言語の柔軟性を高めている。)

すべてのメソッド結合は:aroundメソッド結合を持たなくてはならないと定義されている。

standard メソッドコンビネーション / 標準メソッド結合

これはjavaのもつ継承戦略と共通点がある。標準メソッド結合では、メソッド指定子(Qualifier)を指定しない場合には上書き戦略を用いるが、その他に:around,:before,:after メソッド結合を持つ。

:aroundメソッドでは、(call-next-method)という特殊な関数を呼ぶことができ、これによって適用度が次に大きいメソッドを呼び出すことができる。javaでは子がsuperを通じて親メソッドを呼び出すが、CLOSでは逆に親が子を呼び出す、という違いがある。

+ メソッド結合

これは、すべての適用可能メソッドを実行し、それらの返した値を足し合わせて全体の値として返すという結合方法である。同様のメソッド結合として標準で定義されているものに、list,progn,appendなどがある。

max メソッド結合

これは、すべての適用可能メソッドを実行し、それらの返した値の最大値を全体の値として返すという結合方法である。 同様のメソッド結合として標準で定義されているものにminがある。

新たなメソッド結合の定義

define-method-combination[5]は、ユーザーに、新たなメソッド結合を定義する自由を与える。

以下に、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.

脚注

  1. ^ 「CLOS は標準規格である。複数のベンダーがCLOSを提供している。CLOS やその一部は他のLISP系言語である EuLisp や EmacsLisp にオブジェクト指向を導入するのに使われている」 p. 110 (Veitch 1998)
  2. ^ p. 108 (Veitch 1998)
  3. ^ Hyperspec, Determining the Effective Method : http://www.lispworks.com/documentation/HyperSpec/Body/07_ffa.htm
  4. ^ http://www.lispworks.com/documentation/HyperSpec/Body/07_ffab.htm
  5. ^ http://www.lispworks.com/documentation/HyperSpec/Body/m_defi_4.htm#define-method-combination

外部リンク