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 clojure@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+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

Reply via email to