PCL -> Clojure, Chapter 7 - No Fluff Just Stuff

PCL -> Clojure, Chapter 7

Posted by: Stuart Halloway on September 18, 2008

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 

There is one subtle difference worth mentioning. When you write macros in a Lisp, you have some control over how many parentheses get involved. Common Lisp tends to introduce parentheses around the "setup" part of the macro, e.g.


(dotimes (x 10) ...)

Clojure avoids these parentheses, e.g.


(dotimes x 10 ...)

I find Clojure's "avoid grouping parens" approach easier both to read and remember. YMMV.

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.

Stuart Halloway

About Stuart Halloway

Stuart Halloway is a founder and President of Cognitect, Inc. (www.cognitect.com). He is a Clojure committer, and a developer of the Datomic database.

Stuart has spoken at a variety of industry events, including StrangeLoop, Clojure/conj, EuroClojure, ClojureWest, SpeakerConf, QCon, GOTO, OSCON, RailsConf, RubyConf, JavaOne, and NFJS.

Stuart has written a number of books and technical articles. Of these, he is most proud of Programming Clojure.

Learn more about Stu's presentations on his wiki.

Why Attend the NFJS Tour?

  • » Cutting-Edge Technologies
  • » Agile Practices
  • » Peer Exchange

Current Topics:

  • Languages on the JVM: Scala, Groovy, Clojure
  • Enterprise Java
  • Core Java, Java 8
  • Agility
  • Testing: Geb, Spock, Easyb
  • REST
  • NoSQL: MongoDB, Cassandra
  • Hadoop
  • Spring 4
  • Cloud
  • Automation Tools: Gradle, Git, Jenkins, Sonar
  • HTML5, CSS3, AngularJS, jQuery, Usability
  • Mobile Apps - iPhone and Android
  • More...
Learn More »