8
votes

I have an atom that has two parts to it.

(def thing (atom {:queue '() :map {}}))

I want to update both :queue and :map in one atomic stroke, to prevent them from getting off-sync.

Queue individually:

(swap! thing update-in [:queue] (list 1))

(From this question: How to append to a nested list in a Clojure atom?)

Map individually:

(swap! thing assoc-in [:map 1] (:key :value))

(From this question: Using swap to MERGE (append to) a nested map in a Clojure atom?)

How can I do these both within a single swap statement? (assuming that would prevent them from getting off-sync?)

2

2 Answers

11
votes

You have one change you want to make, right? And you could write that change as a pure function? All you need to do is write that function, and pass it as the argument to swap!.

(defn take-from-queue [{q :queue, m :map}]
  {:queue (rest q), :map (assoc m :new-task (first q))})

(swap! thing take-from-queue)

Where of course I have no idea what you actually want the body of your function to do, so I've made up something that doesn't throw an exception.

2
votes

Say you have a hash-map atom:

(def m1 (atom {:a "A" :b "B"})) 

To change :a and :b at the same time, changing their values to values that are different, say the numbers 1 and 2, use this function:

(defn my-swap! [params]
  (swap! m1 (fn [old new] new) params))

, like so:

(my-swap! {:a 1 :b 2}) ;=> {:a 1, :b 2}

And the same effect could be achieved with the following function and execution:

(defn my-multi-swap! [params1 params2]
  (swap! m1 (fn [old new1 new2] new2) params1 params2))

(my-multi-swap! {} {:a 1 :b 2}) ;=> {:a 1, :b 2}

Normally reset! is used if you want to ignore the old value. Here we use it:

(defn my-merge-swap! [params]
  (swap! m1 (fn [old new] (merge old new)) params))

(my-merge-swap! {:b 3}) ;=> {:a "A", :b 3}

The first parameter to the swap! function is the existing value of the atom, and you must pass in one or more extra parameters, which you can use to give the atom its new value.