逐次解釈される関数で毎回マクロ呼び出しが展開されるが、コンパイルされた関数では(コンパイル時の)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)
にも使うことはないでしょう。