Devon Peticolas
Devon Peticolas

Reputation: 440

How do I split an atom in Clojure?

I have a set which is stored in an atom, like so

(def numbers (atom #{1 2 3 4 5}))

Numbers are atomically added and removed from this set regularly using swap!. In a separate thread, I would like to have a function that extracts and remove the even numbers from the set and returns them.

One way I could do this would look something like:

(let [{even true odd false} (group-by even? @numbers)]
  (reset! numbers odd)
  even)

However, this isn't an atomic operation. numbers could change between the group-by and reset!. Is there a way to perform this operation atomically?

Upvotes: 3

Views: 199

Answers (3)

Andy
Andy

Reputation: 11472

Meanwhile in 2021 (since 1.9) you can use swap-vals! which atomically returns both the old and new values, so of course the evens is just the difference between them:

(apply clojure.set/difference
       (swap-vals! numbers
                   #(into #{} (filter odd? %))))

Upvotes: 0

DaoWen
DaoWen

Reputation: 33019

The function applied by swap! does not need to be atomic. You should be able to do this using swap!.

If you want to leave just the odd numbers and get back the even numbers, you can have the function applied by swap! return just the odds, and then that's what the atom's value will be updated to.

The remaining question is how to get back the even numbers. There are lots of ways you could do this—I'm not sure what would be the best. Maybe you could use the metadata on your collection? Something like this:

(def numbers (atom #{1 2 3 4 5}))

(defn atom-splitter [xs]
  (let [{even true odd false} (group-by even? xs)]
    (with-meta (set odd) {:evens (set even)})))

(swap! numbers atom-splitter)

The value of @numbers is then #{1 3 5}.

The value of (meta @numbers) is {:evens #{2 4}}.

Since swap! returns the swapped-in value, you can be sure the metadata on the returned value contains the other half of your split collection. For example, the following would be safe:

(defn split-off-evens [a]
  (-> (swap! numbers atom-splitter) meta :evens))

Calling (split-off-evens numbers) returns #{2 4} if numbers has its original value. Since this is still a correct use of an atom, you can assume that Clojure handles all the thread-safety issues.

Upvotes: 6

justncon
justncon

Reputation: 136

In this case the idiomatic way to maintain atomicity is to use transactions:

(def numbers (ref #{1 2 3 4 5}))
(future (loop []
          (Thread/sleep 10)
          (dosync
           (alter numbers conj (rand-int 1000)))
          (recur)))

(dosync
 (let [{even true odd false} (group-by even? @numbers)]
   (ref-set numbers odd)
   even))

For more see: http://clojure.org/refs.

EDIT

If you really want to use atoms, then this will work:

(def numbers (atom #{1 2 3 4 5}))
(future (loop []
          (Thread/sleep 10)
          (swap! numbers conj (rand-int 1000)))
          (recur))

(loop []
  (let [val @numbers
        {even true odd false} (group-by even? val)]
    (if (compare-and-set! numbers val odd)
      even
      (recur))))

Upvotes: 1

Related Questions