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

Dav*_*ams 3 idioms clojure 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))
Run Code Online (Sandbox Code Playgroud)

打印

$ lein run
#<Ref@494eaec9: {yellow #<Atom@191410e5: 1>}>
#<Ref@494eaec9: {yellow #<Atom@7461373f: 2>}>
Run Code Online (Sandbox Code Playgroud)

mik*_*era 6

这样做不是非常惯用的Clojure:在持久数据结构中嵌入可变对象通常会破坏不可变数据结构的整个点.

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

至于线程安全:它实际上取决于你将如何使用它.ref在这种情况下,A 似乎有点矫枉过正,因为当您需要协调跨多个参考的交易时,它才真正需要,这是您没有的.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}
Run Code Online (Sandbox Code Playgroud)

编辑:更好的方法是保持函数的可变性,并将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}
Run Code Online (Sandbox Code Playgroud)

  • 我很少传递refs/atoms.我在Clojure中的大多数函数都是纯粹的:它们采用纯值,并返回新的更改值.我甚至编写了一个完整的游戏,其中世界状态是一个单一的不可变数据结构:https://github.com/mikera/alchemy."技巧"是将函数视为返回旧数据的更新版本的东西,其中包含更改的内容, (3认同)