PCL -> Clojure, Chapter 7

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 7, Macros: Standard Control Constructs.

Rolling your own

Common Lisp control constructs are generally part of the standard library, not the core language. Ditto for Clojure. If you don't find a control construct you want, you can always roll it yourself. For example, Clojure doesn't have an unless, so here goes:

  (defmacro unless [condition & body]
    `(when (not ~condition)
       ~@body))

defmacro differs from Common Lisp in two important ways.

  • The argument list is a vector [...], not a list (...). (This is true for functions as well, I just hadn't mentioned it yet). Clojure gives vectors, sets, and maps equal billing with lists by giving them their own literal syntax.
  • Clojure uses different reader macros for unquote and unquote-splicing. Where CL uses , and ,@, Clojure uses ~, and ~@.

The avoidance of commas in read macros is a well-considered decision. Commas are whitespace in Clojure. This often results in an interface that is simultaneously human-friendly and list-y. The following two expressions are equivalent:

  {:fn "John" :ln "Doe"}
  {:fn "John", :ln "Doe"}

The latter form makes the map more readable, and more similar to other languages.

doseq

Common Lisp provides dolist for iterating a list. Clojure works in terms of sequences, which are collections that can be traversed in a list-like way. The Clojure analog to dolist is doseq. It can work with lists:

  user=> (doseq [x '(1 2 3)] (println x))
  1
  2
  3

doseq also works with maps. Note the destructuring bind since I care only about the values:

  user=> (doseq [[_ v] {:fn "John" :ln "Doe"}] (println v))
  John
  Doe

In fact, doseq works with any kind of sequence (hence the name). (iterate inc 1) produces an infinite collection incrementing up from 1. (take 5 ...) pulls a finite set of 5 elements from a collection.

  user=> (doseq [x (take 5 (iterate inc 1))] (println x)))
  1
  2
  3
  4
  5

Don't try to doseq an infinite collection, and don't say I didn't warn you.

dotimes

Common Lisp provides dotimes for iteration with counting. Here is the Clojure version of PCL's multiplication table example:

  user=>(dotimes [x 10]
          (dotimes [y 10]
            (print (format "%3d " (* (inc x) (inc y)))))
          (println))
    1   2   3   4   5   6   7   8   9  10 
    2   4   6   8  10  12  14  16  18  20 
    3   6   9  12  15  18  21  24  27  30 
    4   8  12  16  20  24  28  32  36  40 
    5  10  15  20  25  30  35  40  45  50 
    6  12  18  24  30  36  42  48  54  60 
    7  14  21  28  35  42  49  56  63  70 
    8  16  24  32  40  48  56  64  72  80 
    9  18  27  36  45  54  63  72  81  90 
   10  20  30  40  50  60  70  80  90 100 

CL do and loop

Common Lisp provides some more general control constructs: do and loop. Clojure's functions of the same name serve very different purposes. Clojure's do is equivalent to CL's progn, and Clojure's loop works with recur.

You could write Clojure macros to emulate CL's do and loop, but you probably won't want too. Instead, you can use list comprehensions or lazy sequences, which I will introduce later in this series.

Wrapping up

Like CL, Clojure defines control structures using macros. Also like CL, Clojure has control structures that are functional, plus some that are evaluated for their side effects. Clojure's control structures tend to use fewer parentheses.

Clojure does not duplicate CL's general purpose imperative control structures. Instead, you can often use list comprehensions and lazy sequences.


Notes

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

Revision history