Is there a way to swap!
and return an arbitrary value along with the value of the atom
?
For instance:
(swap! (atom {1 1 2 2 3 3 4 4 5 5})
(fn [m]
(loop []
(let [i (rand-int 10)]
(if (contains? m i)
(recur)
(assoc m i "ok"))))))
In the above function I have no way to know which key was added to the map (unless I list them beforehand).
I could use another atom to store the result, but it there something simpler I am overlooking?
Here's a weird but general approach:
(defn swap-diff! [atom f & args]
(loop []
(let [curr-value @atom
new-value (apply f curr-value args)]
(if (compare-and-set! atom curr-value new-value)
[new-value (data/diff curr-value new-value)]
(recur)))))
This uses compare-and-set!
in a loop
until it succeeds, similar to how swap!
works internally. When it succeeds, it returns a tuple of the new value and the output of clojure.data/diff
. The diff
output will show you exactly how the atom's value changed.
(swap-diff! (atom {1 1, 2 2, 3 3})
#(loop []
(let [i (rand-int 10)]
(if (contains? % i)
(recur)
(assoc % i "ok")))))
=> [{1 1, 2 2, 3 3, 9 "ok"} (nil {9 "ok"} {3 3, 2 2, 1 1})]
{1 1, 2 2, 3 3, 9 "ok"}
is the atom's new value. (nil {9 "ok"} {3 3, 2 2, 1 1})
is the output of diff
, where the first item are items only in the old value, the second item are items only on the new value, and the third value are items in both. In your case, you only care about the new items.
Update: If you don't want to deal with a tuple return value, you could tag the return value with diff metadata:
(defn swap-diff! [atom f & args]
(loop []
(let [curr-value @atom
new-value (apply f curr-value args)]
(if (compare-and-set! atom curr-value new-value)
(with-meta new-value
(zipmap [:before :after :both]
(data/diff curr-value new-value)))
(recur)))))
And then call meta
on the result to get the diff:
(meta *1)
=> {:before nil, :after {9 "ok"}, :both {3 3, 2 2, 1 1}}
This becomes much cleaner with a new function swap-vals!
:
(defn swap-diff! [atom f & args]
(let [[old new] (apply swap-vals! atom f args)]
(with-meta new (zipmap [:before :after :both]
(data/diff old new)))))