有没有更好的方法在 Clojure 中将事物构建为原子?

Is there a better approach to building things up as atoms in Clojure?

为了构建数据结构,我发现自己做了很多事情,例如:

(let [foo (atom [])]
  (do
    (swap! foo conj {:foo "bar"})
    (swap! foo conj {:foo "baz"}))
  @foo)

=> [{:foo "bar"} {:foo "baz"}]

这是反模式吗?我用了很多原子。

首先,您不需要 do,因为它隐含在 let 中。然后,对于这个例子,普通的旧 -> 效果很好(使用 my favorite template project):

(ns tst.demo.core
  (:use tupelo.core tupelo.test))

(defn stuff
  []
  (-> []
    (conj {:foo "bar"})
    (conj {:foo "baz"})))
  
(dotest
  (is= (stuff)
    [{:foo "bar"}
     {:foo "baz"}])

另一个选项是用户 reduce:

(defn save-odds
  [accum arg]
  (if (odd? arg)
    (conj accum arg)
    accum))

<snip>

  (is= (reduce save-odds
         []
         (range 6))
    [1 3 5]))

话虽如此,恕我直言,使用原子作为累加器并没有错。它简单明了。而且,如果原子的“令人讨厌的”突变永远不会泄漏到您的函数之外,它就不会在程序的其余部分造成任何复杂性。

"If mutation occurs and no outside function is affected, does it really matter?"

毕竟reduce和小伙伴们内部也是用mutation的,都是“函数式”编程的优秀范例。也就是说,它们是纯函数(具有引用透明性),并且不会产生副作用。

这里不需要atom。您可以使用不可变数据结构:

(-> []
    (conj {:foo "bar"})
    (conj {:foo "baz"}))
;;=> [{:foo "bar"} {:foo "baz"}]

对于来自 OOP 或命令式语言的人来说,这可能是最困难的转变:避免可变性。