Macros can be notoriously difficult to debug. Clojure helpfully catches a variety of compile-time errors, but care must be taken to use the power that macros offer without being snared by their traps.
Consider what happens when referring to a var that isn’t yet defined. In a function, this will trigger a compile-time error:
(defn oops [arg] (frobnicate arg)) ;= #<CompilerException java.lang.Exception: ;= Unable to resolve symbol: frobnicate in this context (NO_SOURCE_FILE:1)>
Sadly, we can define a similar macro without any warning:
(defmacro oops [arg] `(frobnicate ~arg)) ;= #'user/oops
Trying to use this macro will produce an error when it is used:
(oops 123) ;= #<CompilerException java.lang.IllegalStateException: ;= Var user/frobnicate is unbound. (NO_SOURCE_FILE:0)>
What happened? Remember that macros execute at compile
time. At compile time, Clojure doesn’t (and can’t) know if the
frobnicate will refer to a var
that has a defined value at runtime or not. The macro sees and returns
only lists, symbols, and other data structures. Whether those symbols are
valid when the code produced by the macro is executed is not for the macro
to decide. This can make debugging macros tricky, but we have a couple of
tools at our disposal.
The most fundamental tool in debugging macros is
macroexpand-1. This function takes a data structure (in debugging contexts, often a quoted macro form) and taps into the Clojure compiler to return the code—which, remember, is ...