Next: , Previous: , Up: 関数   [Contents][Index]


13.8 ジェネリック関数

defunを使用して定義された関数は、その引数の型と期待する値に関して、ハードコードされた一連の仮定をもちます。たとえば数字か数字のリストを引数値として処理するようにデザインされた関数は、ベクターや文字列のような他の型の値で呼び出されると失敗したりエラーをシグナルするでしょう。これはその関数実装が、デザイン時に想定した以外の型に対応しないために発生します。

対照的に多相型関数(polymorphic functions)を使用したオブジェクト指向プログラムでは、同一の名前をもつ一連の特化した関数のそれぞれが、特定の引数型セットにたいして記述されます。どの関数が実際に呼び出されるかは、実際の引数の型にもとづいて実行時に決定されます。

Emacsはポリモーフィズム(polymorphism)にたいするサポートを提供します。他のLisp環境、特にCommon LispとCommon Lispオブジェクトシステム(CLOS)と同じように、このサポートはジェネリック関数(generic functions)を基礎としています。Emacsのジェネリック関数は同一名の使用を含むCLOSに密接にしたがっているので、CLOSの経験があればこのセクションの残りの部分は非常に身近に感じるでしょう。

ジェネリック関数は、その名前と引数のリストを指定して、(通常は)実装されていない抽象操作(abstract operation)を指定します。引数のいくつかの固有クラスにたいする実際の実装はメソッド(methods)により提供され、これは個別に定義されるべきです。ジェネリック関数を実装するそれぞれのメソッドはジェネリック関数としてとして同じ名前をもちますが、そのジェネリック関数で定義された引数のスペシャライジング(specializing)により、メソッドの定義はどの種類の引数を処理可能かを示します。これらの引数スペシャライザー(argument specializers)は多少の差はあれ特化したものにできます。たとえばstring型はsequenceのようなより一般的な型より特化した型です。

C++やSimulaのようなメッセージベースのOO言語と異なり、ジェネリック関数を実装するメソッドはクラスに属さずに、それらが実装するジェネリック関数に属することに注意してください。

ジェネリック関数が呼び出されると、呼び出し側に渡された実際の引数と各メソッドの引数スペシャライザーを比較することにより、適用可能なメソッドを呼び出します。その呼び出しの実際の引数がメソッドのスペシャライザーと互換性があれば、そのメソッドが適用可能です。複数のメソッドが適用可能ならば、それらは以下で説明する特定のルールにより合成されて、その組み合わせが呼び出しを処理します。

Macro: cl-defgeneric name arguments [documentation] [options-and-methods…] &rest body

このマクロは指定したnameargumentsでジェネリック関数を定義する。bodyが与えられたなら、それは実装のデフォルトを与える。(常に与えられるべきであるが)documentationが与えられたなら、それは(:documentation docstring)の形式でそのジェネリック関数のドキュメント文字列を指定する。オプションのoptions-and-methodsは以下のフォームのいずれかを指定できる:

(declare declarations)

declareフォームで説明するようなdeclareフォーム。

(:argument-precedence-order &rest args)

このフォームは適用可能なメソッド合成にたいするソート順に影響を与える。合成において2つのメソッドを比較する際、メソッドの引数は通常は左から右に試験されて、引数スペシャライザーがより特化した最初のメソッドが他のメソッドより前になる。このフォームで定義された順序はそれをオーバーライドして、左から右ではなくこのフォームの順に応じて試験される。

(:method [qualifiers…] args &rest body)

このメソッドはcl-defmethodが行うようなメソッドを定義する。

Macro: cl-defmethod name [extra] [qualifier] arguments [&context (expr spec)…] &rest [docstring] body

このマクロはnameと呼ばれるジェネリック関数の、特定の実装を定義する。実装コードはbodyで与えられる。もし与えられたらdocstringはそのメソッドのドキュメント文字列である。リストargumentsはジェネリック関数を実装するすべてのメソッドで等しく、その関数の引数リストとマッチしなければならず、(arg spec)という形式の引数スペシャライザーを提供する。ここでargcl-defgeneric呼び出しで指定された引数名、specは以下のスペシャライザーフォームのいずれかであること:

type

このスペシャライザーは、引数がtypeのいずれかであることを要求する。typeは以下で説明する型ヒエラルキーのいずれかの型である。

(eql object)

このスペシャライザーは、引数がobjecteqlであることを要求する。

(head object)

引数はcarobjecteqlであるようなコンスセルでなければならない。

struct-type

引数はcl-defstruct (Structures in Common Lisp Extensions for GNU Emacs Lispを参照)で定義されたstruct-typeという名前のクラス、またはその子クラスのインスタンスでなければならない。

メソッド定義は新たな引数リストのキーワード&contextを使用できる。これはメソッド実行時に環境をテストする余分なスペシャライザーを導入する。このキーワードは必須の引数リストの後、かつすべての&rest&optionalキーワードの前に記述すること。&contextスペシャライザーは正規の引数スペシャライザー(expr spec)と非常によく似ているが、exprはカレントコンテキストで評価される式であり、specは比較対象となる値となる。たとえば&context (overwrite-mode (eql t))は、メソッドをoverwrite-modeがオンのときだけ適用可能にする。&contextキーワードの後には任意個数のコンテキストスペシャライザーを続けることができる。コンテキストスペシャライザーはジェネリック関数の引数signatureの一部ではないので、これらを必要としないメソッドでは省略できる。

型スペシャライザー(arg type)は以下のリストのシステム型(system types)のいずれかを指定できる。親の型が指定されたときは、型がより特化した子型、孫型、曾孫型、...のいずれかであるような任意の引数も互換となるだろう。

integer

親型: number

number
null

親型: symbol

symbol
string

親型: array

array

親型: sequence

cons

親型: list

list

親型: sequence

marker
overlay
float

親型: number

window-configuration
process
window
subr
compiled-function
buffer
char-table

親型: array

bool-vector

親型: array

vector

親型: array

frame
hash-table
font-spec
font-entity
font-object

:extra string’として表されるオプション要素extraによって、同一のspecializerとqualifierにたいして、stringで区別されるメソッドを追加できる。

オプションのqualifierは複数の適用可能なメソッドの合成を許容する。与えられなければ定義されるメソッドはprimary(主)メソッドとなり、スペシャライズされた引数にたいする主要な実装の提供に責任を有する。qualifierとして以下の値のいずれかを使用してauxiliary(副)メソッドも定義できる:

:before

このauxiliaryメソッドはprimaryメソッドの前に実行される。より正確にはすべての:beforeメソッドは、より特化したメソッドが最初になる順で、primaryメソッドの前に実行される。

:after

このauxiliaryメソッドはprimaryメソッドの後に実行される。より正確にはすべてのこの類のメソッドは、より特化したメソッドが最後になる順で、primaryメソッドの後に実行される。

:around

このauxiliaryメソッドはprimaryメソッドの代替えとして実行される。この類のメソッドでもっとも特化したものが他のメソッドより前に実行される。このようなメソッドは他のauxiliaryメソッドやprimaryメソッドを呼び出すために、通常は以下で説明するcl-call-next-methodを使用する。

cl-defmethodを使用して定義した関数をインタラクティブにすることはできない。つまりinteractiveフォームを追加してコマンドにすることはできない(コマンドの定義を参照)。多相型コマンド(polymorphic command)が必要なら、cl-defgenericcl-defmethodを通じて定義した多相型関数(polymorphic function)を呼び出す通常のコマンドを定義することを推奨する。

ジェネリック関数が呼び出されると、毎回その関数にたいして定義された適用可能なメソッドを合成することによってその呼び出しを処理するeffectiveメソッド(effective method)を構築します。適用可能なメソッドを探してeffectiveメソッドを生成するプロセスはdispatchと呼ばれます。その呼び出しの実際の引数と互換性があるスペシャライザーをもつすべてのメソッドが、互換性のあるメソッドです。すべての引数がスペシャライザーと互換でなければならないので、それらはすべてメソッドが適用可能かどうか判定します。複数の引数に明示的に特化したメソッドをmultiple-dispatchメソッド(multiple-dispatch methods)と呼びます。

適用可能なメソッドはそれらが合成される順にソートされます。最左の引数スペシャライザーがもっとも特化したものであるようなメソッドが、順序の最初になります(上述したようにcl-defmethodの一部として:argument-precedence-orderを指定することによりこれをオーバーライドできる)。そのメソッドのbodyがcl-call-next-methodを呼び出すと、もっとも特化した次のメソッドが実行されます。適用可能な:aroundメソッドがあれば、それらのうちもっとも特化したメソッドが実行されます。そのメソッドはより特化していない任意の:aroundメソッドを実行するために、cl-call-next-methodを呼び出すべきです。次に:beforeメソッドがその特化した順に、その後にspecificityメソッドが実行されます。そして後に:afterメソッドがその特化した順と逆順で実行されます。

Function: cl-call-next-method &rest args

primaryメソッドか:around auxiliaryメソッド内のレキシカルbody内で呼び出されると、同じジェネリック関数にたいして適用可能な次のメソッドを呼び出す。通常これは引数なしで呼び出され、これは次の適用可能なメソッドを呼び出すメソッドが、呼び出されたときと同じ引数で次のメソッドを呼び出すことを意味する。それ以外ならかわりに指定された引数が使用される。

Function: cl-next-method-p

primaryメソッドか:around auxiliaryメソッドのレキシカルbody内からこの関数を呼び出したときに、呼び出す次のメソッドが存在すれば非nilをリターンする。