#:g1: 定番メソッドコンビネーション紹介: and

Posted 2018-12-05 18:10:00 GMT

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

今回はCommon Lispの組み込みメソッドコンビネーションのandを紹介していきます。

andメソッドコンビネーション

standardはパターンに分類するなら呼び出しのフックの実現でしたが、andは、メソッドの集合をandで実行するものです。

二次元座標2dと、それを継承した3d、ついでに何故か2dのスーパークラスである1dがある場合を考えてみましょう。

(defclass 1d ()
  ((x :initarg :x :accessor x)))

(defclass 2d (1d) ((y :initarg :y :accessor y)))

(defclass 3d (2d) ((z :initarg :z :accessor z)))

これらの等価を判定する==を考えてみると、andを使えば、

(defgeneric == (x y)
  (:method-combination and))

(defmethod == and ((a 1d) (b 1d)) (= (x a) (x b)))

(defmethod == and ((a 2d) (b 2d)) (= (y a) (y b)))

(defmethod == and ((a 3d) (b 3d)) (= (z a) (z b)))

こんな風にすっきり書けます。

(== (make-instance '1d :x 10)
    (make-instance '1d :x 10))
→ t 

(== (make-instance '2d :x 10 :y 20) (make-instance '2d :x 10 :y 25)) → nil

(== (make-instance '3d :x 100 :y 50 :z 80) (make-instance '3d :x 100 :y 50 :z 80)) → t

(== (make-instance '3d :x 100 :y 50 :z 80) (make-instance '3d :x 100 :y 50 :z 8)) → nil

前回定義してみたmc-expandで展開形を確認してみると、こんな感じに、実際にandでメソッドが囲まれています。
※なお展開形は実装依存です。LispWorksでは実際は、sys::blocked-andが使われています。

(mc-expand #'== 
           'and
           nil 
           (make-instance '3d :x 100 :y 50 :z 80)
           (make-instance '3d :x 100 :y 50 :z 8))(and (call-method #<standard-method == (and) (3d 3d) 41D052D4A3>
                  nil)
     (call-method #<standard-method == (and) (2d 2d) 41D052D48B>
                  nil)
     (call-method #<standard-method == (and) (1d 1d) 41D04E5EE3>
                  nil))

さらに、andは、:around修飾子も持っているので、実行の全体を何かの処理で包みたい場合に使えます。

(defvar *debug* nil)

(defgeneric == (x y) (:method-combination and))

(defmethod == and ((a 1d) (b 1d)) (when *debug* (print (list (x a) (x b)))) (= (x a) (x b)))

(defmethod == and ((a 2d) (b 2d)) (when *debug* (print (list (y a) (y b)))) (= (y a) (y b)))

(defmethod == and ((a 3d) (b 3d)) (when *debug* (print (list (z a) (z b)))) (= (z a) (z b)))

(defmethod == :around (x y) (let ((*debug* T)) (call-next-method)))

(== (make-instance '3d :x 100 :y 50 :z 80) (make-instance '3d :x 1 :y 50 :z 80))(80 80)(50 50)(100 1) → nil

:around修飾子の謎

メソッドコンビネーションアドベントカレンダーを書き始める前は、and等でも:aroundが使えるのを知りませんでした。
使えることを知ってからも一体何に使うのか謎でしたが、Flavorsにあった、defwrapperのようなパターンを記述するためなのかもしれません。
そう考えると:aroundが最外周に配置されることもなんとなく分かる気がしますが、 上記の例は、ちょっとわざとらしい例ながらもdefwrapperの使い方に近いです。

次回も、標準組み込みのメソッドコンビネーションを紹介したりしようかなと思います。


HTML generated by 3bmd in LispWorks 7.0.0

comments powered by Disqus