
Common Lisp Object System (CLOS)は、 ANSI Common Lispにおけるオブジェクト指向プログラミングのための機能です。CLOS は動的なオブジェクトシステムであり、 C++やJavaのようなより静的な言語に見られる OOP 機能とは根本的に異なります。CLOS は、 MIT FlavorsやCommonLoopsなどの以前の Lisp オブジェクトシステムに触発されたものですが、どちらよりも汎用的です。当初はアドオンとして提案された CLOS は、Common Lisp の ANSI 標準の一部として採用され、EuLispやEmacs Lispなどの他の Lisp 方言にも採用されています。[ 1 ]
CLOS の基本的な構成要素は、メソッド、クラス、それらのクラスのインスタンス、および汎用関数です。CLOS は、これらを定義するためのマクロdefclass、、defmethodおよびを提供しますdefgeneric。インスタンスは、メソッドを使用して作成されますmake-instance。
クラスは、複数のスーパークラス、スロットのリスト(C++/Java の用語ではメンバ変数)、および特別なメタクラスを持つことができます。スロットは、クラスごと(クラスのすべてのインスタンスがスロットを共有)またはインスタンスごとに割り当てることができます。各スロットには名前があり、その名前を使用して関数でスロットの値にアクセスできますslot-value。さらに、スロットの値を書き込んだり読み取ったりするための特別な汎用関数を定義することもできます。CLOS クラス内の各スロットには、一意の名前が必要です。
CLOSは多重ディスパッチシステムです。つまり、メソッドは必要な引数の全部または一部に基づいて特殊化できます。ほとんどのオブジェクト指向言語は単一ディスパッチであり、メソッドは最初の引数のみに基づいて特殊化されます。もう一つの特徴は、メソッドがクラスに「属さない」ことです。クラスは汎用関数やメソッドのための名前空間を提供しません。メソッドはクラスとは別に定義され、クラスのスロットへの特別なアクセス権(「this」、「self」、「protected」など)を持ちません。
CLOS のメソッドは、汎用関数にグループ化されます。汎用関数は、関数のように呼び出し可能なオブジェクトであり、メソッドのコレクションを共通の名前と引数構造に関連付け、それぞれが異なる引数に特化されます。Common Lisp は構造体と組み込みデータ型 (数値、文字列、文字、シンボルなど) に CLOS 以外のクラスを提供しているため、CLOS ディスパッチはこれらの CLOS 以外のクラスでも機能します。CLOS は、個々のオブジェクト (eql 特殊化子) に対するディスパッチもサポートしています。CLOS はデフォルトではすべての Common Lisp データ型に対するディスパッチをサポートしていません (たとえば、完全に特殊化された配列型や、によって導入された型に対してはディスパッチは機能しませんdeftype)。ただし、ほとんどの Common Lisp 実装では、汎用関数がアプリケーション固有の特殊化とディスパッチ規則を提供できるメタオブジェクト プロトコルが提供されています。
CLOSにおけるディスパッチは、ほとんどのオブジェクト指向言語とは異なります。
このディスパッチメカニズムは実行時に動作します。そのため、メソッドを追加または削除すると、実行時に有効なメソッドが変更される可能性があります(汎用関数が同じ引数で呼び出された場合でも)。メソッドの組み合わせを変更すると、有効なメソッドが異なる場合もあります。
例えば、
; 共通引数構造プロトタイプを宣言します。( defgeneric f ( x y )); (f integer y) の実装を定義します。ここで、y はすべての型に一致します。( defmethod f (( x integer ) y ) 1 )( f 1 2.0 ) => 1; (f 整数実数) の実装を定義します。( defmethod f (( x整数) ( y実数)) 2 )( f 1 2.0 ) => 2 ; 実行時にディスパッチが変更されました。ほとんどの動的言語のオブジェクト指向システムと同様に、CLOS はカプセル化を強制しません。どのスロットにもslot-value、関数または (オプションで自動生成される)アクセサ メソッドを使用してアクセスできます。関数を使用してアクセスするには、slot-valueスロットの名前を知っている必要があります。CL プログラマは、言語のパッケージ機能を使用して、エクスポートする関数またはデータ構造を宣言します。
通常の(「プライマリ」)メソッドの他に:before、、、:afterおよび:around「補助」メソッドがあります。前者の2つは、クラス階層に基づいて特定の順序でプライマリメソッドの前または後に呼び出されます。メソッドは、プライマリメソッドが実行されるかどうかを制御できます。さらに、プログラマは、クラス階層に沿って考えられるすべてのプライマリメソッドを呼び出す:aroundか、最も近い一致を提供するメソッドのみを呼び出すかを指定できます。
標準メソッド組み合わせは、上記で説明した主要メソッド、前メソッド、後メソッド、周辺メソッドを提供します。他のメソッドタイプを持つメソッド組み合わせも存在します。新しい(単純なものから複雑なものまで)メソッド組み合わせとメソッドタイプを定義することも可能です。
CLOSは多重継承をサポートしています。多重継承においてメソッドが実行されるデフォルトの順序が正しくない場合、プログラマはメソッドの組み合わせ順序を指定することで、ダイヤモンド継承の問題を解決できます。
CLOSは動的であり、実行時にオブジェクトのコンテンツだけでなく構造も変更できます。CLOSは、クラス定義をその場で変更すること(対象のクラスのインスタンスが既に存在する場合でも)や、演算子を介して特定のインスタンスのクラスメンバーシップを変更することをサポートしていますchange-class。CLOSでは、実行時にメソッドを追加、再定義、削除することもできます。円楕円問題はCLOSで容易に解決され、ほとんどのOOP設計パターンは消滅するか、質的に単純化されます。[ 2 ]
CLOSはプロトタイプ言語ではありません。オブジェクトをそのクラスのメンバーとしてインスタンス化するには、まずクラスを定義する必要があります。
ANSI Common Lisp 標準とは別に、メタオブジェクトプロトコル (MOP) と呼ばれる CLOS の広く普及した拡張機能があります。MOP は、CLOS 実装の基盤への標準インターフェースを定義し、クラス、スロット記述、汎用関数、メソッド自体をメタクラスのインスタンスとして扱い、新しいメタクラスの定義と CLOS のすべての動作の変更を可能にします。CLOS MOP の柔軟性は、後にグレゴール・キザレスなどの同じエンジニアによって開発されたアスペクト指向プログラミングの先駆けとなりました。MOP は、一連のプロトコルによってオブジェクトシステム全体の動作を定義します。これらは CLOS の観点から定義されます。したがって、提供されている CLOS の機能を拡張または変更することで、新しいオブジェクトシステムを作成することが可能です。書籍「メタオブジェクトプロトコルの芸術」では、CLOS MOP の使用方法と実装について解説しています。
Common Lispの様々な実装では、メタオブジェクトプロトコルのサポートが若干異なっています。Closer [ 3 ]プロジェクトは、不足している機能を提供することを目的としています。
Flavors (およびその後継であるNew Flavors)は、MIT Lisp Machineのオブジェクトシステムでした。Lisp Machineオペレーティングシステムの大部分と、それを使用する多くのアプリケーションは、FlavorsまたはNew Flavorsを使用しています。Flavorsは、多重継承やミックスインなどの機能を導入しました。Flavorsはほとんど廃止されていますが、Common Lisp用の実装は存在します。Flavorsはメッセージパッシングパラダイムを使用していました。New Flavorsはジェネリック関数を導入しました。
CommonLoopsはLOOPS(Xerox Interlisp -D由来)の後継です。CommonLoopsはCommon Lisp向けに実装されました。Portable CommonLoops(PCL)と呼ばれる移植性の高い実装が、CLOSの最初の実装でした。PCLは広く移植されており、現在でもいくつかのCommon Lisp実装のCLOS実装の基盤となっています。PCLは、システム依存の部分がごくわずかで、ほとんどが移植性の高いCommon Lispで実装されています。
CLOSの強力さと表現力、そして歴史的にTiny CLOS( Schemeで使用するためにGregor Kiczalesによって書かれた、簡略化されたポータブルなCLOS実装)が利用可能であったことから、CLOSに似たMOPベースのオブジェクトシステムは、ほとんどのLisp方言実装において事実上の標準となり、他の言語のOOP機能にも取り入れられるようになりました。