Macro magic – creating a for-loop in Clojure

Posted: August 3, 2012 in Uncategorized
Tags: ,

Clojure is a functional programming language at heart, but there are times when it is useful to have some imperative programming tools at hand – sometime imperative style is a good fit for the problem you are trying to solve, and at other times it is helpful to squeeze that last bit of performance out of critical code.*

One such construct is the humble “for” loop, which in Java looks like:

for (int i=0; i<10 ; i++) {
  System.out.println(i);
}

Here’s how we might like it to look in Clojure:

(for-loop [i 0 (< i 10) (inc i)]
  (print i)
)

Unfortunately, a for-loop similar to the one defined above doesn’t exist in Clojure. Fortunately we are in the Land of Lisp, so the lack of a language feature should never be an impediment – macros to the rescue!

A good piece of general advice is that you shouldn’t use macros unless you really need to. However, we have no option in this case. for-loop cannot be implemented as a function for at least the following reasons:

  • It needs to bind a symbol “i” at compile time
  • We don’t actually want to construct a vector as an argument to for-loop at runtime. This would be a significant overhead given our stated goal was to create an imperative for-loop suitable for micro-optimisations.
  • Even if bound, (< i 10) and (inc i) would execute just once as arguments to the function. There would be no way to run the repeatedly on each iteration of the loop as we require
  • The body of the for loop would also be executed just once as a parameter to the function

The common theme in also of these is that we need to perform some transformation of the code at compile time rather than following the normal function evaluation rules. Most languages don’t allow you to do this, but we have macros at our disposal in Clojure and can escape the normal constraints that programmers face.

So we have concluded that we definitely need a macro if we want to enable our for-loop syntax. How do we go about building one?

A very useful technique is to write the code that you would like the macro to produce for a specific case. Here’s how you might write the example above, using loop/recur to ensure that Clojure does an efficient tail-recursive loop:

(loop [i 0]
  (if (< i 10)
    (do
      (println i)
      (recur (inc i)))))

To make this into a macro you need to do two things:

  • Quote your example code with the syntax-quote (`)
  • Pull out the parameters for the macro, refering them in your macro code with unquote (~) for single forms and unquote-splice (~@) where you have a sequence of forms (e.g. multiple statements in the for-loop)

So the resulting macro would look like:

(defmacro for-loop [[symbol initial check-exp post-exp] & code]
 `(loop [~symbol ~initial]
    (if ~check-exp
      (do
        ~@code
        (recur ~post-exp)))))

Voila – we have extended the language with an efficient for-loop syntax in six lines of code.

*I’m not advocating premature optimization. This applies only to situations where you have indentified a genuine bottleneck and determined that the benefits of doing extra optimization outweight the costs in terms of your time and the potential impact on maintainability.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s