I am fairly comfortable with Clojure but have always shied away from macros. In an attempt to remedy that, I'm reading through "Mastering Clojure Macros" and also looking at some Clojure core macros generally.
While reading over the cond
macro, I got a little tripped up as to what is actually being evaluated when. Assuming clauses
is not nil and that the initial when
test passes, we then evaluate the list call. List is a function, so it must first evaluate all of it's arguments before entering it's body. The first argument is just the symbol 'if
, the next argument is then (first claues)
which evaluates to the first test, but then the part I found a bit confusing is what happens with the next (3rd) argument. It looks like the entire form:
(if (next clauses)
(second clauses)
(throw (IllegalArgumentException.
"cond requires an even number of forms")))
is actually evaluated before the final macroexpansion is returned for evaluation. If this is correct, does that mean that the test for an even number of forms occurs before the macro is actually expanded, and therefore can bail with an exception before the macro has actually generated a list for evaluation at runtime?
(defmacro cond
"Takes a set of test/expr pairs. It evaluates each test one at a
time. If a test returns logical true, cond evaluates and returns
the value of the corresponding expr and doesn't evaluate any of the
other tests or exprs. (cond) returns nil."
{:added "1.0"}
[& clauses]
(when clauses
(list 'if (first clauses)
(if (next clauses)
(second clauses)
(throw (IllegalArgumentException.
"cond requires an even number of forms")))
(cons 'clojure.core/cond (next (next clauses))))))