我今天在IRC的 #clojure 频道上谈了这个,但是我想在这里详细介绍一下。基本上,为了更好地理解原子,swap!
,deref
和Clojure并发作为一个整体,我想尝试编写一个函数,它不仅返回使用{交换的值 - {1}},也是换出的值。
swap!
可以打印:
(def foo (atom 42))
.
.
.
((fn [a]
(do
(println "swapped out: " @a)
(println "swapped in: "(swap! a rand-int)))) foo)
但是,如果另一个帖子在swapped out: 42
swapped in: 14
swap!
和@a
的调用之间deref
相同的原子,那么我可能会交换一个不是42的值。
如何编写一个能正确回放两个值(换出和换入)的函数?
我不关心原子确实改变的各种值:我想知道的是所交换的值是什么。
这可以使用保证不会死锁的代码编写,如果是这样,为什么?
答案 0 :(得分:13)
Clojure的swap!
只是一个旋转的比较和设定。您可以定义一个可以返回任何内容的备用版本:
(defn alternate-swap [atom f & args]
(loop []
(let [old @atom
new (apply f old args)]
(if (compare-and-set! atom old new)
[old new] ; return value
(recur)))))
答案 1 :(得分:2)
原子是不协调的,因此在交换功能之外的任何尝试都可能会失败。你可以写一个你调用的函数而不是swap!它构造了一个在应用实函数之前保存现有值的函数,然后将这个构造函数传递给swap!
。
user> (def foo (atom []))
#'user/foo
user> (defn save-n-swap! [a f & args]
(swap! a (fn [old-val]
(let [new-val (apply f (cons old-val args))]
(println "swapped out: " old-val "\n" "swapped in: " new-val)
new-val))))
#'user/save-n-swap!
user> (save-n-swap! foo conj 4)
swapped out: []
swapped in: [4]
[4]
user> (save-n-swap! foo conj 4)
swapped out: [4]
swapped in: [4 4]
[4 4]
此示例打印它,将它们推送到存储在另一个原子
中的更改日志也是有意义的答案 2 :(得分:1)
如果你想要返回值,Stuart的答案是正确的,但是如果你打算做一堆println来理解原子/ refs是如何工作的,我会建议给atom / ref添加一个watch { {3}}
(add-watch your-atom :debug (fn [_ _ old new] (println "out" old "new" new)))
答案 3 :(得分:1)
你可以使用像:
这样的宏(defmacro swap!-> [atom & args]
`(let [old-val# (atom nil)
new-val# (swap! ~atom #(do
(swap! old-val# (constantly %))
(-> % ~args)))]
{:old @old-val# :new new-val#}))
(def data (atom {}))
(swap!-> data assoc :a 3001)
=> {:new {:a 3001} :old {}}
答案 4 :(得分:1)
参考交换值!自 1.9 起可用:https://clojuredocs.org/clojure.core/swap-vals%21
答案 5 :(得分:0)
您可以依赖承诺将当前值存储在swap!
操作中。然后在向量中返回新旧值,如下所示:
(defn- swap-and-return-old-value!
[^clojure.lang.IAtom atom f & args]
(let [old-value-promise (promise)
new-value (swap! atom
(fn [old-value]
(deliver old-value-promise old-value)
(apply f old-value args)))]
[new-value @old-value-promise]))