This article is part of a series describing a port of the samples from Practical Common Lisp (PCL) to Clojure. You will probably want to read the intro first.
This article covers Chapter 8, Macros: Defining Your Own.
Lisp macros gain their power by controlling argument evaluation. In a normal Lisp function all arguments are evaluated when calling a function. Consider this call to function foo
:
Arguments a
and b
are evaluated, and then passed to function foo
. If foo
were a macro, however, all bets would be off. Then foo
's arguments might be evaluated in bizarre orders, or not at all.
This may seem a little crazy until you consider a simple if
:
if
cannot possibly be a normal Lisp function. If it were, you would always both wake-up
and sleep
, regardless of the value of monday
.
As the if
example suggests, control flow is an obvious use case for macros. PCL demonstrates custom macros by defining a new control flow macro named do-primes
.
In order to implement do-primes
, I will need a primeness test. For clarity, I will divide this into two functions. First, a simple helper to detect factors.
Now I can tell when one number divides another:
A prime is simply a number with no divisors greater than one. I am a busy guy, so I won't check all the natural numbers, only those from two up to the square root of the number being tested. Here is a simple primeness test:
My eventual objective is to call do-primes
like this:
where i
is the loop variable and runs the primes from 100
to 200
. Because Clojure has nice support for infinite sequences, I find it easier to begin by thinking in terms of the pure math. So, here is a function that returns the sequence of primes starting from a number:
(iterate inc number)
returns an infinite sequence starting with number
and then incrementing by one for each subsequent element. The filter
then whittles this down to numbers that are prime.
This sequence is infinite, so don't try to view it from the console. Take your primes a few at the time:
Now I need a simple helper that begins with primes-from
, but cuts off the sequence at a chosen end
:
The for
is a list comprehension. It takes all the (primes-from start)
, but only while
those numbers are still less than or equal to end
.
Now I am finally ready to write the macro do-primes
:
Macros work in two steps: expansion followed by normal Lisp evaluation. The expansion phase is like a template substitution, but with the full power of Lisp at your disposal.
In the definition of do-primes
above, the syntax-quote (\
`) identifies the static part of the template:
The unquote (~
) and splicing-unquote (~@
) provide the dynamic part of the template by exempting their forms from syntax quoting rules.
Your reaction at this point should be "That's a lot of ugly punctuation." Fear not, macroexpand-1
will ease the pain. macroexpand-1
will show you how Clojure expands the macro, without executing the expanded result. This gives you a chance to experiment with the rules for quoting and unquoting. Here is an example:
Looking back at the definition of do-primes
, here is what happened:
doseq
expanded to the fully-qualified clojure/doseq
. (I haven't covered namespaces yet, but the clojure
namespace contains most of the Clojure core.)i
, 1
, and 10
are direct expansions from the macro call.primes-in-range
is one of the helper functions I wrote earlier. In the sample repository, I have placed this in the pcl/chap_08
namespace, hence the expansion.body
contains a list of things I want to do with my primes, specifically ((print i))
. That is almost what I need, except a few too many parens. The "splice" part of splicing unquote gets rid of the extra parens, splicing the list into the template. This is exactly what I need to match the doseq
signature.Now I can do-primes
:
The easiest way to write a macro is to work backwards. Write the form that you want the macro to expand into, and then test interactively with macroexpand-1
until you have a macro that expends correctly.
Macros are hard, and I have skipped some of the building blocks here. Check out the chapter in PCL.
The sample code is available at http://github.com/stuarthalloway/practical-cl-clojure.