#:g1: メソッドコンビネーションってなに?

Posted 2018-12-01 09:50:35 GMT

Lisp メソッドコンビネーション Advent Calendar 2018 1日目 》

Lisp メソッドコンビネーション Advent Calendar 2018始まりました。
開始初日にして既につらい。
setfネタともう一品と思ってテーマにメソッドコンビネーションを選んでみましたが、setf以上にニッチでした。

メソッドコンビネーションってなに?

さて、メソッドコンビネーションについての説明ですが、読んでそのまま、メソッドのコンビネーションのことです。
どういう面の組み合わせかというと、ざっくりメソッドの起動とその順番についてと考えて良いでしょう。

(defclass k0 () ())
(defclass k1 (k0) ())
(defclass k2 (k1) ())

(defmethod m ((obj k2)) 'k2) (defmethod m ((obj k1)) 'k1) (defmethod m ((obj k0)) 'k0)

こんな感じの定義があったとすると、

Common Lispでは、compute-applicable-methodsで起動するメソッドを確認することが可能です。

(compute-applicable-methods #'m (list (make-instance 'k2)))

(#<standard-method m nil (k2) 40E011D08B> #<standard-method m nil (k1) 40E004A153> #<standard-method m nil (k0) 40E006B64B>)

上記の定義では、k0 < k1 < k2 という継承順のクラスに対し、それぞれmを定義しています。

k2についてmを起動してみると、

(m (make-instance 'k2))
→ k2 

となりますが、Common Lispの標準では、compute-applicable-methodsで求めたリストのメソッドが呼ばれていきます。
この例では他2つのメソッドは、呼ばれませんが、明示的な操作と相対的な名前で呼ぶことも可能です。

3つのメソッドのコンビネーションについて説明しましたが、Common Lispでは上記はstandardという名前が付いています。

なんとなく想像が付くかと思いますが、Common Lispでは任意のメソッドの束を任意の構成で呼んだり呼ばなかったりが可能です。

Advice機構とメソッドコンビネーション

メソッドコンビネーションというとbeforeafter等の修飾子を付けてフックを掛ける使い方の印象が強いと思います。

具体的な例でいうと、上記のコードにbeforeafterを追加定義してmを起動すると、

(defmethod m :before ((obj k2)) (print "before k2"))
(defmethod m :after ((obj k2)) (print "after k2"))

(m (make-instance 'k2))
▻ "before k2"
→ k2
▻ "after k2" 

となります。

さて、compute-applicable-methodsで確認してみると、メソッドが増えているのが分かります。

(compute-applicable-methods #'m (list (make-instance 'k2)))(#<standard-method m (:after) (k2) 40E0868EF3>
    #<standard-method m (:before) (k2) 40E086956B>
    #<standard-method m nil (k2) 40E011D08B>
    #<standard-method m nil (k1) 40E004A153>
    #<standard-method m nil (k0) 40E006B64B>)

起動のされ方については、Common LispのMOPで定義されているcompute-effective-methodで起動コードを確認することが可能です。

(c2mop:compute-effective-method #'m
                                (c2mop:find-method-combination #'m 'standard nil)
                                (compute-applicable-methods #'m (list (make-instance 'k2))))(progn
    (call-method #<standard-method m (:before) (k2) 40204EB4DB> nil)
    (multiple-value-prog1
        (call-method #<standard-method m nil (k2) 40E011D08B>
                     (#<standard-method m nil (k1) 40E004A153>
                      #<standard-method m nil (k0) 40E006B64B>))
      (call-method #<standard-method m (:after) (k2) 40204E84E3> nil))) 

上記はLispWorksの例ですが、起動するコードを直接確認できるので、何か良く分からなくなってきたら実際にどんなものが生成されるか確認してみるのも良いでしょう。

このように生成されたコードを眺めてみると、メソッドコンビネーションのコンビネーションの一つとして、フック/Advice機構が実現されていることが分かります。

本体メソッドの前後に副メソッドを配置するコンビネーションは、GoFデザインパターンでもObserverパターンとして良く知られていますが、古えのLisp畑(Flavors等)では、フレーム理論の影響からか、配置されるメソッドをdaemonと呼び、この標準パターンもdaemonと呼んでいます(ややこしい)
伝統を受け継いでいるCommon Lispでもこのコンビネーションが標準となっていて、standardという名前が付いている、という訳です。

フレームワークの代表的な使われ方と、フレームワークそのものが混同されるのは常ですが、メソッドコンビネーションもフックとしての使われ方だけではないことに留意する必要はあるでしょう。

今後は、メソッドコンビネーションの歴史、Common Lispや、Lisp Machine Lisp等でのメソッドコンビネーションの定義方法の解説etcを書いていく予定です(一体書けるのか)


HTML generated by 3bmd in LispWorks 7.0.0

comments powered by Disqus