Clojure:atom和ref的惯用用法是什么?

3

我正在编写一些Clojure代码,它将获取到一个map的ref并对其中的key-value对进行递增操作。我认为我的ref使用正确,但是对于atom我不太确定。我需要使用swap!来更符合惯用语吗?我对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>}>

酷,谢谢,发布在那里。 - David Williams
1个回答

6
这并不是很符合Clojure的惯用法:在持久化数据结构中嵌入可变对象通常会破坏不可变数据结构的整个目的。我建议完全避免使用内部原子,而只需将数字与键相关联即可。然后,my-map 中包含的整个数据结构将是不可变的。
至于线程安全性:这实际上取决于你如何使用它。在这种情况下,ref 似乎有些过度了,因为只有在需要跨多个refs协调事务时才真正需要它,而你在这里没有这种情况。对于你尝试做的事情,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}

编辑:更好的做法是避免在函数中使用可变性,并将增量键定义为纯函数。

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

我非常感谢您的回复。 最近我一直在思考Clojure,尝试概念化我如何在命令式世界中执行某些似乎需要状态的操作。 例如,朴素贝叶斯分类器。 某个地方,必须计算出总数,然后我们必须从这些总数计算权重。 您有关于如何思考/建模Clojure上固有的“有状态”事物的任何建议吗? 是否有比传递变异引用更好的管理“状态”的方法? 我觉得我即将理解一些更大的东西,但现在卡住了... - David Williams
3
我很少传递refs/atoms。我的大多数Clojure函数都是纯的:它们使用纯值,并返回新的更改后的值。我甚至编写了一个完整的游戏,其中世界状态是单个不可变数据结构:https://github.com/mikera/alchemy 。"诀窍"在于将函数视为返回旧数据更新版本并更改某些内容的东西。 - mikera
等等,(lib/setup game) 不是会改变一个可变的 PersistentTreeGrid 的 game 吗?world.clj:31 - David Williams
1
顺便说一下,(fn [n] (if n (inc n) 1)) 就是 (fnil inc 0) - amalloy
1
@David:不是的,它返回一个新的Game,并初始化了:lib数据结构。请注意,该代码位于as->宏块中,因此game会被重新绑定到每个操作的结果上。这看起来有点像突变状态,但实际上它是一个纯函数。 - mikera

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接