具有任意 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!
的正确用法
有没有办法将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!