「Common Lisp Object System」の版間の差分

削除された内容 追加された内容
Guicho w (会話 | 投稿記録)
Melan (会話 | 投稿記録)
整形
1行目:
 
= 概要 =
 
'''Common Lisp Object System'''(コモン リスプ オブジェクトシステム、略称 '''CLOS''')は、ANSI [[Common Lisp]] (CL) の一部をなす[[オブジェクト指向プログラミング]]機能であり、他の類似の言語([[EuLisp]] や [[Emacs Lisp]])にも導入されている<ref>「CLOS は標準規格である。複数のベンダーがCLOSを提供している。CLOS やその一部は他のLISP系言語である EuLisp や EmacsLisp にオブジェクト指向を導入するのに使われている」 p. 110 (Veitch 1998)</ref>。当初アドオンとして提案され、[[ANSI]]の標準に組み込まれた。CLOS は[[動的プログラミング言語|動的]]オブジェクトシステムであり、[[C++]]や[[Java]]のような静的なオブジェクト指向言語とは大きく異なる。初期のLISPオブジェクトシステム(MIT [[Flavors]] や [[Common LOOPS]])に影響されているが、より汎用的である。この汎用性を持たせることが難しい点である。
 
LISPにオブジェクト指向を導入することは簡単である。2ページ程度のコードがあれば実現できる(Graham, 1994)。オブジェクト指向LISPを柔軟で拡張性に富んだものにするのはもっと難しい。CLOS は完全なオブジェクトシステムであり、オブジェクト指向風に実装されている。CLOS のオブジェクト指向実装は CLOS Metaobject Protocol (MOP) と呼ばれ、これによってカスタマイズや拡張が可能となっている。<ref>p. 108 (Veitch 1998)</ref>
 
== 特徴 ==
=== 多重ディスパッチ ===
 
CLOS は[[多重ディスパッチ]]システムである。すなわち、引数の[[データ型]]によって[[メソッド (計算機科学)|メソッド]]を用意できる。多くのオブジェクト指向言語は単一ディスパッチであり、メソッドは第一引数のデータ型でしか多重化できない。CLOS のメソッドは[[総称関数]]にグループ化される。総称関数は同じ名前と引数構造を持つ(ただし個々の引数のデータ型が異なる)メソッドを集めたものである。後の例によりこのことをより良く説明する。
== 多重ディスパッチ ==
 
CLOS は[[多重ディスパッチ]]システムである。すなわち、引数の
[[データ型]]によって[[メソッド (計算機科学)|メソッド]]を用意できる。多
くのオブジェクト指向言語は単一ディスパッチであり、メソッドは第一引数の
データ型でしか多重化できない。CLOS のメソッドは[[総称関数]]にグループ
化される。総称関数は同じ名前と引数構造を持つ(ただし個々の引数のデータ
型が異なる)メソッドを集めたものである。後の例によりこのことをより良く
説明する。
 
== 弱いカプセル化 ==
 
=== 弱いカプセル化 ===
多くの動的オブジェクト指向言語([[Python]]など)と同様、CLOS では
多くの動的オブジェクト指向言語([[Python]]など)と同様、CLOS では[[カプセル化]]が行われない。任意のデータ(スロット)に<code>slot-value</code> 関数でアクセス可能である。
<code>slot-value</code> 関数でアクセス可能である。
 
データ構造や関数管理にあたっては、CL のプログラマはパッケージ機能を用いる。その意味で、カプセル化はファイル単位やクラス単位ではなくパッケージ単位で行われる。具体的には、スロットの名前を外部に公開するか否かを選ぶことによって未知のプログラムからオブジェクトの中身を保護する。
その意味で、カプセル化はファイル単位やクラス単位ではなくパッケージ単位で行われる。
具体的には、スロットの名前を外部に公開するか否かを選ぶことによって
未知のプログラムからオブジェクトの中身を保護する。
 
<source lang="lisp">
55 ⟶ 38行目:
</source>
 
=== 多重継承 ===
CLOS は[[多重継承]]を許している。[[菱形継承]]問題は、メソッド結合度の戦略によって異なる解決法を選ぶことができる。
 
=== 動的クラス変更 ===
CLOS は[[多重継承]]を許している。[[菱形継承]]問題は、メソッド結合度の
CLOSでのクラスは動的であり、オブジェクトの内容だけでなく「構造」を実行時に変更できる。インスタンスが属するクラスを変更する関数は <code>(change-class INSTANCE NEW-CLASS-NAME &rest INITARGS)</code> である。
戦略によって異なる解決法を選ぶことができる。
 
また、CLOS は実行時に(既にそのクラスがインスタンスを持っていても)クラス定義を変更できる。具体的には、<code>defclass</code>を再度評価してクラス定義を変更した際、CLOSは<code>(make-instances-obsolete CLASS)</code>を呼び出す。<code>make-instances-obsolete</code>は指定されたクラスのすべてのインスタンスに対して<code>update-instance-for-redefined-class</code>を呼び出す。
 
== 動的クラス変更 ==
 
CLOSでのクラスは動的であり、オブジェクトの内容だけでなく「構造」を実行時に変更できる。
インスタンスが属するクラスを変更する関数は <code>(change-class INSTANCE NEW-CLASS-NAME &rest INITARGS)</code> である。
 
また、CLOS は実行時に(既にそのクラスがインスタンスを持っていても)クラス定義を変更できる。
具体的には、<code>defclass</code>を再度評価してクラス定義を変更した際、CLOSは<code>(make-instances-obsolete CLASS)</code>を呼び出す。<code>make-instances-obsolete</code>は指定されたクラスのすべてのインスタンスに対して<code>update-instance-for-redefined-class</code>を呼び出す。
 
<code>update-instance-for-redefined-class</code>は新たに追加されたスロットや削除されたスロットの情報を引数として受けとり、同じく引数として受け取ったインスタンスから新しい定義に基づいて新たに作ったインスタンスへ値をコピーする。値をコピーした後、元のインスタンスをガベージコレクタに掛け、かつ元のポインタが新たなインスタンスを指すように変更する。
 
=== クラスベース ===
 
CLOS は[[プロトタイプベース]]ではない。インスタンスをあるクラスのメンバーとして作成するには事前に<code>defclass</code>によってそのクラスを定義しなければならない。
 
=== MOP : Meta Object Protocol ===
 
ANSI 標準の範囲外だが、CLOS の実装に広く採用されている拡張として[[メタオブジェクト|メタオブジェクトプロトコル]](MOP)がある。MOP は CLOS 実装基盤に標準インタフェースを定義し、クラスを[[メタクラス]]のインスタンスとして扱い、新たなメタクラスを定義したり、基底クラスの振る舞いを修正したりできる。CLOS MOP は[[アスペクト指向プログラミング]]の先取りとも言え、実際同じ技術者(Gregor Kiczales など)が関わっている。
 
MOPは実装系によって扱いが異なるが、その重要性のため、可搬性を担保する試みが行われている。結果、現在では、インターフェースを共通にするライブラリ Closer-MOP がデファクトスタンダードとなっている。
可搬性を担保する試みが行われている。結果、現在では、
インターフェースを共通にするライブラリ Closer-MOP が
デファクトスタンダードとなっている。
 
= 実行メソッドの形成<ref>Hyperspec, Determining the Effective Method :
http://www.lispworks.com/documentation/HyperSpec/Body/07_ffa.htm</ref> =
 
実行する内容は実行時に決定される。
コンパイラによる最適化がある場合もある。
<code>declare</code>宣言によってタイプをコンパイラに指定しないばあい、
すべての方判定とメソッドの形成が実行時に行われるため、速度は非常に遅く
なる。
 
== 適用可能実行メソッドを並べるの形成 ==
実行する内容は実行時に決定される。コンパイラによる最適化がある場合もある。<code>declare</code>宣言によってタイプをコンパイラに指定しないばあい、すべての方判定とメソッドの形成が実行時に行われるため、速度は非常に遅くなる。<ref>Hyperspec, Determining the Effective Method : http://www.lispworks.com/documentation/HyperSpec/Body/07_ffa.htm</ref>
 
=== 適用可能メソッドを並べる ===
引数の型と継承関係に応じて、適用してよいメソッドを集める。
 
クラス<code>person</code>がクラス<code>animal</code>を継承しているとする。p1とp2は共に<code>person</code>のインスタンスである。総称関数<code>reaction</code>の以下のような呼び出
ているとする。p1とp2は共に<code>person</code>のインスタンスである。
総称関数<code>reaction</code>の以下のような呼び出し
 
<source lang="lisp">
120 ⟶ 83行目:
上の4つはすべて適用可能メソッドである。
 
=== クラス継承順によるメソッドの並び替え ===
これら4つのメソッドは、メソッド適用度に従ってソートされる。メソッド適用度は、クラスの継承順にしたがって計算される。
 
上の例では、呼び出す際のp1とp2が共にクラスpersonのインスタンスであるため、それに最もマッチしている4番目のメソッドが最も高い適用度を持つ。
これら4つのメソッドは、メソッド適用度に従ってソートされる。
メソッド適用度は、クラスの継承順にしたがって計算される。
 
また、適用度は引数の順序でも並び替えられる。上の例では、2番目と3番目を比べた場合、第一引数の適用度が優先されるため、2番目の適用度のほうが高くなる。
上の例では、呼び出す際のp1とp2が共にクラスpersonのインスタンスであるた
め、それに最もマッチしている4番目のメソッドが最も高い適用度を持つ。
 
また、適用度は引数の順序でも並び替えられる。上の例では、2番目と3番目
を比べた場合、第一引数の適用度が優先されるため、2番目の適用度のほうが
高くなる。
 
結果的に、メソッドらは4,2,3,1の順でソートされる。
 
=== メソッド結合(メソッド・コンビネーション) ===
CLOSでは、上のソートによって作られたメソッドのリストに一定の戦略を適用することで、最終的に実際に行う動作を決定する。この戦略は自由に変えられる多様なものであり、<b>メソッド結合</b>と呼ばれる。いくつかのバリエーションが標準で定義されている。
 
他の言語で、あるインスタンスのメソッドを呼び出すとき、そのクラスが継承を持つときの動作について考えてみよう。Javaのような言語においては、継承されたメソッドは上書きされてしまう。ときに<code>super</code>という特殊なメソッドが許され、これにより継承している親のメソッドを呼ぶことができ、柔軟にしようと努力している。
CLOSでは、上のソートによって作られたメソッドのリストに
一定の戦略を適用することで、最終的に実際に行う動作を決定する。
この戦略は自由に変えられる多様なものであり、<b>メソッド結合</b>と呼ば
れる。いくつかのバリエーションが標準で定義されている。
 
一方,CLOSではそのような通常の「上書き」戦略だけにとどまらず、多種多様な戦略をデフォルトで持ち、かつ自由に定義できる。
他の言語で、あるインスタンスのメソッドを呼び出すとき、
そのクラスが継承を持つときの動作について考えてみよう。
Javaのような言語においては、継承されたメソッドは上書きされてしまう。
ときに<code>super</code>という特殊なメソッドが許され、これにより
継承している親のメソッドを呼ぶことができ、柔軟にしようと努力している。
 
一方,CLOSではそのような通常の「上書き」戦略だけにとどまらず、多種多様
な戦略をデフォルトで持ち、かつ自由に定義できる。
 
すべてのメソッド結合は :around メソッド結合を持たなくてはならない。
 
==== standard メソッドコンビネーション / 標準メソッド結合 ====
これはjavaのもつ継承戦略と同様である。菱形継承問題については、継承親を記述した順序に従ってメソッドを適用する。
 
これはjavaのもつ継承戦略と同様である。
菱形継承問題については、継承親を記述した順序に従ってメソッドを適用する。
 
標準メソッド結合は、指定しない場合上書き戦略を用いるが、その他に<code>:around</code>,<code>:before</code>,<code>:after</code>
その他に<code>:around</code>,<code>:before</code>,<code>:after</code>
メソッドを持つ。
 
<code>:around</code>メソッドでは、<code>(call-next-method)</code>という特殊な関数を呼ぶことができ、これによって適用度が<b>次に大きい</b>メソッドを呼び出すことができる。
う特殊な関数を呼ぶことができ、これによって適用度が<b>次に大きい</b>メソッドを
呼び出すことができる。
 
javaのような言語では子がsuperを呼んで親メソッドを呼び出すが、CLOSでは逆に親が子を呼び出すことになっている。
逆に親が子を呼び出すことになっている。
 
==== + メソッド結合 ====
これは、すべての適用可能メソッドを実行し、それらの返した値を足し合わせて全体の値として返すという結合方法である。
 
同様のメソッド結合として標準で定義されているものに、<code>list</code>,<code>progn</code><code>append</code>などがある。
これは、すべての適用可能メソッドを実行し、それらの返した値を足し合わせ
て全体の値として返すという結合方法である。
 
同様の==== max メソッド結合として標準で定義されているものに、 ====
これは、すべての適用可能メソッドを実行し、それらの返した値の最大値を全体の値として返すという結合方法である。
<code>list</code>,<code>progn</code><code>append</code>などがある。
 
=== max メソッド結合 ===
 
これは、すべての適用可能メソッドを実行し、それらの返した値の最大値を
全体の値として返すという結合方法である。
 
同様のメソッド結合として標準で定義されているものに<code>min</code>がある。
 
== 例 ==
 
= 例 =
 
以下に、CLOSを用いて複数のクラスでメソッドを適用する例を示す.
 
=== クラス定義 ===
 
動物、野生の犬、ペットの犬、人間、ステュワーデス、男というクラスを定義した。
== クラス定義 ==
 
動物、野生の犬、ペットの犬、人間、ステュワーデス、男というクラスを定義
した。
 
<source lang="lisp">
223 ⟶ 157行目:
</source>
 
=== スタンダード・メソッドコンビネーション ===
 
== スタンダード・メソッドコンビネーション ==
 
それぞれの継承順に基づいて、何かを言わせてみる。
 
251 ⟶ 183行目:
</source>
 
=== list メソッドコンビネーション ===
多重ディスパッチと<code>list</code>メソッドコンビネーションを用いて、何かが何かに出会った時の反応をリストにして書き出させてみる。
 
多重ディスパッチと<code>list</code>メソッドコンビネーションを用いて、
何かが何かに出会った時の反応をリストにして書き出させてみる。
 
<source lang="lisp">
324 ⟶ 254行目:
;; --> (:RUN-AWAY :LOOK)
</source>
 
 
== 参考文献 ==