具有任意返回值的clojure交换

时间:2018-01-28 13:29:35

标签: concurrency clojure clojurescript

有没有办法swap!并返回任意值以及atom的值?

例如:

(swap! (atom {1 1 2 2 3 3 4 4 5 5})
         (fn [m]
           (loop []
             (let [i (rand-int 10)]
               (if (contains? m i)
                 (recur)
                 (assoc m i "ok"))))))

在上面的函数中,我无法知道哪个键被添加到地图中(除非我事先列出它们)。

我可以使用另一个原子来存储结果,但它有一些我更容易忽略的原因吗?

2 个答案:

答案 0 :(得分:3)

这是一种奇怪但通用的方法:

(defn swap-diff! [atom f & args]
  (loop []
    (let [curr-value @atom
          new-value (apply f curr-value args)]
      (if (compare-and-set! atom curr-value new-value)
        [new-value (data/diff curr-value new-value)]
        (recur)))))

这会在compare-and-set!中使用loop,直到成功为止,类似于how swap! works internally。成功时,它返回新值的元组和clojure.data/diff的输出。 diff输出将准确显示原子值的变化情况。

(swap-diff! (atom {1 1, 2 2, 3 3})
            #(loop []
               (let [i (rand-int 10)]
                 (if (contains? % i)
                   (recur)
                   (assoc % i "ok")))))
=> [{1 1, 2 2, 3 3, 9 "ok"} (nil {9 "ok"} {3 3, 2 2, 1 1})]

{1 1, 2 2, 3 3, 9 "ok"}是原子的新值。 (nil {9 "ok"} {3 3, 2 2, 1 1})diff的输出,其中第一项是旧值中的项,第二项是新项 值,第三个值是两者中的项。在您的情况下,您只关心新项目。

更新:如果您不想处理元组返回值,可以使用diff元数据标记返回值:

(defn swap-diff! [atom f & args]
  (loop []
    (let [curr-value @atom
          new-value (apply f curr-value args)]
      (if (compare-and-set! atom curr-value new-value)
          (with-meta new-value
                     (zipmap [:before :after :both]
                             (data/diff curr-value new-value)))
        (recur)))))

然后在结果上调用meta以获取diff:

(meta *1)
=> {:before nil, :after {9 "ok"}, :both {3 3, 2 2, 1 1}}

Clojure 1.9

使用新功能swap-vals!

,这会变得更加清晰
(defn swap-diff! [atom f & args]
  (let [[old new] (apply swap-vals! atom f args)]
    (with-meta new (zipmap [:before :after :both]
                           (data/diff old new)))))

答案 1 :(得分:1)

最简单的方法是将返回值和结果放在地图中:

(swap! (atom {:value {1 1 2 2 3 3 4 4 5 5}
              :return nil})
       (fn [{:keys [value]}]
           (loop []
             (let [i (rand-int 10)]
               (if (contains? value i)
                 (recur)
                 {:value (assoc value i "ok")
                  :return i})))))

请注意,您的示例函数不是纯函数(因为它使用rand-int),因此它不能正确使用swap!