只有在atom
的值发生变化时才能触发副作用函数的最简单方法是什么?
如果我使用ref
,我想我可以这样做:
(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))
但这似乎有点迂回,因为即使我没有尝试协调多个ref
的更改,我也使用了ref
。基本上我只是为了方便地访问成功交易中的旧值和新值而使用它。
我觉得我应该可以使用atom
代替。有没有比这更简单的解决方案?
(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))
这似乎也很迂回,因为我在每次拨打swap!
时都会添加和删除手表。但是我需要这个,因为我不希望手表闲置会导致在其他代码修改atom
时触发副作用功能。
重要的是,每次突变到原子时,副作用函数只被调用一次,并且只有当变换函数transform-item
实际返回一个新值时才是这样。有时它会返回旧值,产生新的变化。
答案 0 :(得分:2)
(when (not= @a (swap! a transform))
(do-side-effect))
但是你应该非常清楚你需要什么样的并发语义。例如,另一个线程可以在读取它并交换它之间修改原子:
我不清楚这个问题是否可取或不可取。如果你不想要这种行为,那么除非你用锁来引入并发控制,否则一个原子就不会做这个工作。
当你开始使用ref并询问一个原子时,我想你可能已经考虑过并发性了。从您的描述看来,ref方法更好:
(when (dosync (not= @r (alter r transform))
(do-side-effect))
您是否有理由不喜欢您的参考解决方案?
如果答案是"因为我没有并发"然后我会鼓励你使用ref。它并没有真正的缺点,它使你的语义显而易见。 IMO程序往往会增长并且达到并发存在的程度,Clojure非常善于明确存在时会发生什么。 (例如哦,我只是在计算东西,哦,我现在只是把这些东西作为一个网络服务公开,哦,现在我和我一起)。
无论如何,请记住像alter和swap这样的功能!返回值,因此您可以将其用于简洁的表达式。
答案 1 :(得分:0)
我遇到了同样的情况,只提出了2个解决方案。
:changed?
在atom中保持无意义:changed
标记以跟踪交换功能。并获取swap!
的返回值以查看是否有变化。例如:
(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)))
您可以在交换函数中抛出异常,并将其捕获到外部:
(try
(swap! data (fn [d] (if (changed?) d2 (ex-info "unchanged" {})))
(do-your-task)
(catch Exception _
))