Previous: Eval During Expansion, Up: Problems with Macros [Contents][Index]
マクロ呼び出しは逐次解釈される関数で毎回マクロ呼び出しが展開されるが、コンパイルされた関数では(コンパイル時に)1回だけしか展開されないという事実にもとづく問題が、時折発生します。そのマクロ定義が副作用をもつ場合、それらのマクロは、そのマクロが難解展開されたかにより、異なる動作をとるでしょう。
したがって、あなたが何をしているか本当に判っていないのであれば、マクロ展開形の計算での副作用は避けるべきです。
避けることのできない特殊な副作用が1つあります。それはLispオブジェクトの構築です。ほとんどすべてのマクロ展開形には、リストの構築が含まれます。リスト構築はほとんどのマクロの核心部分です。これは通常は安全です。用心しなければならないケースが1つだけあります。それは構築するオブジェクトが、マクロ展開形の中でクォートされた定数の一部となるときです。
そのマクロが1回だけ — コンパイル時 — しか展開されない場合、そのオブジェクトの構築もコンパイル時の1回です。しかし逐次実行では、そのマクロはマクロ呼び出しが実行されるたびに展開され、これは毎回新たなオブジェクトが構築されることを意味します。
クリーンなLispコードのほとんどでは、この違いは問題になりません。しかし、マクロ定義によるオブジェクト構築の副作用を処理する場合には、問題になるかもしれません。したがって問題を避けるために、マクロ定義によるオブジェクト構築の副作用を避けてください。以下は副作用により問題が起こる例です:
(defmacro empty-object () (list 'quote (cons nil nil)))
(defun initialize (condition) (let ((object (empty-object))) (if condition (setcar object condition)) object))
initialize
が解釈された場合、initialize
が呼び出されるたびに、新しいリスト(nil)
が構築されます。したがって、各呼び出しの間において、副作用は存続しません。しかしinitialize
がコンパイルされた場合、マクロempty-object
はコンパイル時に展開され、これは1つの“定数”(nil)
を生成し、この定数はinitialize
の毎回の呼び出しで、再利用・変更されます。
このような異常な状態を避ける1つの方法は、empty-object
を、メモリー割り当て構造ではなく、一種の奇妙な変数と考えることです。'(nil)
のような定数にたいしてsetcar
を使うことはないでしょうから、当然(empty-object)
にも使うことはないでしょう。