関数を解釈実行しているときには、マクロ呼び出しを評価するたびに展開しますが、 コンパイルした関数では、(コンパイル時に)1回だけ展開します。 この違いが問題になることもあります。 マクロ定義に副作用があると、 マクロを何回展開したかに依存して動作が異なります。
したがって、マクロ展開の計算では、 本当になにをしているのか理解していない限り、副作用は避けてください。
特別な種類の副作用の1つ、つまり、 Lispオブジェクトを構築することは回避できません。 ほとんどすべてのマクロ展開では、リストを構築し、 それがマクロの重要な点でもあります。 これは、通常、安全ですが、 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)
が作られます。
したがって、2つの呼び出しのあいだで副作用が残ることはありません。
initialize
をコンパイルしてあると、
マクロempty-object
はコンパイル時に展開され、
1つの『定数』(nil)
を作りますが、
これは、initialize
を呼び出すたびに、
再利用され変更されてしまいます。
このような病的な場面を回避する1つの方法は、
empty-object
を、メモリ割り付けではなく、
ある種の定数と考えることです。
'(nil)
のような定数にsetcar
は使わないでしょうから、
(empty-object)
も自然にそのように使わないでしょう。