仅在原子值改变时调用副作用函数

时间:2016-02-22 00:48:23

标签: clojure clojurescript

只有在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实际返回一个新值时才是这样。有时它会返回旧值,产生新的变化。

2 个答案:

答案 0 :(得分:2)

(when (not= @a (swap! a transform))
  (do-side-effect))

但是你应该非常清楚你需要什么样的并发语义。例如,另一个线程可以在读取它并交换它之间修改原子:

  1. a = 1
  2. 线程1读取为1
  3. 线程2将a修改为2
  4. 线程1将a从2交换为2
  5. 线程1确定1!= 2并调用do-side-effect
  6. 我不清楚这个问题是否可取或不可取。如果你不想要这种行为,那么除非你用锁来引入并发控制,否则一个原子就不会做这个工作。

    当你开始使用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 _
    ))