*macroexpand-hook*の使い道 — #:g1

Posted 2010-12-29 10:26:00 GMT

以前から、*macroexpand-hook*ってどんな使い道があるのだろうかと謎に思っていたのですが、Spice Lisp(TOPS-10 CL)のソースを眺めていて、macromemoというファイルを発見し、その中で*macroexpand-hook*が使われているのを見付けました。
-(http://pdp-10.trailing-edge.com/clisp/02/clisp/upsala/macromemo.clisp.html)
マクロ展開関数の引数が2つのようなので、ANSI CL的に3つに揃えてみると、

(defun memoize-macro-call (expander expression env)
  "Replaces the call to a macro in Expression with a call to the expanded form
  with magic stuff wrapped around it."
  (let ((expansion (funcall expander expression env)))
    (if (eq (car expression) '*macroexpansion*) nil  ; "unless" is a macro...
	(displace expression (list '*macroexpansion* expansion
				   (cons (car expression) (cdr expression)))))
    expansion))

(defun displace (x y) "Replaces the CAR and CDR of X with the CAR and CDR of Y, returning the modified X." (rplaca x (car y)) (rplacd x (cdr y)))

(defmacro *macroexpansion* (expansion original) (declare (ignore original)) expansion)

(setq *macroexpand-hook* 'memoize-macro-call)

こんな感じになります。
動作させてみると、
;; 下準備
(defmacro foo (x)
  `(progn ,x))

(defmacro bar (y) `(progn ,y (foo 42)))

;; フックを設定 (setq *macroexpand-hook* 'memoize-macro-call)

(defparameter *expr* (copy-tree '(bar 7)))

(macroexpand *expr*) ;=> (PROGN 7 (*MACROEXPANSION* (PROGN 42) (FOO 42))) ; T

*expr* ;=> (*MACROEXPANSION* (PROGN 7 (*MACROEXPANSION* (PROGN 42) (FOO 42))) (BAR 7))

;; 通常に戻す (setq *macroexpand-hook* 'funcall)

(defparameter *expr* (copy-tree '(bar 7)))

;; 破壊的に書き変わっているので再度定義 (defmacro foo (x) `(progn ,x))

(defmacro bar (y) `(progn ,y (foo 42)))

(macroexpand *expr*) ;=> (PROGN 7 (FOO 42)) ; T

*expr* ;=> (BAR 7)

MacLISP等には、マクロ関係の機能で、displaceというのがあり、都度マクロを展開するのではなく、一度展開したものは、前の結果に置き換え、というものがあったようです。(ちなみに古いコードが残っているMaximaにもあります)
上記のコードは、実際の動作が掴みにくいのですが、一度展開されると同じものが返っているのが分かります。
現在は、コンパイラ指向の処理系が多いので効果は薄そうで、むしろ副作用の方が気になりますが、インタプリタでは有効なテクニックだったりしたのかもしれません。はっきりしたことは分かりませんが…。
マクロのメモ化も面白いと思って良くあるタイプのハッシュテーブルを使ったものに書き換えてみようかなとも思いましたが、引数の扱いはどうしたもんかということで頓挫しました。

comments powered by Disqus