#:g1: MOP vs マクロ (8): メソッド定義でselfを使って楽をしたい

Posted 2019-03-31 19:03:15 GMT

今回のMOP vs マクロは、defmethodのカスタマイズネタで比較してみたいと思います。

メソッド定義でselfを使いたい

Common Lispはマルチメソッドなので、シングルメソッドの言語のようなselfはありませんが、なんとなく気分で、

(defmethod foo ((self bar) x y) ...)

のように書いたりすることもあります。

このようにディスパッチは先頭一つでしか行なわない場合に、defmethod内部でselfも使えたら便利なんじゃないか、ということで、そのようなカスタマイズをしてみたいと思います。

マクロ篇

まずはマクロでの実現。とりあえず、安直に下記のように書いてみました。

(ql:quickload :closer-mop)

(defpackage "9ef5d5fa-900d-5269-8012-a9c0d39a1860" (:use :c2cl))

(in-package "9ef5d5fa-900d-5269-8012-a9c0d39a1860")

(defmacro defgeneric-self (name (&rest args) &body body) (destructuring-bind (class name) name `(defgeneric ,name (,class ,@args) ,@body)))

(defmacro defmethod-self (name (&rest args) &body body) (destructuring-bind (class name) name `(defmethod ,name ((self ,class) ,@args) (with-slots ,(mapcar #'slot-definition-name (class-slots (find-class class))) self ,@body))))

selfとなるインスタンスのクラスをどう指定するかですが、Flavors風に(defmethod (class name) ()...)としてみています。

また、defmethod内部では、selfとインスタンスのスロットがスロット名の変数でアクセスできるようにしたいので、with-slotsでボディを囲んでいます。

なお、マクロで実現といってもスロット名を取得したりする必要があるので、MOPを使う必要はあります。

試してみる

(defconstant <foo>
  (defclass foo () 
    ((a :initform nil)
     (b :initform nil)
     (c :initform nil))))

(finalize-inheritance <foo>)

(defmethod-self (foo frob) (a b c) (list self a b c))

(frob (make-instance <foo>) 0 1 2)(#<foo 4020290013> nil nil nil)

クラスのスロット名とメソッドの引数名が被った時にはクラスのスロットに遮蔽されてしまいますが、Flavorsもこんな動作なので良しとします。

MOP篇

(ql:quickload :closer-mop)

(defpackage "a16a6b7f-083d-52aa-a466-d22b941a23c8" (:use :c2cl))

(in-package "a16a6b7f-083d-52aa-a466-d22b941a23c8")

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

(defmethod make-method-lambda ((gf self-generic-function) (method standard-method) λxp env) (destructuring-bind (lambda (self &rest args) &body body) λxp (call-next-method gf method (let ((slot-names (mapcar #'slot-definition-name (class-slots (find-class self))))) `(,lambda (,self ,@args) (let ((self ,self)) (declare (ignorable self)) (with-slots ,slot-names self (declare (ignorable ,@slot-names)) ,@body)))) env)))

メソッドのボディのコードをカスタマイズするには、make-method-lambdaが返す、lambda式を編集することになるようです。
ボディのコードをいじる用途には、ちょっと面倒なインターフェイスという印象。

試してみる

(defconstant <foo>
  (defclass foo () 
    ((a :initform nil)
     (b :initform nil)
     (c :initform nil))))

(defgeneric frob (foo a b c) (:generic-function-class self-generic-function))

(defmethod frob (foo a b c) (list self a b c))

(frob (make-instance <foo>) 0 1 2)(#<foo 402028F48B> nil nil nil)

やっていることはマクロ版と殆ど変わりありません。
第一引数のシンボルをクラス名にする必要があるという所が危ういですが、まあ良しとします。

マクロでお化粧することも可能ですが、そうするとMOPで書く意味があまりないなという気分になってしまいます。

(defmacro defgeneric-self (name (&rest args) &body body)
  (destructuring-bind (class name)
                      name
    `(defgeneric ,name (,class ,@args) ,@body
       (:generic-function-class self-generic-function))))

(defmacro defmethod-self (name (&rest args) &body body) (destructuring-bind (class name) name `(defmethod ,name (,class ,@args) ,@body)))

(defgeneric-self (foo bar) ())

(defmethod-self (foo bar) () (list self a b c))

まとめ

クラスの情報を得るのにMOPのイントロスペクション機能を使う必要はありますが、得た情報からコード生成をすることに関しては、マクロの方が単純で明解ですね。


HTML generated by 3bmd in LispWorks 7.0.0

comments powered by Disqus