執拗な再帰マクロ展開
処理系に依るかも知れないが、macroexpandは全てのマクロを展開してくれない。
(defmacro macro (&body body) `(progn (foo) ,@body (bar))) (defmacro macro2 (&body body) `(prog1 ,@body (buzz))) (defmacro macro3 () ''fizz) (macroexpand '(macro (macro2 (macro3)))) | (progn (foo) (macro2 (macro3)) (bar))
次のように、全部展開してくれた方がうれしい。
(exhausted-recursive-macroexpand '(macro (macro2 (macro3)))) | (progn (foo) (let ((#1=#:result 'fizz)) (progn (buzz)) #1#) (bar)) ;整形(これはまた別の問題) (progn (foo) (let ((#1=#:result 'fizz)) (progn (buzz)) #1#) (bar))
上の定義と形が異なる気がするが、ご存じのようにprog1もマクロなので、それも展開された形になっている。というわけで、そんな関数を作ってみた。
(defun erm (form) (cond ((atom form) form) ((eq (car form) 'lambda) form) (t (multiple-value-bind (form trail) (macroexpand-1 (rerm form)) (if trail (erm form) form))))) (defun rerm (form) (if (atom form) form (append (list (erm (car form))) (rerm (cdr form))))) ;補題 (macroexpand-1 '(lambda () 'hoge)) | #'(lambda nil 'hoge) t
簡単に動作説明をしてみる。macroexpand-1は、展開できたら2番目の戻り値にtを返すので、これ以上展開できないところまで繰り返す。その動作をS式の内側から実行していく。一回展開したら、また中身を見直さないといけないのでなかなか面倒である。
また、(lambda 〜)の形のときだけは展開しないようにしている。これは、(lambda)が(function (lambda))に展開されるので、繰り返し展開すると無限ループになってしまうからである。
その他にも、無限ループに陥る可能性がある。defmacro中に同じ名前を使うのは論外(展開が終わらない)だが、&wholeを用いたマクロはその内部に自分と同じものが現れる。本当は、これを回避しなくてはならない。macro-functionで&wholeを用いているか確認したり、一度展開したかどうか記憶したりする必要があるが、割と面倒なのでここでは手抜きをした*1。
*1:&wholeの挙動をよく知らないので、それを調べるのが面倒だった