The other day, I got a strange error while writing a macro (actually, deftest
from Peter Seibel's Practical Common Lisp. My defmacro
looked like this:
(defmacro deftest (name parameters &body body)
`(defun ,name ,parameters
(let ((*test-name* ,name))
,@body)))
At first glance, it looks fine. So, I defined a few tests with it (check
is another macro for reporting test results):
(deftest test-+ ()
(check
(= (+ 1 2) 3)
(= (+ 1 2 3) 6)
(= (+ -1 -3) -4)))
Trying to evaluate this, I got an error:
CL-USER> (deftest test-fn () (format t "testing~%"))
; in: DEFTEST TEST-+
; (LET ((*TEST-NAME* TEST-+))
; (FORMAT T "ohai~%"))
;
; caught WARNING:
; undefined variable: TEST-FN
;
; compilation unit finished
; Undefined variable:
; TEST-FN
; caught 1 WARNING condition
TEST-FN
CL-USER>
I racked my brain trying to figure it out. Here's the macroexpand-1
of that definition:
(DEFUN TEST-FN ()
(LET ((*TEST-NAME* TEST-FN))
(FORMAT T "testing~%")))
T
Still being new to Lisp, I didn't see what was wrong with it. However, the LET
gives it away:
(LET ((*TEST-NAME* TEST-FN))
It tries to evaluate the symbol TEST-FN, which we haven't defined yet (we're still building the function; as Paul Graham writes in On Lisp, "building a function and associating it with a certain name are two separate operations." (page 13). Let's take a look back at the original defmacro
: you'll notice that we're evaluating name
in the LET:
(let ((*test-name* ,name))
What I really wanted to do was to quote the value of name:
(let ((*test-name* ',name))
With that, the testing suite works.
This is one reason I'm hand typing all the examples. It's bugs like this one that give me the best education and help me recognise when things go sideways later.