4
votes

I talked about this a bit on IRC's #clojure channel today but would like to go more in detail here. Basically, in order to better understand atoms, swap!, deref and Clojure concurrency as a whole, I'd like to try to write a function which not only returns the value that was swapped-in using swap!, but also the value that was swapped out.

(def foo (atom 42))

.
.
.

((fn [a]
  (do
    (println "swapped out: " @a)
    (println "swapped in: "(swap! a rand-int)))) foo)

may print:

swapped out:  42
swapped in:   14

However if another thread does swap! the same atom between the @a deref and the call to swap! then I may be swapping out a value that is not 42.

How can I write a function which gives back correctly both values (the swapped out and the swapped in)?

I don't care about the various values that the atom does change to: all I want to know is what was the value swapped out.

Can this be written using code that is guaranteed not to deadlock and if so why?

6

6 Answers

16
votes

Clojure's swap! is just a spinning compare-and-set. You can define an alternate version that returns whatever you like:

(defn alternate-swap [atom f & args]
  (loop []
    (let [old @atom
          new (apply f old args)]
      (if (compare-and-set! atom old new)
        [old new]  ; return value
        (recur)))))
3
votes

Atoms are un-coordinated so it seems likely that any attempt to do this outside of the swapping function it's self will likely fail. You could write a function that you call instead of swap! which constructs a function that saves the existing value before applying the real function, and then pass this constructed function to swap!.

user> (def foo (atom  []))
#'user/foo
user> (defn save-n-swap! [a f & args] 
         (swap! a (fn [old-val] 
                    (let [new-val (apply f (cons old-val args))] 
                       (println "swapped out: " old-val "\n" "swapped in: " new-val) 
                       new-val)))) 
#'user/save-n-swap!
user> (save-n-swap! foo conj 4) 
swapped out:  [] 
 swapped in:  [4]
[4] 
user> (save-n-swap! foo conj 4) 
swapped out:  [4]
 swapped in:  [4 4]
[4 4] 

This example prints it, It would also make sense to push them to a changelog stored in another atom

3
votes

If you want the return value, Stuart answer is the correct one, but if you are just going to do a bunch of println to understand how atoms/refs work, I would recommend to add a watch to the atom/ref http://clojuredocs.org/clojure_core/1.2.0/clojure.core/add-watch

(add-watch your-atom :debug (fn [_ _ old new] (println "out" old "new" new)))
2
votes

You could use a macro like:

(defmacro swap!-> [atom & args]
  `(let [old-val# (atom nil)
         new-val# (swap! ~atom #(do
                                  (swap! old-val# (constantly %))
                                  (-> % ~args)))]
     {:old @old-val# :new new-val#}))

(def data (atom {}))

(swap!-> data assoc :a 3001)
=> {:new {:a 3001} :old {}}
0
votes

You could rely on a promise to store the current value inside the swap! operation. Then you return the new and old value in a vector, as follows:

(defn- swap-and-return-old-value!
  [^clojure.lang.IAtom atom f & args]
  (let [old-value-promise (promise)
        new-value (swap! atom
                     (fn [old-value]
                       (deliver old-value-promise old-value)
                       (apply f old-value args)))]
    [new-value @old-value-promise]))