具有任意 return 值的 clojure 交换

clojure swap with arbitrary return value

有没有办法将swap!和return一个任意值连同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"))))))

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

我可以使用另一个原子来存储结果,但是我忽略了一些更简单的东西吗?

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

(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)))))

这在 loop 中使用 compare-and-set! 直到成功,类似于 how swap! works internally。当它成功时,它 return 是新值和 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 的输出,其中第一项是旧值中的项 only,第二项是项 only 在新值上,第三个值是 中的项目。在你的情况下,你只关心新项目。

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

(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 以获得差异:

(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)))))

最简单的方法是将 return 值和结果都放在映射中:

(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!

的正确用法