defmacro
is pure CL code with the difference that it gets executed at compile-time, Scheme's syntax-rules
is a specialized sub-language which is almost-but-not-quite Scheme. Therefore, it loses the convenient equivalence to normal Scheme code and is, at least for me, much harder to understand and much less intuitive to use.
On the other hand, CL's defmacro
suffers from the lack of macro hygiene, as opposed to Scheme's macro system.
Simple examples of this symptom are easy to fix. Consider this macro (from Practical Common Lisp) - iterating over prime numbers in a given range:
(defmacro do-primes ((var start end) &body; body) `(do ((,var (next-prime ,start) (next-prime (1+ ,var))) (ending-value ,end)) ((> ,var ending-value)) ,@body))The problem here is with
ending-value
. This code is broken:
(do-primes (ending-value 0 10) (print ending-value))Because
ending-value
is being re-binded inside do-primes
. Fortunately, this case is easy to fix:
(defmacro do-primes ((var start end) &body; body) (let ((ending-value-name (gensym))) `(do ((,var (next-prime ,start) (next-prime (1+ ,var))) (,ending-value-name ,end)) ((> ,var ,ending-value-name)) ,@body)))
However, there are cases that can't be fixed in any simple way. For example,
look at the last code sample again - what if do-primes
is called
inside some context where do
is redefined to be a different
function (with flet
or labels
). Sure, this is a far
fetched example - but it can happen, and the code will surely break. Think about
it - the macro can be used successfully for years, until it gets use in a
slightly weird piece of code - and then it will fail in a mysterious way which
is quite difficult to debug.
Another far-fetched-but-can-happen problem: if a macro refers to some global symbol and is called inside a lexical context that shadows this symbol. Another hard-to-fix problem.
There are probably other examples of where defmacro
can break.
Sure, with a judicious use of gensym
s and solid coding conventions
the probability of something happening is low, and indeed, tons of CL code with
macros work perfectly. However, there's this nagging feeling of a looming
disaster that may strike at some unknown time with a slightly non-standard piece
of code. This isn't pleasant.
Scheme's macro system is hygienic and doesn't suffer from these problems. Of course, this comes at the price of simplicity and ease-of-use, as I mentioned earlier.
P.S. Interestingly, you can write code with defmacro
in Scheme if
you want. Most of the major implementations support it. It appears that Scheme's
macros can be used to implement (non-hygienic) CL macros, but not the other way
around.