#:g1: macroexpand-dammitの紹介

Posted 2014-02-01 15:00:00 GMT

(LISP Library 365参加エントリ)

 LISP Library 365 の33日目です。

macroexpand-dammitとはなにか

 macroexpand-dammitとは、ポータブルなmacroexpand-allの実装です。

パッケージ情報

パッケージ名macroexpand-dammit
Quicklisp
参考サイトJohn Fremlin's blog: Portable Common Lisp code walking with macroexpand-dammit
CLiKihttp://cliki.net/macroexpand-dammit
Quickdocshttp://quickdocs.org/macroexpand-dammit

インストール方法

(ql:quickload :macroexpand-dammit)

試してみる

 どんな関数があるかは、Quickdocsで確認できます。

 Common Lispの場合、マクロ展開には、トップレベルだけ展開するmacroexpand-1とオペレーターがマクロである限り展開するmacroexpandが用意されています。

(defmacro lahalito (x)
  `(list ,x))

(defmacro badialma (x) `(list (lahalito ,x)))

(defmacro morlis (x) `(badialma ,x))

のような定義がある場合、

(macroexpand-1 '(morlis 1))
;=>  (BADIALMA 1)
;    T

(macroexpand '(morlis 1)) ;=> (LIST (LAHALITO 1)) ; T

のような動作になりますが、オペレーター以外の式に含まれるマクロまで全部展開される訳ではないので、処理系は大抵macroexpand-allというものを用意しています。

(macroexpand-all '(morlis 1))
;=>  (LIST (LIST 1))

 macroexpand-allのようなものをユーザーが作成するには、通常は&environmentで渡される処理系依存の環境オブジェクトを扱う必要があるのですが、ANSI Common Lispでは具体的に環境オブジェクトがどのようなものであるかは処理系依存とされているため、ポータブルな実装が難しくなっています。

 この問題に挑戦したのが、今回紹介するmacroexpand-dammitですが、環境オブジェクトの文脈を拡張したりする通常の方法とは別のアプローチで問題を回避しています(詳しくは参考サイト参照)。
また、通常の展開に加えて作者の好みで、展開後の不要なmacroletの部分の削除や、コンパイラマクロの展開をし、処理系依存部分の展開は抑制をしたりします。

(macroexpand-dammit:macroexpand-dammit 
 '(symbol-macrolet ((m (random))) 
   (macrolet ((m (m &optional (b m)) `(+ ,b ,m))) 
     (defun m (n) (m m n)))))
;=>  (DEFUN M (N) (+ N (RANDOM)))

(define-compiler-macro foo (n) (typecase n (fixnum `(the fixnum ,n)) (integer `(the integer ,n)) (t `(the t ,n))))

(macroexpand-dammit:macroexpand-dammit '(symbol-macrolet ((m (random))) (macrolet ((m (m &optional (b m)) `(+ ,b ,m))) (defun m (n) (m (foo m) n))))) ;=> (DEFUN M (N) (+ N (THE T (RANDOM))))

 ちなみに、処理系が用意するmacroexpand-allはこんな感じ

(swank-backend:macroexpand-all  
 '(symbol-macrolet ((m (random))) 
   (macrolet ((m (m &optional (b m)) `(+ ,b ,m))) 
     (defun m (n) (m (foo m) n)))))
;=>  (SYMBOL-MACROLET ((M (RANDOM)))
;      (MACROLET ((M (M &OPTIONAL (B M))
;                   `(+ ,B ,M)))
;        (PROGN
;         (EVAL-WHEN (:COMPILE-TOPLEVEL) (SB-C:%COMPILER-DEFUN 'M 'NIL T))
;         (EVAL-WHEN (:LOAD-TOPLEVEL :EXECUTE)
;           (SB-IMPL::%DEFUN 'M
;                            (SB-INT:NAMED-LAMBDA M (N)
;                              (BLOCK M (+ N (FOO (RANDOM)))))
;                            NIL 'NIL (SB-C:SOURCE-LOCATION))))))

 下記のような場合は、かなりすっきりします。

(define-symbol-macro $expand-level$ 0)

(defmacro badialma (&environment env x) (let ((current-expand-level (macroexpand '$expand-level$ env))) (if (> current-expand-level 10) x `(symbol-macrolet (($expand-level$ ,(1+ current-expand-level))) (badialma (list ,x))))))

(swank-backend:macroexpand-all '(badialma x))
;=>  (SYMBOL-MACROLET (($EXPAND-LEVEL$ 1))
;      (SYMBOL-MACROLET (($EXPAND-LEVEL$ 2))
;        (SYMBOL-MACROLET (($EXPAND-LEVEL$ 3))
;          (SYMBOL-MACROLET (($EXPAND-LEVEL$ 4))
;            (SYMBOL-MACROLET (($EXPAND-LEVEL$ 5))
;              (SYMBOL-MACROLET (($EXPAND-LEVEL$ 6))
;                (SYMBOL-MACROLET (($EXPAND-LEVEL$ 7))
;                  (SYMBOL-MACROLET (($EXPAND-LEVEL$ 8))
;                    (SYMBOL-MACROLET (($EXPAND-LEVEL$ 9))
;                      (SYMBOL-MACROLET (($EXPAND-LEVEL$ 10))
;                        (SYMBOL-MACROLET (($EXPAND-LEVEL$ 11))
;                          (LIST
;                           (LIST
;                            (LIST
;                             (LIST
;                              (LIST
;                               (LIST
;                                (LIST
;                                 (LIST (LIST (LIST (LIST X))))))))))))))))))))))
(macroexpand-dammit:macroexpand-dammit '(badialma x))
;=>  (LIST (LIST (LIST (LIST (LIST (LIST (LIST (LIST (LIST (LIST (LIST X)))))))))))

まとめ

 今回は、macroexpand-dammitを紹介してみました。
SLIMEからも便利に使えるような関数が予め設定されているので手軽に使えます。
一度試してみては如何でしょうか。

comments powered by Disqus