defun
を用いて定義された関数は、引数の型や期待される値について、ハードコードされた一連の前提条件を持っています。例えば、引数として数値または数値のリストを扱うように設計された関数は、ベクトルや文字列など、それ以外の型の値で呼び出された場合、エラーが発生するかエラーシグナルを返します。これは、関数の実装が、設計時に想定した型以外の型を処理できるように準備されていないためです。
これに対し、オブジェクト指向プログラムでは多相関数が用いられます。これは、同じ名前を持つ特化された関数群であり、それぞれが特定の引数型の集合向けに記述されています。実際にどの関数が呼び出されるかは、実行時に実際の引数の型に基づいて決定されます。
Emacsはポリモーフィズムをサポートしています。他のLisp環境、特にCommon LispとそのCommon Lisp Object System (CLOS) と同様に、このサポートはジェネリック関数に基づいています。Emacsのジェネリック関数は、類似した名称の使用を含めCLOSに密接に準拠しているため、CLOSの経験があれば、本節の残りの部分は非常に馴染み深く感じられるでしょう。
ジェネリック関数は、名前と引数のリストを定義することで抽象的な操作を指定しますが、(通常は)実装は指定しません。いくつかの特定の引数クラスに対する実際の実装はメソッドによって提供され、これらは個別に定義する必要があります。ジェネリック関数を実装する各メソッドは、ジェネリック関数と同じ名前を持ちますが、そのメソッドの定義では、ジェネリック関数で定義された引数を特化させることで、どのような種類の引数を扱えるかを示します。これらの引数特化子は、より具体的なものからそうでないものまであります。例えば、string型は、sequenceのようなより汎用的な型よりも具体的です。
なお、C++やSimulaといったメッセージベースのオブジェクト指向言語とは異なり、ジェネリック関数を実装するメソッドはクラスに属するのではなく、それが実装するジェネリック関数に属する点に注意してください。
ジェネリック関数が呼び出される際、呼び出し元から渡された実引数と各メソッドの引数スペシファイアを比較することで、適用可能なメソッドを選択します。呼び出しの実引数がメソッドのスペシファイアと互換性がある場合、そのメソッドは適用可能となります。複数のメソッドが適用可能な場合は、後述する特定のルールに従ってそれらが統合され、その統合されたメソッドが呼び出しを処理します。
このマクロは、指定された name および arguments を持つジェネリック関数を定義します。body
が指定されている場合は、デフォルトの実装が提供されます。documentation
が指定されている場合(常に指定されるべきです)、ジェネリック関数のドキュメント文字列を (:documentation docstring)
の形式で指定します。オプションの options-and-methods
は、以下のいずれかの形式を取ることができます:
(declare declarations)declare
フォームで説明されている宣言フォーム。
(:argument-precedence-order &rest args)このフォームは、適用可能なメソッドを組み合わせる際のソート順に影響します。通常、組み合わせ時に2つのメソッドを比較する場合、メソッドの引数が左から右へと順に調べられ、引数の特殊化子がより具体的な方のメソッドが、もう一方よりも先に配置されます。このフォームで定義される順序はその順序を上書きし、引数は左から右ではなく、このフォームにおける順序に従って調べられます。
(:method [qualifiers…] args
&rest body)このフォームは、cl-defmethodと同様にメソッドを定義します。
このマクロは、name と呼ばれるジェネリック関数に対する特定の実装を定義します。実装コードは body で指定されます。docstring
が存在する場合、それはそのメソッドのドキュメント文字列となります。ジェネリック関数を実装するすべてのメソッドで同一であり、かつその関数の引数リストと一致しなければならない
arguments リストは、(arg spec)
形式の引数スペシャライザを提供します。ここで、arg は cl-defgeneric の呼び出しで指定された引数名であり、spec は以下のスペシャライザ形式のいずれかとなります:
typeこの特殊化器は、引数が指定された type(以下に述べる型階層に含まれる型のいずれか)であることを求めます。
(eql object)この特殊化器では、引数が与えられた object に対して eql である必要があります。
(head object)引数は、car が eql から object までの cons セルでなければならない。
struct-type引数は、cl-defstruct で定義された struct-type という名前のクラス(Common Lisp
Extensions for GNU Emacs Lisp の Structures
を参照)、またはその子クラスのいずれかのインスタンスである必要があります。
メソッド定義では、新しい引数リストキーワード &context
を使用できます。これにより、メソッド実行時の環境を判定する追加のスペシャライザーが導入されます。このキーワードは、必須引数のリストの後、かつ &rest や &optional
といったキーワードの前に記述する必要があります。&context
スペシャライザーは、通常の引数スペシャライザー(expr、spec)と非常によく似ていますが、expr
が現在のコンテキストで評価される式である点と、spec
が比較対象となる値である点が異なります。例えば、&context (overwrite-mode (eql
t)) を指定すると、overwrite-mode
が有効な場合にのみメソッドが適用可能になります。&context
キーワードの後には、任意の数のコンテキストスペシャライザーを続けることができます。コンテキストスペシャライザーはジェネリック関数の引数シグネチャに含まれないため、それらを必要としないメソッドでは省略可能です。
型特化子 (arg type) では、以下のリストにある システム型
のいずれかを指定できます。親型を指定した場合、そのより具体的な子型、および孫型、曾孫型などの型を持つ引数も互換性を持ちます。
integer親タイプ: number.
numbernull親タイプ: symbol
symbolstring親タイプ: array.
array親タイプ: sequence.
cons親タイプ: list.
list親タイプ: sequence.
markeroverlayfloat親タイプ: number.
window-configurationprocesswindowsubrcompiled-functionbufferchar-table親タイプ: array.
bool-vector親タイプ: array.
vector親タイプ: array.
framehash-tablefont-specfont-entityfont-object‘:extra string’ と表記されるオプションの extra 要素を使用すると、同一の特殊化子および限定子に対して、string で区別されるメソッドをさらに追加できます。
オプションの qualifier を指定すると、適用可能な複数のメソッドを組み合わせることができます。これが指定されていない場合、定義されたメソッドは プライマリ メソッドとなり、特化した引数に対する汎用関数の主要な実装を提供する役割を担います。また、qualifier に以下のいずれかの値を指定することで、補助メソッド を定義することも可能です:
:beforeこの補助メソッドは、プライマリメソッドよりも先に実行されます。より正確には、すべての:beforeメソッドが、最も具体的なものから順に、プライマリメソッドよりも先に実行されます。
:afterこの補助メソッドは、プライマリメソッドの後に実行されます。より正確には、こうしたメソッドはすべて、最も具体的なものから順に、プライマリメソッドの後に実行されます。
:aroundこの補助メソッドは、プライマリメソッドの代わりに実行されます。こうしたメソッドのうち、最も具体的なものが他のどのメソッドよりも先に実行されます。通常、これらのメソッドは、後述するcl-call-next-methodを使用して、他の補助メソッドやプライマリメソッドを呼び出します。
cl-defmethod を用いて定義された関数は、interactive 形式を追加してもインタラクティブ(すなわちコマンド(コマンドの定義
を参照))にすることはできません。多相コマンドが必要な場合は、cl-defgeneric および
cl-defmethod で定義された多相関数を呼び出す通常のコマンドを定義することを推奨します。
ジェネリック関数が呼び出されるたびに、その関数に定義された適用可能なメソッドを組み合わせることで、この呼び出しを処理する実効メソッドが構築されます。適用可能なメソッドを特定し、実効メソッドを生成するこのプロセスをディスパッチと呼びます。適用可能なメソッドとは、そのすべての特殊化子が呼び出しの実引数と互換性を持つもののことです。すべての引数が特殊化子と互換性を持つ必要があるため、それらすべてがメソッドの適用可否を決定します。明示的に複数の引数を特殊化するメソッドは、多重ディスパッチメソッドと呼ばれます。
適用可能なメソッドは、結合される順にソートされます。最も左の引数のスペシャライザーが最も具体的なメソッドが、順序の先頭に来ます。(前述の通り、cl-defmethodの一部として:argument-precedence-orderを指定すると、それが上書きされます。)メソッド本体がcl-call-next-methodを呼び出す場合、次に具体的なメソッドが実行されます。適用可能な:aroundメソッドがある場合は、その中で最も具体的なものが最初に実行され、cl-call-next-methodを呼び出してより具体性の低い:aroundメソッドのいずれかを実行します。次に、:beforeメソッドが具体性の順に実行され、続いてプライマリメソッド、そして最後に:afterメソッドが具体性の逆順で実行されます。
プライマリメソッドまたは:around補助メソッドのレキシカルボディ内から呼び出された場合、同一のジェネリック関数に対して次に適用可能なメソッドを呼び出します。通常は引数なしで呼び出され、これは呼び出し元のメソッドが呼び出された際と同じ引数を用いて、次に適用可能なメソッドを呼び出すことを意味します。それ以外の場合は、指定された引数が代わりに使用されます。
この関数は、プライマリメソッドまたは:around補助メソッドの字句本体内から呼び出された際、次に呼び出すべきメソッドが存在する場合にnon-nilを返します。