THX1137
THX1137

Reputation: 983

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

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

Answers (2)

Sean Corfield
Sean Corfield

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

Alan Thompson
Alan Thompson

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

Related Questions