Last active
September 16, 2019 10:59
-
-
Save NicMcPhee/4957518 to your computer and use it in GitHub Desktop.
Three different ways of doing counters in Clojure, one of which doesn't work and thereby illustrates the scope of atomicity and a difference between atom and ref.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;;;;;;;;;;;;;;;;;; | |
;; The first way | |
;;;;;;;;;;;;;;;;;; | |
;; This use of atom is simple and works fine. | |
(def counter (atom -1)) | |
(defn next-value [] | |
(swap! counter inc)) | |
;;;;;;;;;;;;;;;;;; | |
;; The second way | |
;;;;;;;;;;;;;;;;;; | |
;; That said, it irked me slightly to have to start | |
;; the counter at -1, but that's necessary since swap! | |
;; returns the value *after* the update (and I want the | |
;; first value to be 0. | |
;; So I thought maybe I'd capture the original value | |
;; and then return it (as below). That does *not* work | |
;; however because we capture the value of @atom-counter | |
;; before knowing if the swap! will work or not. If the | |
;; swap! is executed several times we'll still have and | |
;; return the same initial value that we read at the start. | |
;; That causes the same value to be returned from | |
;; (atom-next-value) if we run it in several threads at the | |
;; same time. | |
;; => (pmap (fn [_] (atom-next-value)) (range 20)) | |
;; (0 0 4 2 3 7 5 6 8 9 13 10 11 12 17 14 18 15 19 16) | |
(def atom-counter (atom 0)) | |
(defn atom-next-value [] | |
(let [result @atom-counter] | |
(swap! atom-counter inc) | |
result)) | |
;;;;;;;;;;;;;;;;;; | |
;; The second way | |
;;;;;;;;;;;;;;;;;; | |
;; That problem could be fixed if we could ensure | |
;; that essentially the entire body of atom-next-value | |
;; was atomic. There's no good way to do that with | |
;; atoms, though, because all we've got is swap!, and | |
;; we can't ask swap to return the old value instead | |
;; of the new one. | |
;; We can, however, us a ref and wrap the whole thing | |
;; in a dosync: | |
(def ref-counter (ref 0)) | |
(defn ref-next-value [] | |
(dosync | |
(let [result @ref-counter] | |
(alter ref-counter inc) | |
result))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment