Hatena::ブログ(Diary)

Common Lisp クックブック

Common Lisp クックブックをベースにしてます。

2007-12-12

[] 4.6. 修飾子とメソッド結合

はじめに一言。たいした思案もなしにメソッド結合を使うのは、手放しで多重継承を使うようなもので、あっと言う間にコードを把握できなくなります。

defmethod の完全な文法を次に示します。

  • defmethod function-name {method-qualifier}* specialized-lambda-list declaration* | documentation? form*

ここでは defmethod をいつも通りに使うか、標準メソッド結合 (standard method combination)の定義に使うかだけを見ていきます(その他のメソッド結合もありますし、自分で新しいメソッド結合を定義することもできますが、そこまでした人を見たことはありません)。標準メソッド結合では、次のキーワードのうち一つだけ指定できます::before, :after, around 。複数のメソッド修飾子を指定することはできません。修飾子のないメソッドは基本メソッド(primary method)と呼ばれます。総称関数のディスパッチの完全な手順を次に示します。:before:after メソッドは副作用としてのみ実行されます。

  1. 適用可能なメソッドを調べ、メソッド修飾子に従ってメソッドのリストを作ります。
  2. 適用可能な基本メソッドがなければエラーを発します。
  3. 引数定子の順序に従って、各リストを並び替えます。
  4. 最も特定的な(引数の値が最もマッチする引数定子を持つ) :around メソッドがあればそれを実行し、その戻り値を総称関数戻り値として返します。基本メソッドは実行されません
  5. :around メソッド内で call-next-method を呼ぶと、次点に特定的な :around メソッドが実行されます。
  6. :around メソッドが一つもないか、:around メソッドで call-next-method を呼んでも次点に特定的な :around メソッドがない場合、次の手順で処理が進められます。
    1. 特定的な順序で並び替えた、すべての :before メソッドを起動します。戻り値は無視されます。call-next-methodnext-method-p を呼ぶことはできません(呼ぶとエラーを発します)。
    2. 最も特定的な基本メソッドを実行し、その戻り値を総称関数の値とします。
    3. 基本メソッドが call-next-method を呼ぶと、次点に特定的な基本メソッドが実行されます。
    4. 基本メソッドが call-next-method を呼んでも次点に特定的な基本メソッドがない場合、エラーを発します。
    5. 基本メソッドの処理が終わったら、特定的でない順序で並び替えた、すべての :after メソッドを実行します。戻り値は無視され、call-next-methodnext-method-p を呼ぶことはできません。

複雑過ぎて頭がおかしくなると思った方、もっともです。一連の処理がたまねぎだとすると、最も外側の層に :around メソッドがあり、:before:after メソッドが中間の層にあり、基本メソッドが中心にあります。たったの三層で済んでいることに感謝しましょう。この仕様を理解するのに、:before:after メソッドを組み合わせた次の擬似コードが役に立つと思います(※Lispコードとして評価できないので注意。余計わかりにくいと思うけど…)。

; :before, :after メソッドを自主的に呼び出す spong メソッド
(defmethod spong :before-and-after (&rest args)
  (let ((before (find-method #'spong '(:before) args))
        (after  (find-method #'spong '(:after) args)))
    (when before (invoke-method before args))
    (multiple-value-prog1
        (call-next-before-and-after-method)
      (when after (invoke-method after args)))))

:after メソッドは特定的でない順序で呼ばれますが、実行中にどうやってその順序を知るのか考えてみてください。実はまったく簡単なことで、最初に呼ばれたメソッドが最も特定的だということを考えればわかります。*1

実際には、それほど複雑でもありません。次に my-describe戻り値を隠蔽する簡単な例を示します。

CL-USER 76 > (defmethod my-describe :around (self)
               (call-next-method)
               (values))
#<STANDARD-METHOD MY-DESCRIBE (:AROUND) (T) 20605A34>
 
; 戻り値がない(以前は NIL )
CL-USER 77 > (my-describe Eric)
Eric? Is that you?
 
CL-USER 78 >

もう一つ例を挙げます。CLOSmake-instance 関数は、二段階の処理を行います。新しいオブジェクトを生成し、make-instance のすべてのキーワード引数initialize-instance 総称関数に渡します。(処理系の)実装者やアプリケーション開発者は、インスタンスのスロットを初期化するための :after メソッドを定義することができます。システムが提供する基本メソッドは、(a) クラス定義で指定された :initform:initarg 、(b) make-instance から渡されたキーワード引数に従ってスロットを初期化します。他のメソッドでこの振る舞いを拡張することもできます。例えば、スロット値を求めるためにデータベースにアクセスするようなキーワードを追加するなどです。initialize-instance 総称関数が受け取るラムダリストを次に示します。

  • initialize-instance instance &rest initargs &key &allow-other-keys

練習問題

  • initialize-instance:after メソッドを追加し、すべての aardvarkイギリスケンブリッジ(Cambridge, England)から来たことにします。もう一つ、次の関係が成り立たなくなるようなメソッド(どんな修飾子をつけますか?)を追加しなさい。
(make-instance 'cannibal :diet (make-instance 'cannibal))
  • initialize-instance は、他のオブジェクトシステムのコンストラクタを強化したものと見ることもできます。ただし、CLOSにはデストラクタがありません。デストラクタがないのは問題ですか?

*1:実行したメソッドの :after メソッドを逆順に並び替えれば、特定的でない順に :after メソッドが並びます。

ふぇじょーーーあwwwwふぇじょーーーあwwww 2009/06/14 20:41
これヤった後でパチ屋に行ったら勝率上がりすぎwwwwww

http://shiofuki.navi-y.net/4jZ3o8C/

ただの軍資金稼ぎのつもりでヤってたんだけど、
パチも負けねーもんだから金が余りまくりっす・・(^^;
まー金は余っても困らないからまだ続けるけどねーヽ( ・∀・)ノ
とりあえずBMWでも買うわwwwwwww

しゅおおぁあああ!!!!!しゅおおぁあああ!!!!! 2009/06/22 06:41
あっもう!ちょ!!!凄い!!!凄いよ!!!!!
あぁぁテンション上がりすぎて何から言えばいいかわかんねwww
勃.起おさまんねーし今からもっかい行ってくるwwwwwww

http://ahan.yumenokuni.net/nTWo9Ho/

ぎょはぁ!!!!!ぎょはぁ!!!!! 2009/08/16 08:57
ヘイヘイ!!あひひひほはぁwwwwwww ちょwwいきなりごめwwwwww
寝てるだけで5 万もらっちゃって真面目な自分がヴァカらしくなってさwwwww
はぁーいま女シャワー浴びてんだけど、もう1ラウンドでまた5 万くれるってYO!wwwwww
またマグロでさっさと中 出 しするわwwwwwwwww

http://kachi.strowcrue.net/YEYS6SO/