Remove first item from Clojure vector atom and return it
My go-to solution for this is storing whatever return value is needed in the metadata of the contained value. I don't know how idiomatic that is and it obviously only works for classes that implement the IMeta
interface.
(defn pop-head!
[a]
(-> (swap! a #(with-meta (subvec % 1) {:head (% 0)}))
(meta)
:head))
This profits from the fact that swap!
returns the now-stored value of the atom. Let's try it:
(def a (atom [1 2 3 4]))
(pop-head! a) ;; => 1
(pop-head! a) ;; => 2
(pop-head! a) ;; => 3
(pop-head! a) ;; => 4
(pop-head! a) ;; => IndexOutOfBoundsException...
Yeah, you might want to handle that case. ;)
A spin loop with compareAndSet
is used to swap!
an atom. Clojure also provides a lower level compare-and-set!
for atoms that you can use to do your own spin loop and return both the old and new value.
(defn swap*!
"Like swap! but returns a vector of [old-value new-value]"
[atom f & args]
(loop []
(let [ov @atom
nv (apply f ov args)]
(if (compare-and-set! atom ov nv)
[ov nv]
(recur)))))
(defn remove-first-and-return [atom]
(let [[ov nv] (swap*! atom subvec 1)]
(first ov)))
This is not a solution for this use case, but might be for some others.
You can create a watch on the atom with add-watch which will send an event with both the old and new values.
If you need to use an atom, use a locally encapsulated atom to store the first element of the in-transaction value of the winning transaction.
(let [f-atom (atom nil)]
(swap! items-atom #(do (reset! f-atom (first %))
(rest %)))
@f-atom)
Alternatively, achieve the same with a ref
and a dosync
transaction block:
(dosync
(let [f (first @items-ref)]
(alter items-ref rest)
f)))
Here, in case the transaction fails because a parallel write operation succeeded, the transaction does not return or have effect on the ref until it could have been retried so that the read and write operations were performed without interruption by another write operation.