PCL -> Clojure, Chapter 8

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.

Rolling 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:

  (foo a b)

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 monday (wake-up) (sleep))

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.

Preparing for 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.

  (defn divides? [candidate-divisor dividend]
    (zero? (rem dividend candidate-divisor)))

Now I can tell when one number divides another:

  user=> (divides? 7 42)
  true
  user=> (divides? 11 42)
  false

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:

  ; yes, I know there are faster ways.  
  (defn prime? [num]
    (when (> num 1)
      (every? (fn [x] (not (divides? x num)))
        (range 2 (inc (int (Math/sqrt num)))))))

Sequences of primes

My eventual objective is to call do-primes like this:

  (do-primes i 100 200 
    (print (format "%d " i)))

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:

  (defn primes-from [number]
    (filter prime? (iterate inc 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:

  user=> (take 5 (primes-from 1000))
  (1009 1013 1019 1021 1031)

Now I need a simple helper that begins with primes-from, but cuts off the sequence at a chosen end:

  (defn primes-in-range [start end]
    (for [x (primes-from start) :while (<= x end)] x))

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.

do-primes

Now I am finally ready to write the macro do-primes:

  (defmacro do-primes [var start end & body]
    `(doseq [~var (primes-in-range ~start ~end)] ~@body))

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:

  • For symbols, syntax-quote resolves the name to a fully qualified symbol (with some exceptions we don't need to worry about in this example).
  • For lists, syntax-quote will recursively syntax-quote the contained forms.

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:

  user=> (macroexpand-1 '(do-primes i 1 10 (print i)))
  (clojure/doseq i (pcl.chap_08/primes-in-range 1 10) (print i))

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:

  user=> (do-primes i 100 150 
    (print (format "%d " i)))
  101 103 107 109 113 127 131 137 139 149

Wrapping up

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.


Notes

The sample code is available at http://github.com/stuarthalloway/practical-cl-clojure.

Revision history