Hi there,
I have some state which I'd like to set to some default value, A. I'd then like
to update A to a new value A' and then, if (not (= A A')) I'd like to fire off
a function - say print to stdout that A has changed. If (= A A') I'd like
nothing to happen at all. Additionally, I'd like to do this in a way that's
thread safe - i.e. I can be updating A from any given thread and I'd never like
to miss a time when A has changed and also I don't want to fire of the fn more
times than necessary.
An initial naive solution might be the following:
(def a (atom 1))
(defn changed-fn
[]
(println "something changed!"))
(defn new-val
[]
(int (rand 2)))
(defn update
[]
(let [old-a @a ;;A
new-a (reset! a (new-val)] ;;B
(when-not (= old-a new-a)
(changed-fn))))
However, if there are two updates running concurrently, statements A and B may
be interleaved such that if we have two threads X and Y, a starting val of 0
for a and the result of calling new-val be 0 each time:
X (deref a) ;=> 1
Y (deref a) ;=> 1
X (reset! a 0) ;=> 0
Y (reset! a 0) ;=> 0
We now call our changed-fn twice. If however, we don't interleave the calls:
X (deref a) ;=> 1
X (reset! a 0) ;=> 0
Y (deref a) ;=> 0
Y (reset! a 0) ;=> 0
this results in changed-fn called only once. Clearly this approach doesn't work.
Ideally, it seems that there could be versions of reset! and swap! that
returned a vector result containing the old and new vals which could be
directly compared.
The solution appears to be to use a transaction and bind the result of a
comparison within the dosync:
(def a (ref 1))
(defn update
[]
(let [changed? (dosync
(let [old-a @a
new-a (ref-set a (new-val))]
(= old-a new-a)))]
(when changed? (changed-fn))))
Is this the only way of achieving this? Is there another, perhaps more
idiomatic, approach? It certainly seems overkill to have to use a transaction
when the number of references we wish to coordinate is only one - itself and
its previous value.
Sam
---
http://sam.aaron.name
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to [email protected]
Note that posts from new members are moderated - please be patient with your
first post.
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en