How to update an atom & return the change?

618 views
Skip to first unread message

Jakub Holy

unread,
Mar 20, 2014, 6:28:47 PM3/20/14
to clo...@googlegroups.com
I have couple of times run into a situation where I want to update a state map held in an atom
and return the change, not the new value. I haven't found a good way to do it so either I am missing
something obvious or there are more idiomatic ways to achieve what I need. Could you advise me?

A concrete example: In ma webapp I want to assign a unique random ID to each user. Creating that ID is simple:

(def state (atom {:teams {}}))

;; Remove already used IDs from a lazy seq of random IDs (=> unique), take the 1st one
(defn unique-rand-id [id-set]
  (first (remove id-set (repeatedly #(rand-int Integer/MAX_VALUE))))))

;; Add a new team with a unique random ID to the teams map
(defn make-team [teams]
  (let [id (unique-rand-id (set (keys teams)))]
    (assoc teams id {})))

;; Create a new team; TODO: How to get the new team's ID?!
(swap! state #(update-in % [:teams] make-team))

So I can generate and remember a new unique random ID but there is no way to find out
what ID it was (I cannot just take diff of state before and after since other threads could
have also added new IDs to it in the meanwhile.)

Any advice is appreciated. Thank you!

Stephen Gilardi

unread,
Mar 20, 2014, 7:57:12 PM3/20/14
to clo...@googlegroups.com
There was a stackoverflow question recently that requested a solution for a similar problem:


One solution there is similar to this:

(defn swap*!
  "Like swap! but returns a vector of [old-value new-value]"
  [atom f & args]
  (loop [old-value @atom]
    (let [new-value (apply f old-value args)]
      (if (compare-and-set! atom old-value new-value)
        [old-value new-value]
        (recur @atom)))))

This will return the correct old-value and new-value which you can diff.

Another note: 

(swap! state #(update-in % [:teams] make-team))

can be written more succinctly:

(swap! state update-in [:teams] make-team)

—Steve

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Jakub Holy

unread,
Mar 21, 2014, 12:32:25 PM3/21/14
to clo...@googlegroups.com

Thanks a lot, Stephen!

You received this message because you are subscribed to a topic in the Google Groups "Clojure" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojure/2dHvX7bf7nA/unsubscribe.
To unsubscribe from this group and all its topics, send an email to clojure+u...@googlegroups.com.

Jakub Holy

unread,
Mar 21, 2014, 7:22:46 PM3/21/14
to clo...@googlegroups.com
For the interested, this is my (certainly pretty imperfect) solution for changing a value in a state map and returning the old and new value (that can be safely diffed to get the change):

(defn swap-in!
  "Combination of update-in and swap! returning the value at the path before and after."
  [atom path f & args]
  (loop [] 
    (let [old-a @atom
          old-val (get-in old-a path)
          new-val (apply f (cons old-val args))
          new-a (assoc-in old-a path new-val)]
      (if (compare-and-set! atom old-a new-a)
        [old-val new-val]
        (recur)))))

;; example:
(swap-in! (atom {:k 1}) [:k] + 2) ;; => [1 3]


You received this message because you are subscribed to a topic in the Google Groups "Clojure" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojure/2dHvX7bf7nA/unsubscribe.
To unsubscribe from this group and all its topics, send an email to clojure+u...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.



--
Forget software. Strive to make an impact, deliver a valuable change.

(
Vær så snill og hjelp meg med å forbedre norsken min – skriftlig og muntlig. Takk!)

Jakub Holy
Solutions Engineer | +47 966 23 666
Iterate AS | www.iterate.no
The Lean Software Development Consultancy
- http://theholyjava.wordpress.com/ -

Jarrod Swart

unread,
Mar 21, 2014, 9:59:57 PM3/21/14
to clo...@googlegroups.com
Just to add to this because I ran across this reading some code a few days ago:

Reply all
Reply to author
Forward
0 new messages