仅在原子值更改时调用副作用函数
Call a side effecting function only when atom value changes
仅当 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
实际上 return 是一个新值时。有时它会 return 旧值,产生新的变化。
(when (not= @a (swap! a transform))
(do-side-effect))
但是你应该很清楚你需要什么样的并发语义。例如,另一个线程可能会在读取原子和交换原子之间修改原子:
- 一=1
- 线程 1 读取 a 为 1
- 线程2修改a为2
- 线程 1 将 a 从 2 交换为 2
- 线程 1 确定 1 != 2 并调用 do-side-effect
我不清楚这个问题是可取的还是不可取的。如果您不想要这种行为,那么除非您引入带锁的并发控制,否则原子将不会完成这项工作。
看到您从引用开始并询问了原子,我认为您可能已经对并发性进行了一些思考。从您的描述看来,ref 方法更好:
(when (dosync (not= @r (alter r transform))
(do-side-effect))
你不喜欢你的 ref 解决方案有什么原因吗?
如果答案是 "because I don't have concurrency" 那么我还是鼓励您使用 ref。它并没有真正的缺点,它使您的语义明确。 IMO 程序往往会增长并达到并发存在的程度,而 Clojure 非常擅长明确说明当它存在时应该发生什么。 (例如,哦,我只是在计算东西,哦,我现在只是将这些东西公开为 Web 服务,哦,现在我是并发的)。
无论如何,请记住更改和交换等功能! return 值,因此您可以将其用于简洁的表达式。
我 运行 遇到了同样的情况,只是提出了 2 个解决方案。
状态字段:changed?
在原子中保留一个无意义的:changed
标记来跟踪交换功能。并取 swap!
的 return 值,看看是否发生了变化。例如:
(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)))
基于异常
你可以在 swap 函数中抛出一个 Exception,然后在外面捕获它:
(try
(swap! data (fn [d] (if (changed?) d2 (ex-info "unchanged" {})))
(do-your-task)
(catch Exception _
))
仅当 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
实际上 return 是一个新值时。有时它会 return 旧值,产生新的变化。
(when (not= @a (swap! a transform))
(do-side-effect))
但是你应该很清楚你需要什么样的并发语义。例如,另一个线程可能会在读取原子和交换原子之间修改原子:
- 一=1
- 线程 1 读取 a 为 1
- 线程2修改a为2
- 线程 1 将 a 从 2 交换为 2
- 线程 1 确定 1 != 2 并调用 do-side-effect
我不清楚这个问题是可取的还是不可取的。如果您不想要这种行为,那么除非您引入带锁的并发控制,否则原子将不会完成这项工作。
看到您从引用开始并询问了原子,我认为您可能已经对并发性进行了一些思考。从您的描述看来,ref 方法更好:
(when (dosync (not= @r (alter r transform))
(do-side-effect))
你不喜欢你的 ref 解决方案有什么原因吗?
如果答案是 "because I don't have concurrency" 那么我还是鼓励您使用 ref。它并没有真正的缺点,它使您的语义明确。 IMO 程序往往会增长并达到并发存在的程度,而 Clojure 非常擅长明确说明当它存在时应该发生什么。 (例如,哦,我只是在计算东西,哦,我现在只是将这些东西公开为 Web 服务,哦,现在我是并发的)。
无论如何,请记住更改和交换等功能! return 值,因此您可以将其用于简洁的表达式。
我 运行 遇到了同样的情况,只是提出了 2 个解决方案。
状态字段:changed?
在原子中保留一个无意义的:changed
标记来跟踪交换功能。并取 swap!
的 return 值,看看是否发生了变化。例如:
(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)))
基于异常
你可以在 swap 函数中抛出一个 Exception,然后在外面捕获它:
(try
(swap! data (fn [d] (if (changed?) d2 (ex-info "unchanged" {})))
(do-your-task)
(catch Exception _
))