Search code examples
clojureclosures

Atom and closure in Clojure


When I tried to learn memoize example in https://clojure.org/reference/atoms , I became confused why mem variable holds the same atom object across multiple function calls.

According to the QA in Clojure closure , this is a behavior of closure. However as the following two functions behave differntly, I come to think that this is a behavior of atom. Could you explain the difference of the following two functions from the view of closure and atom?

(def counter
  (let [count (atom 0)]
      (fn [] (do (swap! count inc)
                 @count))))

(println (counter)) ;; 1
(println (counter)) ;; 2

(def counter2
  (let [count {:value 0}]
      (fn [] (do (assoc count :value (inc (get count :value)))
                  (get count :value)))))

(println (counter2)) ;; 0
(println (counter2)) ;; 0

(EDIT) After reading Eugene's answer I replace the second example function with the following function. The reason why the counter increases its value is that the counter is a closure being closed over a variable with mutable value.

(def counter2
  (let [count (int-array '(0))]
      (fn [] (do (aset count 0 (inc (get count 0)))
                 (get count 0))))) ;; This line is not necessary but is added to keep consistency with the functions above.

(println (counter2)) ;; 1
(println (counter2)) ;; 2

Solution

  • Both functions are closed over a single value. The first function is closed over a container whose value can be changed. The second function is closed over an immutable map that cannot be changed.

    swap! changes the value inside an atom, in-place. (BTW don't mix atom operations like you did with swap! followed by @ unless you're absolutely certain it must be done since it makes the code not atomic. In this case, just return the result of swap!.)

    assoc create a new immutable map and returns not, without affecting its arguments.