#:g1: MOPでメソッドコンビネーションの仕組みを実装してみよう

Posted 2018-12-23 10:44:01 GMT

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

先日、Common Lispのメソッドコンビネーションの実現具合とAMOPでの実現に結構な差異があると書きましたが、今回は、define-method-combinationを使わないでMOP中心でメソッドコンビネーションを定義するとどんな感じになるか、を確かめてみたいと思います。

とりあえず、Common LispとAMOP近辺のメソッドコンビネーションを実現方式を調べてみると5つ位バリエーションがみつかりました。

まず、X3J13-88-003R Metaobject Protocolは草稿どまりですが、AMOPの流儀をベースにしつつも最適化についても考慮していたようなので複雑になっています。
また、define-method-combinationというFlavorsの流れも、MOP上に統合しようとしていたようですがこれも未完です。

次に、ANSI CL規格は、X3J13-88-003Rで練られていた事項を踏まえつつ、MOPの詳細については踏み込まない、という感じになっています。

AMOPの本文、Closetteの実装、は一つの理想形で、ANSI CL規格との兼ね合いのような瑣末なことは考慮されていません(先行しているので当然)

また、各処理系の実装は、概観するとAMOPに準拠しようとしつつ、参照実装の挙動が仕様だ、という感じになっています。
しかし参照実装とはいえ、Portable CommonLoopsの実装が仕様に準拠していない所もあり、その辺りのあやふやさは現在にも受け継がれています。

Closer to MOPは、AMOPの内容を現在メジャーなANSI Common Lisp処理系の上にポータブルな仕様と実装を実現しようとしたものです。

メソッドコンビネーションの枠組み作成

とりあえず、MOP的にメソッドコンビネーションの枠組みを作成してみましょう。

仕様は色々ありますが、compute-effective-methodがメソッドの集合を指定されたメソッドコンビネーションに応じでフォームを組み立てる、というのは共通しています。

なお、以下では、Closer to MOPを利用します。

(ql:quickload :closer-mop)

総称関数の定義

標準では、define-method-combination経由でしかメソッドコンビネーションを定義できないので、総称関数ごと別に作成します。

(defclass mcacgf (standard-generic-function)
  ()
  (:metaclass funcallable-standard-class))

メソッドコンビネーションクラスの定義

ANSI CLには、method-combinationクラスが定義されているのですが、使い方やスロットの詳細は曖昧なままです。
X3J13-88-003Rでは、サブクラスにstandard-method-combinationstandard-simple-method-combinationを定義しているようです。
この流儀に沿っているLispWorksのような実装もある様子。

とりあえずは標準のもののサブクラスにします。

(defclass ac-method-combination (method-combination) ())

メソッドコンビネーションの配置生成定義

effective-methodというとメソッドオブジェクト(メタオブジェクト)っぽいのですが、どうも式(メタプログラム)を指しているようです。

compute-effective-methodが式を生成するので、ここで直接書き下してしまえばOKです。

下記では、単純にするために:aroundなしのstandardにしています。
(Flavorsでいうdaemon)。

define-method-combination構文の便利機能を使わず手書きしている、という感じですね。

(defmethod c2mop:compute-effective-method ((gf mcacgf)
                                           (mc method-combination)
                                           (methods list))
  (loop :for m :in methods
        :for mq := (method-qualifiers m)
        :when (equal '(:before) mq) :collect `(call-method ,m) :into bs
        :when (equal nil mq) :collect m :into ps
        :when (equal '(:after) mq) :collect `(call-method ,m) :into as
        :finally (return `(multiple-value-prog1 
                              (progn (progn :before-daemons ,@bs)
                                :primaries
                                (call-method ,(car ps) (,@(cdr ps))))
                            (progn :after-daemons ,@as)))))

メソッドコンビネーション名の登録

defgeneric:method-combinationオプションで指定できるように登録します。
しかし、find-method-combinationが実装されていない処理系もあるので、フックできないかもしれません(LispWorks等)

(defmethod c2mop:find-method-combination 
           ((gf mcacgf) (type (eql 'mcac)) opts)
  (make-instance 'ac-method-combination))

;;; LispWorksではしょうがないのでテーブルに直に登録 (setf (gethash 'mcac clos::*method-combination-types*) (make-instance 'ac-method-combination))

動かしてみる

(defgeneric foo (x)
  (:generic-function-class mcacgf)
  (:method-combination mcac))

(defmethod foo (x) x) (defmethod foo :after (x) (print (list :after x))) (defmethod foo :before (x) (print (list :before x)))

(foo 42)(:before 42)(:after 42) → 42

(c2mop:compute-effective-method #'foo
                                (make-instance 'ac-method-combination)
                                (compute-applicable-methods #'foo (list t)))(multiple-value-prog1
      (progn
        (progn
          :before-daemons
          (call-method #<standard-method foo (:before) (t) 40E0460B83>))
        :primaries
        (call-method #<standard-method foo nil (t) 40E042A5EB> nil))
    (progn
      :after-daemons
      (call-method #<standard-method foo (:after) (t) 40E0429F9B>)))

困った所

define-method-combinationオプションの:argumentsのサポートが鬼門なのですが、

  1. まともに実装している処理系がないっぽい
  2. MOPでどうするか文献がない

等で、MOPで:argumentsをサポートする場合、どういうAPI構成で書くべきなのか良く分かりません。
ANSI CL規格では、effective method中に展開されるとありますが、このあたりも処理系によって挙動がまちまちのようです。

まとめ

とりあえず、define-method-combinationを経由しないでメソッドコンビネーションを定義してみました。
define-method-combinationのマイナーな機能/オプションについてはCommon Lispの仕様が中途半端なせいか、どうやら忠実に実装できているは処理系がなさそうです。

SICL等は綺麗なCommon Lispを目指しているようなので、今後はSICLあたりも参照してみようかなと思います。


HTML generated by 3bmd in LispWorks 7.0.0

comments powered by Disqus