#:g1: define-method-combinationの短形式は使えるか

Posted 2018-12-09 10:54:06 GMT

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

前回メソッドコンビネーションのlist:aroundと組み合わせることで結構汎用的に使えることが分かってしまいました。
+minmaxappendnconcのメソッドコンビネーションを眺めると、どれも&restな引数の関数なので、listメソッドコンビネーションでできた結果を:aroundapplyすれば良いだけです。

define-method-combinationの短形式は指定したシンボルのフォームで囲むメソッドコンビネーションを定義しますが、関数以外のオペレーターも指定することが可能です。

define-method-combinationの短形式の存在意義を確認するため、clパッケージ内のフォームを物色してみました。

define-method-combinationの短形式で定義できそうなものを探す

ということで、引数が&rest&bodyかで始まるフォームをclパッケージから探してみましたが、下記のようになりました。

(ql:quickload :trivial-arguments)

(loop :for s :being :the :external-symbols :of :cl :when (and (fboundp s) (ignore-errors (typep (trivial-arguments:arglist s) '(cons (member &rest &body) *)))) :collect s)

(rotatef shiftf cond ignore-errors append logeqv tagbody and locally logand lcm psetf trace vector in-package with-standard-io-syntax gcd psetq setq logior nconc or list progn make-broadcast-stream values untrace loop make-concatenated-stream declaim logxor + *)

なるほど、碌な応用が考えつかないものばかりです。
標準で提供されているメソッドコンビネーションは、このリストの中でも割合に有用なものが選ばれていたことも判ります。

とりあえず、幾つか定義を試してみましょう

ignore-errorsメソッドコンビネーション

define-method-combinationの短形式で順次実行のフォームを定義した場合、基本的にprognと同じようになります。

(define-method-combination ignore-errors)

(defgeneric foo (x y) (:method-combination ignore-errors))

(defmethod foo ignore-errors (x y) (+ x y))

(defmethod foo ignore-errors ((x list) (y list)) (append x y))

(foo 1 2) → 3

(foo '(0 1 2) '(a b c)) → nil #<conditions:arithmetic-type-error 40201FF25B>

こんな感じに展開されるのですが、エラーが発生してもとにかく続行したいという時には使えるかもしれません。

ignore-errorsメソッドコンビネーションは、prognメソッドコンビネーションを`ignore-errorsで囲めば定義せずに済んでしまいそうです。
andorメソッドコンビネーションと:aroundignore-errors適用も良さそうです。

一応の展開形確認

(mc-expand #'foo 
           'ignore-errors
           nil
           '(0 1 2)
           '(a b c))

(ignore-errors (call-method #<standard-method foo (ignore-errors) (list list) 402014EFD3> nil) (call-method #<standard-method foo (ignore-errors) (t t) 40201FE2C3> nil))

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

listメソッドコンビネーションに:aroundvalues-listすれば良いのですが、より直截的でしょうか。

(define-method-combination values)

(defgeneric foo (x y) (:method-combination values))

(defmethod foo values (x y) (list x y))

(defgeneric bar (x) (:method-combination values))

(defmethod bar values ((x number)) (list x 'number))

(defmethod bar values ((x rational)) (list x 'rational))

(defmethod bar values ((x float)) (list x 'float))

(defmethod bar values ((x integer)) (list x 'integer))

(bar 1.0)(1.0 float) (1.0 number)

まあ、頭を捻れば応用例も見出せるかもしれません。

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

loopで囲んでみましたが、メソッドコンビネーションスコープとループのスコープが微妙に噛み合いません。
長形式で工夫して定義するかMOPでメソッドコンビネーションの枠組み自体を書き換えるかしないと、あまり有用な定義はできなさそうです。

噛み合わない点ですが、loopblockスコープが指定できない、等々です。
これもprognメソッドコンビネーションで、条件が成立するまでループさせるような:aroundを書いた方が有用かもしれません。

(define-method-combination loop)

(defgeneric baz (x) (:method-combination loop))

(defmethod baz loop (x) (print x) (throw 'baz x))

(defmethod baz loop ((x number)) (print (list (incf x) 'number)))

(defmethod baz loop ((x rational)) (print (list (incf x) 'rational)))

(defmethod baz loop ((x integer)) (print (list (incf x) 'integer)))

(defmethod baz :around (x) (catch 'baz (call-next-method)))

(baz 8)(9 integer)(9 rational)(9 number) ▻ 8 → 8

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

折角なので常軌を逸していそうなtagbodyについて考えてみましたが、blockや、tagbodyのタグのスコープはダイナミックエクステントなので、普通に書いたらメソッドを跨ぐことができません。
ということで、定義がtagbodyblockの中に収まっている必要があります。

(define-method-combination tagbody)

(defgeneric fact (n) (:method-combination tagbody))

(block nil (let ((n 20) (ans 1)) (tagbody (defmethod fact tagbody ((n (eql 0))) (go X)) (defmethod fact tagbody ((n integer)) (setq ans (* n ans)) (go L)) L (decf n) (fact n) X (return ans)))) → 121645100408832000

まとめ

define-method-combinationの短形式を使って有用なものを定義できることは、ほぼなさそうです。
関数系であれば、listメソッドコンビネーション & :aroundの組み合わせで、順次実行フォーム系であれば、progn & :aroundの組み合わせを、非常に簡潔に書ける、ということがメリットですが、長形式だけ用意しておけば良かったのではないかナー、という結論です。


HTML generated by 3bmd in LispWorks 7.0.0

comments powered by Disqus