为了构建数据结构,我发现自己做了很多事情,比如:
(let [foo (atom [])]
(do
(swap! foo conj {:foo "bar"})
(swap! foo conj {:foo "baz"}))
@foo)
=> [{:foo "bar"} {:foo "baz"}]
这是反模式吗?我用了很多原子。
此处不需要atom
。您可以使用不可变的数据结构:
(-> []
(conj {:foo "bar"})
(conj {:foo "baz"}))
;;=> [{:foo "bar"} {:foo "baz"}]
对于来自OOP或命令式语言的人来说,这可能是最难的转变:避免可变性。
首先,您不需要do
,因为它隐含在let
中。然后,对于这个例子,普通的旧->
工作得很好(使用我最喜欢的模板项目(:
(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]))
话虽如此,IMHO使用原子作为累加器并没有错。它很简单&直截了当的并且,如果";讨厌的";原子的突变永远不会泄漏到您的函数之外,它不会在程序的其余部分造成任何复杂性。
"如果发生突变并且没有外部功能受到影响真的很重要吗">
毕竟reduce
和朋友们内部也使用突变,他们是"功能性";编程。也就是说,它们是纯函数(具有引用透明度(,不会产生副作用。