Question

I know I can do the following in Common Lisp:

CL-USER> (let ((my-list nil))
       (dotimes (i 5)
         (setf my-list (cons i my-list)))
       my-list)
(4 3 2 1 0)

How do I do this in Clojure? In particular, how do I do this without having a setf in Clojure?

Was it helpful?

Solution

Clojure bans mutation of local variables for the sake of thread safety, but it is still possible to write loops even without mutation. In each run of the loop you want to my-list to have a different value, but this can be achieved with recursion as well:

(let [step (fn [i my-list]
             (if (< i 5)
               my-list
               (recur (inc i) (cons i my-list))))]
  (step 0 nil))

Clojure also has a way to "just do the looping" without making a new function, namely loop. It looks like a let, but you can also jump to beginning of its body, update the bindings, and run the body again with recur.

(loop [i 0
       my-list nil]
  (if (< i 5)
    my-list
    (recur (inc i) (cons i my-list))))

"Updating" parameters with a recursive tail call can look very similar to mutating a variable but there is one important difference: when you type my-list in your Clojure code, its meaning will always always the value of my-list. If a nested function closes over my-list and the loop continues to the next iteration, the nested function will always see the value that my-list had when the nested function was created. A local variable can always be replaced with its value, and the variable you have after making a recursive call is in a sense a different variable.

(The Clojure compiler performs an optimization so that no extra space is needed for this "new variable": When a variable needs to be remembered its value is copied and when recur is called the old variable is reused.)

OTHER TIPS

My personal translation of what you are doing in Common Lisp would Clojurewise be:

(into (list) (range 5))

which results in:

(4 3 2 1 0)

A little explanation:

The function into conjoins all elements to a collection, here a new list, created with (list), from some other collection, here the range 0 .. 4. The behavior of conj differs per data structure. For a list, conj behaves as cons: it puts an element at the head of a list and returns that as a new list. So what is does is this:

(cons 4 (cons 3 (cons 2 (cons 1 (cons 0 (list))))))

which is similar to what you are doing in Common Lisp. The difference in Clojure is that we are returning new lists all the time, instead of altering one list. Mutation is only used when really needed in Clojure.

Of course you can also get this list right away, but this is probably not what you wanted to know:

(range 4 -1 -1)

or

(reverse (range 5))

or... the shortest version I can come up with:

'(4 3 2 1 0)

;-).

Augh the way to do this in Clojure is to not do it: Clojure hates mutable state (it's available, but using it for every little thing is discouraged). Instead, notice the pattern: you're really computing (cons 4 (cons 3 (cons 2 (cons 1 (cons 0 nil))))). That looks an awful lot like a reduce (or a fold, if you prefer). So, (reduce (fn [acc x] (cons x acc)) nil (range 5)), which yields the answer you were looking for.

For this I would use range with the manually set step:

(range 4 (dec 0) -1) ; => (4 3 2 1 0)

dec decreases the end step with 1, so that we get value 0 out.

user=> (range 5)
(0 1 2 3 4)
user=> (take 5 (iterate inc 0))
(0 1 2 3 4)
user=> (for [x [-1 0 1 2 3]]
         (inc x)) ; just to make it clear what's going on
(0 1 2 3 4)

setf is state mutation. Clojure has very specific opinions about that, and provides the tools for it if you need it. You don't in the above case.

(let [my-list (atom ())]
  (dotimes [i 5]
    (reset! my-list (cons i @my-list)))
  @my-list)

(def ^:dynamic my-list nil);need ^:dynamic in clojure 1.3
(binding [my-list ()]
  (dotimes [i 5]
    (set! my-list (cons i my-list)))
  my-list)

This is the pattern I was looking for:

(loop [result [] x 5]
  (if (zero? x)
    result
    (recur (conj result x) (dec x))))

I found the answer in Programming Clojure (Second Edition) by Stuart Halloway and Aaron Bedra.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top