Clojure:原子和参考的习惯用法?

时间:2013-04-14 02:05:04

标签: clojure idioms ref stm

我正在编写一些Clojure代码,它将引用一个map并在地图中增加一个键值对。我想我正在使用ref,但我不确定原子。我需要使用交换!更加惯用?我是STM和Clojure的新手,这看起来是线程安全/理智吗?我错过了什么?

(defn increment-key [ref key]
    (dosync
        (if (= (get @ref key) nil)
            (alter ref assoc key (atom 1))
            (alter ref assoc key (atom (inc @(get @ref key)))))))

(defn -main [& args]
    (def my-map (ref {}))
    (increment-key my-map "yellow")
    (println my-map)
    (increment-key my-map "yellow")
    (println my-map))

打印

$ lein run
#<Ref@494eaec9: {yellow #<Atom@191410e5: 1>}>
#<Ref@494eaec9: {yellow #<Atom@7461373f: 2>}>

1 个答案:

答案 0 :(得分:6)

执行此操作并不是非常惯用的Clojure:在持久数据结构中嵌入可变对象通常会破坏不可变数据结构的整个点。

我会完全避免内部原子,并且只有一个与键相关联的数字。然后my-map中包含的整个数据结构将是不可变的。

至于线程安全:它实际上取决于你将如何使用它。在这种情况下,ref似乎有点矫枉过正,因为当您需要协调跨多个参考的交易时,它才真正需要,而您在此处没有。可能atom足以满足您的目的。

以下是您可以解决的问题:

(defn increment-key [ref key]
  (swap! ref update-in [key] (fn [n] (if n (inc n) 1))))

(def my-map (atom {}))
(increment-key my-map "yellow")
(println my-map)  ;; => {"yellow" 1}
(increment-key my-map "yellow")
(println my-map)  ;; => {"yellow" 2}

编辑:更好的做法是将可变性保留在函数之外,并将increment-key定义为纯函数。

(defn increment-key [m key]
  (assoc m key (if-let [n (m key)] (inc n) 1)))

(def my-map (atom {}))
(swap! my-map increment-key "yellow")
(println my-map)   ;; => {"yellow" 1}
(swap! my-map increment-key "yellow")
(println my-map)   ;; => {"yellow" 2}