Some notes about Clojure

Published October 4th, 2008

Last week, Rich Hickey came to speak at the monthly Boston Lisp Meeting about his new-ish programming language, Clojure. His presentation went double length, but few left early. Rich showed an impressive breadth of knowledge, fielding some tough questions and making a strong case for his language.

I fooled around with Clojure a little and wanted to share some of the things which haven’t gotten much play so far.

1. Implicit destructuring in binding forms

See examples below:

(def flat "flat")
(def tree '(("one" ("two")) "three" ((("four")))))
 
;; Simple binding (like Common Lisp's LET*).
(let [var1 flat
      var2 tree]
  (list var1 var2))
 
-> ("flat" (("one" ("two")) "three" ((("four")))))
 
;; Full destructuring.
(let [var1 flat
      [[a [b]] c [[[d]]]] tree]
  (list var1 a b c d))
 
-> ("flat" "one" "two" "three" "four")
 
;; Partial destructuring.
(let [[[a [b]] & leftover :as all] tree]
  (list a b leftover all))
 
-> ("one" "two" ("three" ((("four"))))
    (("one" ("two")) "three" ((("four")))))
 
;; Works on strings, too.
(let [[a b c & leftover] "123go"]
  (list a b c leftover))
 
-> (\1 \2 \3 (\g \o))

2. Unnamed arguments for short lambdas

Pretty straightforward:

(map #(+ % 4) '(1 2 3)) 
 
-> (5 6 7)
 
;; Multiple arguments.
(map #(* %1 %2) '(1 2 3) '(4 5 6))
 
-> (4 10 18)

This is roughly equivalent to Arc‘s [+ _ 4] form, though allows for more than one argument. The standard lambda form is also similar to Arc’s:

(map (fn [x] (+ x 4)) '(1 2 3)
 
-> (5 6 7)

3. Data structures as functions

When a map or vector is called as a function, its argument is used as a value lookup.

(let [myvec [100 200 300]
      mymap {:a 1 :b 2 :c 3}]
  (list (myvec 1)
        (mymap :c)))
 
-> (200 3)

For maps, symbols and keywords also work in a functional context:

(:a {:a 1 :b 2 :c 3})
 
-> 1
 
('b {'a 1 'b 2 'c 3})
 
-> 2

4. Implicit gensyms

Perhaps a minor point, but handy for cleaner macros.

(defmacro inc-safe [var]
  `(let [var# ~var]
     (if (integer? var#)
       (inc var#)
       0)))

In a backquoted form, ~var means to unquote (evaluate) var — like ,var in other Lisps — and var# creates a gensym whose name persists within the form. Compare to the equivalent in Common Lisp:

(defmacro 1+-safe (var)
  (let ((gvar (gensym)))
    `(let ((,gvar ,var))
       (if (integerp ,gvar)
           (1+ ,gvar)
           0))))

5. “[...]” code syntax in definition contexts

Look at the argument lists in any of the excerpts above to see what I mean. I’m kind-of on the fence on this one — on one hand it is semantically clearer, on the other it overloads the vector syntax. Also, I think round parentheses are more naturally attractive.

6. SLIME integration

SLIME is the Superior Lisp Interaction Mode for Emacs, and it’s something you can’t live without once you’ve tried it. Clone this git repository and follow the instructions in the README.

$ git clone git://github.com/jochu/swank-clojure.git

 


 

There are, however, a couple design choices which are less appealing, perhaps especially to Common Lisp programmers.

1. Only single-value function returns

I believe this was briefly explained as being limited by the rules of stack allocation in the JVM. The restriction can be mitigated somewhat by returning a list of values that are then destructured:

(defn foo []
  (list 'aaa 'bbb))
 
(let [[a b] (foo)]
  (list b a))
 
-> (bbb aaa)

Common Lisp emulation in Emacs Lisp follows this path. It is a leaky abstraction, though, because foo in the example will always return a list. Even if we only care about the first value, we must acknowledge in every call that it returns others. Compare with Common Lisp’s GETHASH: it has two return values, yet in most contexts it’s treated as having only one.

2. Iteration construct that looks more like recursion

Clojure is a functional language with immutable types. One might expect it would then tend toward recursive programming. Unfortunately, there’s no tail-call optimization in Clojure currently, as it isn’t supported by the JVM; since it’s certainly a sought feature, likely this will change soon. In the meantime, where efficiency matters one can use loop ... recur, which looks a lot like intra-function recursion.

(defn loop-test []
  (loop [collection (list)
         i 0]
    (if (< i 5)
      (recur (conj collection i) (inc i))
      collection)))
 
-> (4 3 2 1 0)

For simple iterative patterns, there are also for and doseq.

 


 

All in all, Clojure is very interesting. I should note I’ve only mentioned superficial stuff; the larger part of the second half of the presentation was Rich expounding on the ease of concurrency and the value of software transactional memory in the language, but these are more esoteric topics.

Thanks to Alec Berryman and George Polak for their comments and suggestions.

Further discussion on the programming reddit


6 Responses to “Some notes about Clojure”

  1. fogus Says:

    RE: `(recur)`

    You make it look ‘more’ like recursion by making the recur target the enclosing `defn` instead. That is:

    (defn member [x sq]
      (if (seq? sq)
        (if (= x (first sq))
          sq
          (recur x (rest sq)))))
    
    (member 'a '(1 2 3 a b c))
    

    The power of `recur` comes from the fact that it will work on lambdas as well… but it would be nice for the JVM to provide TCO. The combination of TCO and recur would be sweet.
    -m

  2. Stephen Bach Says:

    Nice, I didn’t know that functions were implicit recur targets. That’s really smart.

  3. mb Says:

    Some notes:

    – anonymous fns support also variadic arguments: #(apply str %&)

    for can do far more than just iterating:

        (for [x [1 2 3 4 5 6] :while (< x 3)
              y [1 2 3 4 5 6] :when (even? y)]
          [x y])
    

    gives a (lazy) ([1 2] [1 4] [1 6] [2 2] [2 4] [2 6]). It has nothing to do with for loops in other languages. In particular it is not a way of doing things for side effects. That is what doseq is supposed to do.

  4. Stephen Bach Says:

    Thanks mb. Looks like for is perhaps more a device for list comprehensions than iteration.

  5. Robert Virding Says:

    Coming from a pattern matching world the destructuring seems pretty simple, at least to what I am used to. Check out Lisp Flavoured Erlang (LFE) for a lisp example of more destructuring.

  6. Tuple parameter unpacking in 3.x - Page 2 | keyongtech Says:

    […] from scratch similar native pattern- matching features in CPython, like ones of Closure: http://items.sjbach.com/16/some-notes-about-clojure Or better ones of Scala, (or even OcaML, but those are static), etc, that avoid that pain and […]