elisp: Repeated Expansion
13.5.5 How Many Times is the Macro Expanded?
--------------------------------------------
Occasionally problems result from the fact that a macro call is expanded
each time it is evaluated in an interpreted function, but is expanded
only once (during compilation) for a compiled function. If the macro
definition has side effects, they will work differently depending on how
many times the macro is expanded.
Therefore, you should avoid side effects in computation of the macro
expansion, unless you really know what you are doing.
One special kind of side effect can’t be avoided: constructing Lisp
objects. Almost all macro expansions include constructed lists; that is
the whole point of most macros. This is usually safe; there is just one
case where you must be careful: when the object you construct is part of
a quoted constant in the macro expansion.
If the macro is expanded just once, in compilation, then the object
is constructed just once, during compilation. But in interpreted
execution, the macro is expanded each time the macro call runs, and this
means a new object is constructed each time.
In most clean Lisp code, this difference won’t matter. It can matter
only if you perform side-effects on the objects constructed by the macro
definition. Thus, to avoid trouble, *avoid side effects on objects
constructed by macro definitions*. Here is an example of how such side
effects can get you into trouble:
(defmacro empty-object ()
(list 'quote (cons nil nil)))
(defun initialize (condition)
(let ((object (empty-object)))
(if condition
(setcar object condition))
object))
If ‘initialize’ is interpreted, a new list ‘(nil)’ is constructed each
time ‘initialize’ is called. Thus, no side effect survives between
calls. If ‘initialize’ is compiled, then the macro ‘empty-object’ is
expanded during compilation, producing a single constant ‘(nil)’ that is
reused and altered each time ‘initialize’ is called.
One way to avoid pathological cases like this is to think of
‘empty-object’ as a funny kind of constant, not as a memory allocation
construct. You wouldn’t use ‘setcar’ on a constant such as ‘'(nil)’, so
naturally you won’t use it on ‘(empty-object)’ either.