nptclのブログ

Common Lisp処理系nptの開発メモです。https://github.com/nptcl/npt

7章Objectsのジェネリック関数関連の和訳

【追記】7章Objectsの全てを日本語訳しました。

https://nptcl.github.io/npt-japanese/docs/ansicl/7.html
https://nptcl.github.io/npt-japanese/md/ansicl/7.html

ふたつのリンクは、レイアウトが違うだけでどちらも同じ内容なので好きな方を見てください。
リンク先はDictionaryも翻訳してます。
ただし翻訳してあるのは7章だけです。

7章Objectsのクラス関連の和訳の続きです。

目次

7.6 ジェネリック関数とメソッド
7.6.1 ジェネリック関数の紹介
7.6.2 メソッドの紹介
7.6.3 特定パラメーターと限定子の合致
7.6.4 ジェネリック関数の全てのメソッドのラムダリストの合意
7.6.5 ジェネリック関数とメソッドのキーワード引数
7.6.5.1 ジェネリック関数とメソッドのキーワード引数の例
7.6.6 メソッドの選択とコンビネーション
7.6.6.1 有効なメソッドの決定
7.6.6.1.1 適用可能なメソッドの選択
7.6.6.1.2 優先順位による適用可能なメソッドのソート
7.6.6.1.3 ソートされた適用可能なメソッドのMethod-Combination実行
7.6.6.2 Standard Method-Combination
7.6.6.3 Method-Combinationの宣言
7.6.6.4 組み込みのMethod-Combination
7.6.7 メソッドの継承

7. オブジェクト

7.1~7.5までは7章Objectsのクラス関連の和訳を参照。

7.6 ジェネリック関数とメソッド

7.6.1 ジェネリック関数の紹介

ジェネリック関数は、指定された引数のクラスか、 あるいは引数の同一性に依存して動作する関数です。 ジェネリック関数オブジェクトは、メソッドの集合、 ラムダリスト、method-combination、そしてその他の情報に関連付けられます。

通常の関数のように、ジェネリック関数は引数を取り、 一連のオペレーションを実行し、そしておそらくは有効な値を返却します。 通常の関数は単一のコードの実体を持ち、関数が呼び出されたときに常に実行されます。 ジェネリック関数はコードの実体を複数の集合として持ち、 その集合の一部か全部を関数実行のときに選択します。 選ばれたコードの集合とその組み合わせは、ジェネリック関数に渡される1つか複数の引数から、 クラスかあるいは同一性により決定します。それはmethod-combinationによって決定が行われます。

通常の関数とジェネリック関数は、同一の構文により呼び出されます。

ジェネリック関数は本物の関数なので、funcallapplyの最初の引数として使用されたり、 あるいは引数を渡したりすることができます。

ジェネリック関数の関数名の設定は、いくつかの手順のひとつとして確立します。 それはグローバル環境内において、ensure-generic-function, defmethod(暗にensure-generic-functionが呼ばれる), defgeneric(これもまた暗に、ensure-generic-functionが呼ばれる)によって確立されます。 レキシカル環境において、ジェネリック関数の関数名の束縛を確立するための 標準的な方法は提供されていません。

defgenericフォームが評価されるとき、 (ensure-generic-functionによって)次の3つのうちの1つの手順が取られます。

  • もし指定した名前のジェネリック関数がもう存在している場合は、 存在しているジェネリック関数オブジェクトを修正します。 いま実行されたdefgenericフォームによって宣言されたメソッドは追加され、 そして以前のdefgenericフォームによって定義された、 存在していたジェネリック関数のどんなメソッドも削除されます。 いま実行されたdefgenericフォームによるメソッドの追加は、 defmethod, defclass, define-combination, そしてdefstructによって 定義されたメソッドも置き換えます。 ジェネリック関数内のその他のメソッドについては、影響は無いですし置き換えもされません。

  • もし指定した名前が、通常の関数、マクロ、特殊オペレーターによる名前であった場合は、 エラーが発せられます。

  • そうでなければ、ジェネリック関数はdefgenericフォーム内にある メソッド定義により宣言されたメソッドとともに作成されます。

いくつかのオペレーターは、ジェネリック関数のオプションの定義として、 使用するmethod-combinationのタイプや引数優先順位を指定することが許されています。 これらのオペレーターは、「ジェネリック関数のオプションを指定するオペレーター」と言います。 この分類の中で、標準的なオペレーターはdefgenericだけです。

いくつかのオペレーターは、ジェネリック関数のメソッドを定義します。 これらのオペレーターはメソッド定義オペレーターと言われます。 このオペレーターに関連付けられたフォームは、メソッド定義フォームと呼ばれます。 標準的なメソッド定義オペレーターを次の表に示します。

defgeneric
define-combination
defmethod
defstruct
defclass

表7-1 標準メソッド定義オペレーター

注意として、標準メソッド定義オペレーターのdefgenericだけは、 ジェネリック関数のオプションを指定することが出来ます。 defgenericといくつかの実装定義オペレーターは、 ジェネリック関数のオプションを指定することが可能であり、 「ジェネリック関数のオプションを指定するオペレーター」と言われます。

7.6.2 メソッドの紹介

メソッドは、クラス特定か、あるいは同一性特定による動作と、 ジェネリック関数のオペレーションを定義します。

メソッドオブジェクトは、次のものと結びつけられます。 それは、メソッドの動作を定義したコード。 与えられたメソッドが適用するかどうかを特定するための一連の特定パラメーター。 ラムダリスト。そして、method-combinationがメソッドの区分けをするために使われる、一連の限定子。

メソッドオブジェクトは関数ではないため、関数として呼び出すことはできません。 ジェネリック関数が呼び出されたときのような場合において、 オブジェクトシステムの様々な仕組みがメソッドオブジェクトを受け取り、 そしてメソッド関数を呼び出します。 このような動作を、メソッドが実行された、あるいはメソッドが呼び出されたと言います。

メソッド定義フォームは、 ジェネリック関数の引数起因により定義メソッドを実行するためのコードを含みます。 メソッド定義フォームが評価されたとき、メソッドオブジェクトは作成され、 次の4つのアクションが取られます。

  • もし指定した名前のジェネリック関数がすでに存在しており、 さらに特定パラメーターと限定子が 新しいものと一致するメソッドオブジェクトがすでに存在していた場合は、 新しいメソッドオブジェクトが古いものと置き換えられます。 定義されたメソッドが別の特定パラメーターと限定子の場合は、 7.6.3 特定パラメーターと限定子の合致を参照。

  • もし指定した名前のジェネリック関数がすでに存在しており、 さらに特定パラメーターと限定子が 新しいものと一致するメソッドオブジェクトが存在していなかった場合は、 既存のジェネリック関数オブジェクトは、新しいメソッドオブジェクトを含むように修正されます。

  • もし指定した名前が、通常の関数か、マクロ、特別オペレーターの名前であった場合は、 エラーが発せられます。

  • それ以外の場合は、メソッド定義フォームによって定義されたメソッドとともに、 ジェネリック関数が作成されます。

もし新しいメソッドのラムダリストがジェネリック関数のラムダリストに合致していない場合は、 エラーが発せられます。 もしジェネリック関数オプションを指定できないメソッド定義オペレーターが 新しいジェネリック関数を作成する場合は、 ジェネリック関数のラムダリストは、メソッドのものと合致するように、 メソッド定義フォームから生成されるメソッドのラムダリストから導出されます。 合致の議論については、7.6.4 ジェネリック関数の全てのメソッドのラムダリストの合意を参照。

各メソッドは特定されたラムダリストを持っており、それはメソッドが適用されたときに決定します。 特定されたラムダリストは、通常のラムダリストに似ていますが、 要求パラメータの名前の代わりに特定パラメーターとなるのが違っています。 特定パラメーターは(変数名 特定パラメーター名)のリストであり、 特定パラメーター名は次のうちの1つを取ります。

  • シンボル:特定パラメーターはシンボルによるクラス名です。

  • クラス:特定パラメーターはクラス自身です。

  • (eql form):特定パラメーターは型特定子(eql object)を満たし、 objectformを評価した結果となります。 formフォームはメソッド定義フォームが評価された中でのレキシカル環境内によって評価されます。 注意として、formはメソッドが定義されたときに、ただ一度だけ評価されます。 ジェネリック関数が呼び出されるたびに評価されるのではありません。

特定パラメーター名は、ユーザーレベルのインターフェースである defmethodのようなマクロで使用されることを意図しており、 特定パラメーターは関数のインターフェース上で使われます。

要求パラメーターのみを特定化することができ、 各要求パラメーターは特定パラメーターが存在しなければなりません。 表記を単純にするために、もしメソッド定義フォームの特定化されたラムダリスト内において、 要求パラメーターが単純に変数名だけであった場合は、 その要求パラメーターはデフォルトのクラスtが指定されます。

ジェネリック関数に引数の集合が与えられたとき、 適用するメソッドは、特定パラメーターが対応する引数によって満たされるジェネリック関数のメソッドです。 次の定義は、メソッドが適用可能かどうか、また引数が特定パラメーターを満たすかどうかとは、 どういう意味であるのかを示します。

<A1, ..., An>ジェネリック関数の要求パラメーターの順番であるとします。 <P1, ..., Pn>がメソッドMにおける要求パラメーターに対応する特定パラメーターの順番であるとします。 各Aiの型が、型特定子Piによって特定されるとき、メソッドMは適用可能です。 全ての有効な特定パラメータは有効な型指定子でもあるため、 関数typepは、メソッドの選択において、 引数が特定パラメーターを満たすかどうかを決定するために使用できます。

全ての特定パラメーターがクラスtのメソッドは、デフォルトメソッドと呼ばれます。 これは常に適用可能ですが、他のもっと特定的なメソッドによってシャドウされるかもしれません。

メソッドは限定子を持てます。 これはmethod-combinationがメソッドを区別するための方法としての手順を与えます。 1つか複数の限定子を持っているメソッドは、限定されたメソッドと呼ばれます。 限定子を持っていないメソッドは、限定されていないメソッドと呼ばれます。 限定子はリスト以外のオブジェクトです。 標準のmethod-combinationによって定義された限定子の型はシンボルです。

この定義の中で、「プライマリメソッド」と「補助メソッド」という語は、 これらを使用するmethod-combinationタイプにおいて、メソッドを区分けするために使用されます。 method-combinationのstandardでは、プライマリメソッドは限定されていないメソッドであり、 補助メソッドは単一の限定子である:around, :before, :afterのうちの1つを指定したメソッドです。 これらのメソッドは、順にaroundメソッド、beforeメソッド、afterメソッドと呼びます。 method-combinationタイプがdefine-method-combinationの 短いフォームを使用して定義されたとき、 プライマリメソッドはmethod-combinationタイプの名前を限定子に与えたメソッドになります。 そして補助メソッドは:aroundの限定子です。 このように「プライマリメソッド」と「補助メソッド」という語は、 method-combinationタイプにおける、ただの相対的な定義となります。

7.6.3 特定パラメーターと限定子の合致

2つのメソッドが特定パラメーターと限定子それぞれにおいて 互いに合致したと言えるのは、次に示す状況が当てはまる場合です。

  • 両方のメソッドが同じ数の要求パラメーターを持っているとき。 例えば、2つのメソッドの特定パラメーターが、P1,1 ... P1,nP2,1 ... P2,nであったとき。

  • 1≦i≦nの各P1,iP2,iに一致したとき。 特定パラメーターP1,iP2,iに一致したとは、P1,iP2,iが同じクラスであるか、 あるいはP1,i=(eql object1), P2,i=(eql object2), (eql object1 object2)のとき。 それ以外では、P1,iP2,iは一致していません。

  • 2つの限定子のリストが、equalで等しいとき。

7.6.4 ジェネリック関数の全てのメソッドのラムダリストの合意

下記に示すこれらの定義は、ラムダリストの集合の合意を定義します。 ラムダリストには、指定したジェネリック関数の各メソッドのラムダリストと、 ジェネリック関数自身で定義されたラムダリストを含みます。

  1. 各ラムダリストは、同じ数の要求パラメーターを持つ必要があります。

  2. 各ラムダリストは、同じ数の&optionalパラメーターを持つ必要があります。 各メソッドは、&optionalパラメーターに独自のデフォルト値を提供することができます。

  3. もしどれかのラムダリストが&rest&keyを持つなら、 各ラムダリストはそのうちの1つか両方を指定する必要があります。

  4. もしジェネリック関数のラムダリストが&keyを持つなら、 各メソッドは&keyの後の全てのキーワードの名前を受け付けるようにする必要があります。 受け付ける方法は、全ての名前を明に指定する方法、 あるいは&allow-other-keysを指定する方法がありますが、 &keyの指定ではなく&restを指定する方法でも問題ありません。 各メソッドは、独自のキーワード引数を追加で受け付けることができます。 キーワードの名前の有効性のチェックはジェネリック関数が行い、各メソッドでは行いません。 メソッドが実行されたときには、キーワード引数に名前が:allow-other-keys、 値がtrueであるペアが与えられたように呼び出されますが、 そのような引数のペアは渡されません。

  5. &allow-other-keysの使用は、ラムダリスト間で一貫している必要はありません。 もし&allow-other-keysジェネリック関数か適用メソッドのラムダリストに指定されている場合、 ジェネリック関数が呼び出されるときには、どんなキーワード引数も受け付けるでしょう。

  6. &auxの使用は、メソッド間で一貫している必要はありません。

もしジェネリック関数のオプションを指定できないメソッド宣言オペレーターが ジェネリック関数を作成した場合、 さらにメソッドのラムダリストにキーワードパラメーターが指定されている場合は、 ジェネリック関数のラムダリストには&keyが指定されます(しかしキーワード引数は指定されない)。

7.6.5 ジェネリック関数とメソッドのキーワード引数

ジェネリック関数かそのメソッドがラムダリストに&keyを指定しているとき、 ジェネリック関数によって受け取れるキーワード引数の集合の定義は、 適用可能なメソッドによって変化します。 ある呼び出しにおいてジェネリック関数が受け取れるキーワード引数の集合とは、 適用可能な全てのメソッドによって受け取ることができるキーワード引数と、 ジェネリック関数に&keyがあるならば&key以降に示されるキーワード引数の集合です。 &keyの指定が無く、&restの指定があるメソッドは、 受け付けるキーワード引数の集合には影響しません。 もし適用可能なメソッドのどれかのラムダリストか、 あるいはジェネリック関数の宣言によるラムダリストが&allow-other-keysを含んでいる場合は、 そのジェネリック関数は全てのキーワード引数を受け取ります。

ラムダリスト一致の規則は、 各メソッドが次のような全てのキーワード引数を受け付けるようにすることを要求します。 それは、ジェネリック関数の定義において&keyの後に指定したものを明に受け取れるようにするか、 &allow-other-keysを指定するか、あるいは&keyが無い場合に&restを設定するかになります。 各メソッドは、ジェネリック関数の定義にあるキーワード引数に加えて、 独自のキーワード引数を追加で受け取るようにすることができます。

もしジェネリック関数は、渡されたキーワード引数がどの適用メソッドにも受け付けられなかった場合は、 エラーを発する必要があります。3.5 関数呼び出しのエラーチェックを参照。

7.6.5.1 ジェネリック関数とメソッドのキーワード引数の例

例えば、下記の2つのwidthメソッドが定義されていることを考えます。

(defmethod width ((c character-class) &key font) ...)

(defmethod width ((p picture-class) &key pixel-size) ...)

その他のwidth以外のメソッドとジェネリック関数は存在しないと仮定します。 下記のフォームを評価したときには、 キーワード引数:pixel-sizeが適用可能なメソッドで受け付けられないため、 エラーが発せられます。

(width (make-instance 'character-class :char #\Q)
       :font 'baskerville :pixel-size 10)

下記のフォームの評価は、エラーが発せられます。

(width (make-instance 'picture-class :glyph (glyph #\Q))
       :font 'baskerville :pixel-size 10)

下記のフォームの評価は、もしcharacter-picture-classという名前のクラスが、 picture-classcharacter-class両方のサブクラスであった場合には、 エラーにはならないでしょう。

(width (make-instance 'character-picture-class :char #\Q)
       :font 'baskerville :pixel-size 10)

7.6.6 メソッドの選択とコンビネーション

ジェネリック関数が特定の引数とともに呼び出されたとき、 実行するコードを決定しなければなりません。 このコードは、これらの引数に対する有効なメソッドと呼ばれます。 有効なメソッドは、ジェネリック関数内の適用可能なメソッドを結びつけたものであり、 このメソッドのいくつかか、あるいは全てのメソッドが呼び出されます。

もしジェネリック関数が呼び出されたときに、適用可能なメソッドが存在しなかった場合は、 ジェネリック関数no-applicable-methodが呼び出されます。 その呼び出した結果の返却値は、最初のジェネリック関数が呼び出されたものの返却値として使用されます。 no-applicable-methodの呼び出しは、キーワード引数が受付可能かどうか先行してチェックされます。 7.6.5 ジェネリック関数とメソッドのキーワード引数を参照。

もし有効なメソッドが決定されたら、 ジェネリック関数に渡されたものと同じ引数とともに呼び出されます。 どのような返却値であれ、それはジェネリック関数が返却した値として返却されます。

7.6.6.1 有効なメソッドの決定

有効なメソッドは、下記の3ステップによって決定されます。

  1. 適用可能なメソッドの選択します。

  2. 優先順位によって適用可能なメソッドをソートし、最も特定的なメソッドを最初に選択します。

  3. ソートされた適用可能なメソッドをmethod-combinationに渡して実行し、有効なメソッドを生成します。

7.6.6.1.1 適用可能なメソッドの選択

この手順は、7.6.2 メソッドの紹介で定義されています。

7.6.6.1.2 優先順位による適用可能なメソッドのソート

2つのメソッドの優先順位を比べるために、特定パラメーターが順番に調べられます。 デフォルトの調査順は左から右ですが、defgenericか、あるいは別のオペレーターによる ジェネリック関数の:argument-precedence-orderオプションによって、逆順に指定されます。

各メソッドの対応する特定パラメーターが比較されます。 特定パラメーターのペアが一致していたら、次のペアが比較されます。 もし対応する全ての特定パラメーターが一致していたのであれば、 2つのメソッドは違った限定子を持っている必要があります。 このケースの場合、メソッドはどちらの順番でも選択できます。 一致についての詳細は、7.6.3 特定パラメーターと限定子の合致を参照。

もしいくつかの特定パラメーターが一致していなかった場合、 最初に一致しなかった特定パラメーターのペアが優先順位を決定します。 もしどちらの特定パラメーターもクラスであったとき、 その2つのメソッドの対応する特定パラメーターを見て、 クラス優先リストの中に早く現われた方がより特定的なメソッドとなります。 適用可能なメソッドの集合から選択する方法を行っているため、 特定パラメーターは引数のクラスのクラス優先リストに存在することが保証されます。

もし対応する特定パラメーターのペアのうち、ちょうど1つが(eql object)であったときは、 その特定パラメーターを持つメソッドが、他のメソッドより優先します。 もし両方の特定パラメーターがeql形式であったときは、 特定は一致するとしなければなりません (そうでなければ2つのメソッドはこの引数において両方とも適用できなかったでしょう)。

適用可能なメソッドリストの結果は、最も特定的なメソッドが最初であり、 最も特定的ではないメソッドが最後になります。

7.6.6.1.3 ソートされた適用可能なメソッドのMethod-Combination実行

単純な場合として、method-combinationはstandardが使われており、 全ての適用可能なメソッドはプライマリメソッドであるとします。 この場合は、有効メソッドは最も特定的なメソッドとなります。 メソッドは、次に特定的なメソッドを関数call-next-methodの使用にて呼び出すことができます。 call-next-methodによって呼び出されるメソッドは、次のメソッドと言います。 関数next-method-pは、次のメソッドが存在するかどうかをテストします。 もしcall-next-methodが呼ばれたものの、次の特定的なメソッドが存在しなかった場合は、 ジェネリック関数no-next-methodが呼び出されます。

一般的に、有効なメソッドは、適用可能なメソッドを組み合わせた結果のいくつかとなります。 これは次に記載されたような目的によりフォームとして定義されます。 適用可能なメソッドは、いくつかが呼ばれるか、あるいは全部が呼ばれるかを定義します。 また、返却値は1つか複数が返却されるように定義します。 その返却値はジェネリック関数として返却されるものです。 付加的にはいくつかのメソッドがcall-next-methodを用いてアクセス可能になるように定義します。

有効なメソッドにおける各メソッドの役割は、 メソッドの限定子と特定子によって決定されます。 限定子はメソッドに印をつけるものであり、 限定子の意味は手続きにおいて印を用いることで決定されます。 もし適用可能なメソッドが認識できない限定子を持っていた場合はエラーを発し、 有効なメソッドの中にこのメソッドが存在しないものとします。

method-combinationのstandardが限定されたメソッドと一緒に使われたときは、 有効なメソッドは7.6.6.2 Standard Method-Combinationに記載されたものとして生成されます。

他のタイプのmethod-combinationは、defgenericかあるいは別のオペレーターで ジェネリック関数のオプション:method-combinationを使うことで使用できます。 この方法により、手順をカスタマイズできます。

新しいタイプのmethod-combinationは、 define-method-combinationマクロを使うことによって定義することができます。

7.6.6.2 Standard Method-Combination

method-combinationのstandardは、standard-generic-functionクラスによって提供されます。 これはmethod-combinationのタイプが指定されなかった場合か、 あるいは組み込みのmethod-combinationタイプであるstandardが指定された場合に使われます。

プライマリメソッドは有効なメソッドのメインとなる動作として定義されます。 補助メソッドは3つあるうちの1つの方法を用いて動作を変更します。 プライマリメソッドは限定子を持ちません。

補助メソッドは、限定子:before, :after, そして:aroundのメソッドです。 method-combinationのstandardは、メソッドに対して2つ以上の限定子を許容しません。 もしメソッド定義で複数の限定子をもつメソッドを定義した場合は、エラーが発せられます。

  • beforeメソッドは、ただひとつの限定子として:beforeキーワードを持ちます。 beforeメソッドは、プライマリメソッドの前に実行されるコードを定義します。

  • afterメソッドは、ただひとつの限定子として:afterキーワードを持ちます。 afterメソッドは、プライマリメソッドの後に実行されるコードを定義します。

  • aroundメソッドは、ただひとつの限定子として:aroundキーワードを持ちます。 aroundメソッドは、他の適用可能なメソッドの代わりとして実行されますが、 いくつかのシャドウされたメソッドを(call-next-method経由で)呼び出すコードを、 明に含むことができます。

method-combinationのstandardの意味を次に示します。

  • もしaroundメソッドが存在する場合は、最も特定的なaroundメソッドが呼ばれます。 これはジェネリック関数に対して1つか複数の返却値を提供します。

  • aroundメソッドのコード内では、次のメソッドを呼ぶためのcall-next-methodが使用できます。 次のメソッドから戻ったとき、aroundメソッドは返却された値に基づいて、 さらにコードを実行することができます。 もしcall-next-methodを使用したときに呼び出せる適用可能なメソッドが存在しなかった場合は、 ジェネリック関数no-next-methodが呼び出されます。 関数next-method-pは、次のメソッドが存在するかどうかを決定するために使われます。

  • もしaroundメソッドがcall-next-methodを実行したとき、 次の特定的なaroundメソッドが適用可能であれば呼び出されます。 もしaroundメソッドが存在しないか、 あるいは最も特定的ではないaroundメソッドによってcall-next-methodが呼び出された場合は、 次に示すものとして他のメソッドが呼び出されます。

    • 全てのbeforeメソッドがmost-specific-firstの順番で呼ばれます。 これらの返却値は無視されます。 もしbeforeメソッド内でcall-next-methodが使用された場合は、エラーが発せられます。

    • 最も特定的なプライマリメソッドが呼び出されます。 プライマリメソッドのコード内では、 次の特定的なプライマリメソッドを呼び出すためのcall-next-methodが使用できます。 メソッドから戻ったとき、以前のプライマリメソッドは返却された値に基づいて、 さらにコードを実行することができます。 もしcall-next-methodを使用したときに、呼び出せる適用可能なメソッドが存在しなかった場合は、 ジェネリック関数no-next-methodが呼び出されます。 関数next-method-pは、次のメソッドが存在するかどうかを決定するために使われます。 もしcall-next-methodが使われなかった場合は、最も特定的なプライマリメソッドだけが呼び出されます。

    • 全てのafterメソッドがmost-specific-lastの順番で呼ばれます。 これらの返却値は無視されます。 もしafterメソッド内でcall-next-methodが使用された場合は、エラーが発せられます。

  • もしaroundメソッドが呼び出されなかった場合は、 最も特定的なプライマリメソッドが、 1つか複数の値をジェネリック関数の返却値として返却します。 最も特定的ではないaroundメソッドから call-next-methodの呼び出しによって返却される1つか複数の返却値は、 最も特定的なプライマリメソッドの返却値となります。

method-combinationのstandardでは、適用可能なメソッドが存在しても、 適用可能なプライマリメソッドが存在しなかった場合はエラーが発せられます。

beforeメソッドはmost-specific-first順にて実行され、 afterメソッドはleast-specific-first順に実行されます。 この設計の違いの根拠を、例として次のように示します。 クラスC1スーパークラスであるC2の動作を、 beforeメソッドとafterメソッドに追加することで変更することを考えます。 クラスC2の振る舞いがC2のメソッドとして直接定義するか、 あるいはスーパクラスを継承によるものかに関わらず、 クラスC1インスタンスによって呼び出されるメソッドの相対的な順番には影響しません。 クラスC1beforeメソッドは、クラスC2の全てのメソッドの前に実行されます。 クラスC1afterメソッドは、クラスC2の全てのメソッドの後に実行されます。

対称的に、全てのaroundメソッドが実行されるのは、他のメソッドが実行される前です。 このように最も遠いaroundメソッドは、最も特定的なプライマリメソッドの前に実行されます。

もしプライマリメソッドのみが宣言されており、 さらにcall-next-methodが使用されなかった場合は、 最も特定的なメソッドのみが実行されます。 つまり最も特定的なメソッドが他の一般的なメソッドをシャドウしたということです。

7.6.6.3 Method-Combinationの宣言

マクロdefine-method-combinationは、method-combinationの新しいフォームを定義します。 これは、有効なメソッドの生成をカスタマイズする仕組みを提供します。 標準の有効なメソッドの生成手順は、7.6.6.1 有効なメソッドの決定に記載されています。 define-method-combinationには、2つのフォームが存在します。 短いフォームは単純な宣言方法であり、長いフォームはもっと強力で冗長です。 長いフォームはdefmacroと似ています。 コード本体は式であり、Lispフォームを計算します。 これはmethod-combination内の構造を任意に制御する仕組みと、 メソッドの限定子を任意に扱う仕組みを提供します。

7.6.6.4 組み込みのMethod-Combination

オブジェクトシステムは、組み込みのmethod-combinationタイプをいくつか提供しています。 これらのmethod-combinationタイプのうちの1つを、ジェネリック関数で使うことができます。 指定する方法は、method-combinationタイプの名前を、 defgeneric:method-combinationオプションに引数として与えるか、 その他のオペレーターでジェネリック関数のオプション:method-combinationを指定することです。

組み込みのmethod-combinationタイプの名前を、次の表に示します。

+
and
append
list
max
min
nconc
or
progn
standard

表7-2 組み込みのMethod-Combinationタイプ

組み込みのmethod-combinationタイプであるstandardの意味は、 7.6.6.2 Standard Method-Combinationに記載しました。 他の組み込みmethod-combinationタイプは、 シンプルな組み込みmethod-combinationタイプと呼ばれています。

シンプルなmethod-combinationタイプは、 短い形式のdefine-method-combinationによって定義されたかのように動作します。 メソッドは次の2つの役割を認識します。

  • aroundメソッドは、ただひとつの限定子として:aroundキーワードを持ちます。 aroundメソッドの意味はmethod-combinationのstandardと同じです。 aroundメソッド内ではcall-next-methodnext-method-pの関数の使用が提供されます。

  • プライマリメソッドは、ただひとつの限定子としてmethod-combinationの名前を持ちます。 例えば、組み込みのmethod-combinationタイプのandは、 ただひとつの限定子であるandを指定したメソッドをプライマリメソッドとして認識します。 call-next-methodnext-methodの関数はプライマリメソッドでは提供されません。

シンプルな組み込みmethod-combinationタイプの意味を下記に示します。

  • もしaroundメソッドが存在するならば、最も特定的なaroundメソッドが呼ばれます。 これはジェネリック関数へ1つか複数の返却値を提供します。

  • aroundメソッドのコード内では、次のメソッドを呼ぶためのcall-next-methodが使用できます。 もしcall-next-methodを使用したときに、呼び出せる適用可能なメソッドが存在しなかった場合は、 ジェネリック関数no-next-methodが呼び出されます。 関数next-method-pは、次のメソッドが存在するかどうかを決定するために使われます。 次のメソッドから戻ったとき、aroundメソッドは返却された値に基づいて、 さらにコードを実行することができます。

  • もしaroundメソッドがcall-next-methodを実行したとき、 次の特定的なaroundメソッドが適用可能であれば呼び出されます。 もしaroundメソッドが存在しないか、 あるいは最も遠いaroundメソッドによってcall-next-methodが呼び出されたときは、 組み込みmethod-combinationタイプの名前と適用可能なプライマリメソッドのリストから、 評価されるとジェネリック関数の返却値を生成する様なLispフォームが導出されます。 例えば、method-combinationタイプの名前がoperatorのとき、 ジェネリック関数が次のフォームによって呼び出されることを考えます。

(generic-function a1...an)

ここでM1, ... Mkはこの順に適用可能なプライマリリストであるとします。 そのとき、Lispフォームは次のように導出されます。

(operator <M1 a1...an> ... <Mk a1...an>)

もし式<Mi a1...an>が評価されたとき、メソッドMiは引数a1...anを適用します。 例えばoperatororのとき、式<Mi a1...an>は、 ただ<Mj a1...an>, 1≦j<inilを返却したときのみに評価されます。

プライマリメソッドのデフォルトの順番は:most-specific-firstです。 しかし、:method-combinationオプションの2番目の引数に :most-specific-lastを指定したときは逆順にすることができます。

シンプルな組み込みmethod-combinationタイプは、 メソッドに対して正確に1つの限定子を要求します。 もし限定子が存在しない適用可能なメソッドか、 あるいはmethod-combinationタイプが認識しない限定子を指定したときはエラーが発せられます。 もしaroundメソッドが存在するものの、プライマリメソッドが存在しない場合は、エラーが発せられます。

7.6.7 メソッドの継承

サブクラスは、クラスの全てのインスタンスのどんな適用可能なメソッドも継承します。 また、クラスのどんなサブクラス全てのインスタンスにも適用可能です。

メソッドの継承は、メソッド定義オペレーターによって作成されたメソッドであっても、 同じ方法によって行われます。

メソッドの継承の詳細は、7.6.6 メソッドの選択とコンビネーションに記載されています。