Clojure 仅当原子值更改时调用副作用函数
只有当原子的值发生变化时,触发副作用函数调用的最简单方法是什么 如果我使用的是Clojure 仅当原子值更改时调用副作用函数,clojure,clojurescript,Clojure,Clojurescript,只有当原子的值发生变化时,触发副作用函数调用的最简单方法是什么 如果我使用的是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)
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时都会添加和删除手表代码>。但是我需要这个,因为我不想让手表挂在那里,当其他代码修改原子时会触发副作用函数
重要的是,副作用函数在原子的每一次变异中准确调用一次,并且仅当转换函数transform item
实际返回一个新值时才调用。有时它会返回旧值,产生新的变化
(when (not= @a (swap! a transform))
(do-side-effect))
但是您应该非常清楚您需要什么样的并发语义。例如,另一个线程可能会在读取和交换atom之间修改atom:
a=1
线程1将a读取为1
线程2将a修改为2
螺纹1将a从2交换到2
线程1确定1!=2.打电话有副作用
从这个问题上我不清楚这是可取的还是不可取的。如果您不希望出现这种行为,那么atom将不会执行此任务,除非您引入带有锁的并发控制
当您从一个ref开始并询问一个atom时,我想您可能已经考虑过并发性了。根据您的描述,ref方法似乎更好:
(when (dosync (not= @r (alter r transform))
(do-side-effect))
您不喜欢ref解决方案有什么原因吗
如果答案是“因为我没有并发性”,那么我会鼓励您使用ref。它并没有真正的缺点,它使您的语义更加明确。IMO程序趋向于增长,并且达到了并发存在的程度,Clojure非常擅长于明确它存在时应该发生什么。(例如,哦,我只是在计算东西,哦,我只是将这些东西作为web服务公开,哦,现在我是并发的)
在任何情况下,请记住,类似alter和swap的功能!返回值,这样您就可以使用它来获得简洁的表达式。我遇到了同样的情况,并提出了两种解决方案
状态字段:已更改?
保持一个无意义的:在atom中更改标记以跟踪交换功能。并获取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 _
))
您的atom
解决方案甚至没有达到预期效果;如果原子的值在交换之前被另一个线程更改代码>完成后,手表将为该更改触发一次,并在交换后再次触发代码>完成。很好!add watch和remove watch没有封装在事务中,因此我实际上没有将监视隔离到对交换的特定调用代码>。谢谢。我不喜欢我的ref解决方案的原因是,根据文档,我认为ref是为“协调”状态保留的,所以我认为仅仅为了触发更改的副作用而使用它们肯定是错误的。但听起来这是最好的方法,因为您描述的原子语义不适合我的情况。我现在将自豪地讲述我的dosync,而不是带着羞耻和怀疑。