This article is part of a series describing a port of the samples from On Lisp (OL) to Clojure. You will probably want to read the intro first.
This article covers Chapter 7, Macros.
OL begins with a simple
nil! macro that sets something to nil.
nil! is implemented as a macro in Common Lisp (CL)
nil needs to generate a special form. Clojure puts much more careful boundaries around mutable state, so most Clojure data structures are not set-able at all. The few things that can be set are reference types, each with an explicit API and concurrency semantics.
Because setters go through an explicit API instead of a special form, the Clojure
nil! does not need to be macro at all. Here is a
nil! for Clojure atoms:
swap! function is specific to atoms. Usage for
nil! looks like:
The next interesting macro in OL is
nif, which demonstrates the use of backquoting. One way to implement Clojure
There are a few interesting differences from CL here:
~@instead of CL's
,@. This allows Clojure to treat commas as whitespace.
signum, but it has access to all of Java, including
caseis not part of core, and is provided by Clojure Contrib.
OL demonstrates the "fill in the blanks" approach to writing macros:
As examples, OL uses
our-while. The Clojure equivalents are:
There is one interesting new thing here. Clojure'
recur is an explicit way to denote a self-tail-call so that Clojure can implement it with a non-stack-consuming iteration. (Clojure cannot optimize tail calls in a generic way due to limitations of the JVM.)
It is also worth noting that
while loops are uncommon in Clojure. They rely on side effects that change the result of
test, and most Clojure functions avoid side effects.
Both Clojure and CL support destructuring in macro definitions. The OL example of this is a
when-bind macro. Here is a literal translation in Clojure:
[form tst] is a destructuring bind. The first element of
bindings binds to
form, and the second element to
tst. Usage looks like this:
Do not use the
when-bind as defined above. Clojure provides a better version called
when-let adds two features not present in
when-letrequires that the binding form be a vector. This leads to the "arguments in square brackets" style that distinguishes Clojure from many Lisps.
when-letintroduces a temporary binding
temp#using Clojure's auto-gensym feature.
The temporary binding of
temp# keeps the binding form from being expanded directly into the
when, because some binding forms are not legal for evaluation. The following output shows the difference:
If it is not clear to you why
when-bind doesn't work, try calling
macroexpand-1 on both the forms above.
The concepts in OL Chapter 7 translate fairly directly from Common Lisp into Clojure. The bigger differences are choices of idiom. Many of the examples in Common Lisp presume mutable state. In the typical Clojure program these forms would be in the minority.