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:
The 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 nif
is:
There are a few interesting differences from CL here:
~
and ~@
instead of CL's ,
and ,@
. This allows Clojure to treat commas as whitespace.signum
, but it has access to all of Java, including Integer/signum
.case
is 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-when
and our-while
. The Clojure equivalents are:
There is one interesting new thing here. Clojure' loop
/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:
The [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
:
when-let
adds two features not present in when-bind
:
when-let
requires that the binding form be a vector. This leads to the "arguments in square brackets" style that distinguishes Clojure from many Lisps.when-let
introduces 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.
temp#
appears in when-let
.