Reputation: 983
To build up a data structure I find myself doing a lot of things like:
(let [foo (atom [])]
(do
(swap! foo conj {:foo "bar"})
(swap! foo conj {:foo "baz"}))
@foo)
=> [{:foo "bar"} {:foo "baz"}]
Is this an anti-pattern? I'm using a lot of atoms.
Upvotes: 1
Views: 122
Reputation: 6666
No need for an atom
here. You can use immutable data structures:
(-> []
(conj {:foo "bar"})
(conj {:foo "baz"}))
;;=> [{:foo "bar"} {:foo "baz"}]
For folks coming from OOP or imperative languages, this is probably the hardest shift: avoiding mutability.
Upvotes: 6
Reputation: 29984
First off, you don't need the do
since it is implied inside let
. Then, for this example, plain old ->
works great (using 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"}])
Another option is to user reduce
:
(defn save-odds
[accum arg]
(if (odd? arg)
(conj accum arg)
accum))
<snip>
(is= (reduce save-odds
[]
(range 6))
[1 3 5]))
Having said that, there is nothing wrong IMHO with using an atom as an accumulator. It is simple & straightforward. And, if the "nasty" mutation of the atom never leaks outside of your function, it cannot cause any complexity in the rest of the program.
"If mutation occurs and no outside function is affected, does it really matter?"
After all, reduce
and friends also use mutation internally, and they are excellent examples of "functional" programming. That is, they are pure functions (have referential transparency), and cause no side effects.
Upvotes: -1