We now make a brief stop at macrolet. This is the macro-equivalent of labels/flet. It creates a locally defined macro while not interning its name. There are a couple of things to be careful of. First, unlike labels and flet, macrolet does not have access to the local variables in the lexical environment. The second thing to note is that macrolet more closely resembled labels, in that its definitions are visible inside its own body (i.e. macrolet macros can invoke themselves recursively).
To show how defmacro works in practice, here’s a short fragment:
(defun myfunc (x dir) (macrolet ((incdec (y dir) (cond ((keywordp dir) (format t "kw~%") (ecase dir (:PLUS `(1+ ,y)) (:MINUS `(1- ,y)))) (t (format t "non-kw~%") `(ecase ,dir (:PLUS (incdec ,y :PLUS)) (:MINUS (incdec ,y :MINUS))))))) (format t "~A~%" (incdec x :PLUS)) (format t "~A~%" (incdec x :MINUS)) (format t "~A~%" (incdec x dir))))
When I say that the macro does not have access to the local variables, I mean that it is not possible to use x or ,x in the body of the macro, those are unavailable.
The output upon load is as follows:
CL-USER> (load "macrolet") kw kw non-kw kw kw T
Let’s review that output at load time. I’ve added format statements in the macrolet, outside of the backticked region, so we can see how the macro is being expanded. We define the incdec macro so that it examines its invocation context. If the form containing the incdec call has a literal keyword object, such as :PLUS or :MINUS, the macro expands to a simple 1+ or 1- call, respectively. If, on the other hand, the invocation has a variable, or some other form which is not known at compile time to reduce to a keyword, then the macro inserts code to examine the variable at run time and take the appropriate action, recursively calling incdec with the fixed keyword to avoid infinite recursion.
When the function is loaded, it first sees the (incdec x :PLUS) form, so it substitutes the keyword version of the macro. The next form contains :MINUS there, and also substitutes the keyword version. The third line, though, uses dir, which is passed as a parameter, so its value is not available at compile/load time. That causes the macro to expand in the non-keyword version, which itself expands into two keyword versions of the macro.