Call side effect function only when atom value changes

What is the easiest way to call a side effect function that will only be called when the atom value changes?

If I used ref , I could just do this:

 (defn transform-item [x] ...) (defn do-side-effect-on-change [] nil) (def my-ref (ref ...)) (when (dosync (let [old-value @my-ref _ (alter! my-ref transform-item) new-value @my-ref] (not= old-value new-value))) (do-side-effect-on-change)) 

But that seems a bit cool, as I use ref , although I am not trying to coordinate the changes in multiple ref s. In fact, I use it only for convenient access to the old and new value in a successful transaction.

It seems to me that I can use atom instead. Is the solution simpler?

 (def my-atom (atom ...)) (let [watch-key ::side-effect-watch watch-fn (fn [_ _ old-value new-value] (when (not= old-value new-value) (do-side-effect-on-change)))] (add-watch my-atom watch-key watch-fn) (swap! my-atom transform-item) (remove-watch watch-key)) 

It also seems roundabout because I add and remove hours around each swap! call swap! . But I need this because I do not want the clock hanging around, which triggers a side function when another code modifies the atom .

It is important that the side-action function is called exactly once per mutation for an atom, and only when the transform-item transform function actually returns a new value. Sometimes it returns an old value giving a new change.

+5
source share
2 answers
 (when (not= @a (swap! a transform)) (do-side-effect)) 

But it should be very clear to you that you need the semantics of concurrency. For example, another thread may change the atom between reading and replacing it:

  • a = 1
  • Topic 1 reads as 1
  • Theme 2 changes to 2
  • Topic 1 sweeps 2 to 2
  • Thread 1 defines 1! = 2 and calls the do-side-effect

I don’t understand the question of whether this is desirable or undesirable. If you do not want this behavior, then the atom simply will not do the job unless you enter concurrency control with lock.

Seeing that you started with ref and asked about the atom, I think you probably already thought about concurrency. From your description, the ref approach seems to be better:

 (when (dosync (not= @r (alter r transform)) (do-side-effect)) 

Is there a reason you don't like your decision?

If the answer is "because I don't have concurrency", then I would recommend that you use ref anyway. Actually there is no flaw, and this makes your semantics explicit. IMO programs tend to grow to the point where concurrency exists, and Clojure is really different about what should happen when it exists. (For example, oh, I'm just calculating things, oh, I'm just exposing this stuff as a web service now, oh, now I'm at the same time).

In any case, keep in mind that features like alter and swap! return a value, so you can use it for short expressions.

+2
source

I come across the same situation and just come up with 2 solutions.

status field :changed?

Keeping a meaningless label :changed in an atom to track the swap function. And take the return value of swap! to see if things have changed. For instance:

 (defn data (atom {:value 0 :changed? false})) (let [{changed? :changed?} (swap! data (fn [data] (if (change?) {:value 1 :changed? true} {:value 0 :change? false})))] (when changed? (do-your-task))) 

an exception

You can throw an exception into the swap function and catch it outside:

 (try (swap! data (fn [d] (if (changed?) d2 (ex-info "unchanged" {}))) (do-your-task) (catch Exception _ )) 
0
source

Source: https://habr.com/ru/post/1243546/


All Articles