clojurelispsetf

setf in Clojure


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?


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.)