Clojure Atom: A Complete Guide

Want the best way to learn Clojure?

Invest in yourself with my Beginner Clojure Signature Course.

  • 8 fundamental modules
  • 240 fun lessons
  • 42 hours of video
Beginner Clojure: An Eric Normand Signature Course

Clojure is known for its powerful concurrency primitives. The most commonly used primitive is the atom. They hold state and let you safely read and modify it from multiple threads. This guide will teach you how they work and give you lots of code examples of how to use them.

My name is Eric Normand. I've used Clojure since 2008. I've taught it online and in person since 2013. I've worked at several companies using Clojure. I use atoms all the time, so I'm happy to share my knowledge and experience with you.

Before we start, I want you to trust that this guide will be worth your time. I guarantee that you will learn to use atoms effectively using this guide. If you read the whole thing and feel like you've still got questions, I'll answer them for you. You can email me. And if that doesn't work, we can schedule a call. That's my guarantee to you. Now let's get started.

What is an atom?

An atom is an object. You can think of it as a box.

An atom is a box

The box refers to one value (although that value can be a collection). It's meant to contain only immutable values.

An atom points to a value

This simple object allows multiple threads to read and write to it safely. Let's look at the interface for atoms.

atom — Creating an atom

To create an atom, you use the atom function. It takes one argument, which is the initial value inside the box.

(atom 0)

Very often, but not always, we will assign the atom to a global variable.

(def counter (atom 0))

I was always taught global mutable state was bad, but that's what we're doing, and it's super common in Clojure. The thing is, we don't make many of them (often just one), and we use them in a very controlled manner. It doesn't feel bad when I use it. It beats the alternative, which is little bits of state hidden in lots of objects.

However, it's also common to use atoms locally, for instance, by assigning them in a let:

(let [counter (atom 0)]
  ...)

deref — Reading from an atom

To read the value in the atom, you use the function called deref. deref will return the current value of the atom at the time you call it.

(deref counter) ;; => 0

Clojure has a shorthand for deref: You put a @ in front of the atom.

@counter ;; => 0

swap! — Modifying the value of an atom

The interface of atoms is very strict, which is what allows them to be shared so easily. The interface may be strict, but it's very easy to use.

We use the function called swap! to modify the current value of the atom. You pass it at least two arguments. The first argument is the atom you want to modify. That makes sense because the first argument is often the thing you're modifying.

The second argument is a function that takes the current value and returns the new value. This is a mutation function: It's a pure function that returns a modified copy of the first argument.

(swap! counter inc)

Incrementing an atom

inc is a built-in function that increments a number by one. So this code will take the current value of counter, call inc on it, and set the new value of counter to the result. It does this all safely, even if multiple threads are modifying it at the same time. We'll see why in a bit.

swap! can take more arguments than just those two. Any arguments you pass it will be passed, in order, to the mutation function after the current value.

For instance, we can do this:

(swap! counter + 10)

Adding 10 to an atom

The next value of counter will be (+ @counter 10). These extra arguments come in handy because a lot of mutation functions need more than just the value they are modifying.

swap! will return the new value of the atom.

The mutation function should be pure, that is, it shouldn't have any side effects. swap! might run your function multiple times. We'll see why when we talk about how atoms work.

swap-vals! — Modifying the value and returning the old value

Sometimes when you modify the value of an atom, you want to know what the old value was. You can use swap-vals! for that.

(swap-vals! counter + 10) ;; => [0 10]

We'll see some examples of when this is useful when we see how to use atoms to make a stack and queue.

reset! — Setting the value of an atom

If you want the atom to take a new value that isn't based on the current value, you want to use reset!.

(reset! counter 0)

This will set the value of counter to 0, no matter what the current value is. We use this often to get back to an initial value, though you can also do it simply to set the value without regard for the current value.

Building a mutable stack

Clojure's vector and list types both implement a stack discipline. You use three functions:

  1. conj — push a value onto the stack
  2. pop — pop a value off the stack, returning the new stack
  3. peek — get the top value on the stack

However, vectors and lists are immutable, so we can't use them for mutable state. However, we can use them inside of an atom to use its mutability. Here's how we'll do it.

First, we'll define a global mutable stack using an atom. The stack will start empty.

(def stack (atom []))

Then, we want to have two operations:

  1. push!
  2. pop!

Let's start with push!:

(defn push! [value]
  (swap! stack conj value)
  nil)

We use swap! to modify the current value of the stack. We use conj to add the value we pass in. Finally, we return nil because we want to signal that it has an effect (it's not pure).

Now let's do pop!:

(defn pop! []
  (let [[old _new] (swap-vals! stack pop)]
    (peek old)))

This one is a little more complicated. We use swap-vals! to get the old and new values. We don't need the new value, so we ignore it. We call swap-vals! with the pop function, which removes the top of the stack and returns the new stack. Then we call peek on the old value, because we want the top of the stack from before we popped.

And that's it! Here's a cool tip: You can make a queue by using a clojure.lang.PersistentQueue instead of a vector:

(def queue (atom clojure.lang.PersistentQueue/EMPTY))

Then you would define enqueue! and dequeue! functions in the same way:

(defn enqueue! [value]
  (swap! stack conj value)
  nil)

(defn dequeue! []
  (let [[old _new] (swap-vals! stack pop)]
    (peek old)))

As an exercise, modify these functions to take the stack or queue as arguments, instead of referring to them in the global namespace.

How atoms work

It's time to learn how atoms work.

Atoms give you a safe way to read and modify mutable state across multiple threads. They do this by using a compare-and-swap operation. This operation is implemented using the Java class java.util.concurrent.atomic.AtomicReference. Compare-and-swap is a way to modify a value only if it hasn't changed since you last read it. It's implemented in hardware and is thread-safe. It means that we can read a value, modify it, and write it back atomically.

Compare-and-swap lets you do this atomically:

  1. Read the current value
  2. Calculate a new value from the current value
  3. Set the new value if the current value is still the same

Great! But what happens if it's not the same? That's where the retry loop comes in:

  1. Read the current value
  2. Calculate a new value from the current value
  3. Set the new value if the current value is still the same
  4. If it's not the same, go back to step 1

This is why the mutation function you pass to swap! should be pure. It might get called multiple times. The more threads you have updating the value of the atom, the more contention you'll have. Contention is when two or more threads are competing to modify a value. Only one of them will win, and the others will have to retry, which means they'll have to run the mutation function again. This is why you should keep the mutation function pure and fast. It will be run in a loop in the thread until it wins. It sounds expensive, but typically this works just fine. Plus, reading the value of the atom with deref is fast and doesn't contend with other threads.

A theory of time

Atoms let you modify state over time. But there's a deeper theory behind it. It's called the epochal model of time.

The epochal model of time

The model works like this:

IF the atom starts at a valid state AND IF the transition functions always produce the correct next states THEN the observers will always see valid states, no matter when they deref the atom.

This is the guarantee of the atom. Just make sure your initial state is valid and the transition functions return valid values, and you're good.

You don't have to worry about the order the transitions happen in. You don't have to worry about when you read the value. All you have to worry about, really, are the transition functions, which can even be ensured individually.

Holding many values within one atom

Atoms do not let you coordinate changes across multiple atoms. That is, if you need to keep two atoms in sync as they change, atoms don't give you a mechanism to do that. If you need to do that, use a ref, which has transactional semantics across multiple refs.

But you can still use atoms for maintaining multiple mutable values. The way you do it is by putting a collection into the atom, typically a map. Each key in the map can hold a different value. Then the function that modifies the atom can modify all of the values in the map.

Let's see an example. Let's say we want to calculate the average of a lot of numbers, each of them calculated in different threads. We need to keep track of the sum and the count. We can do it like this:

(def average (atom {:sum 0 :count 0}))

Then we can swap an update like this:

(defn add-number! [n]
  (swap! average (fn [{:keys [sum count]}]
                   {:sum (+ sum n) 
                    :count (inc count)})))

But I prefer to use the threading macro for this:

(defn add-number! [n]
  (swap! average #(-> %
                    (update :sum + n)
                    (update :count inc))))

Of course, you can make your map as complex as you need. The update function is super useful for working with nested values.

watches — Running a function when the value changes

One cool thing to do with atoms is to watch them. A watch is a function that gets called whenever the value of the atom changes. You can use it to trigger a side effect.

This code will print a message whenever the value of counter changes:

(def counter (atom 0))
(add-watch counter :log (fn [key atom old new]
                          (println "Counter changed from" old "to" new)))

add-watch takes three arguments:

  1. The atom
  2. A key (which will get passed to the function to differentiate it from other watches)
  3. The watch function to call

The watch function needs to take four arguments:

  1. The key of that watch (in this case, :log)
  2. The atom it was called on
  3. The old value of the atom
  4. The new value of the atom

The key and atom are passed in in case you want to reuse that function for other atoms and keys.

You can remove a watch later like this:

(remove-watch counter :log)

set-validator! — Throw out invalid values

We've been talking about valid states in the atom. Atoms come with a way to validate the new value before it's stored in the atom.

You can set the validator for an atom using set-validator!. The validator is a function of one argument that returns falsey (or throw an exception) if the new value is not valid.

Let's say we only want our counter to be non-negative. We can set a validator like this:

(def counter (atom 0))
(set-validator! counter #(not (neg? %)))

Now when we try to set it to something negative, it will throw an exception:

(swap! counter dec)
; Execution error (IllegalStateException) at user/eval8075 (REPL:69).
; Invalid reference state

You can remove the validator like this:

(set-validator! counter nil)

Note that the atom will run the validator when you first set it. If the current value is not valid, it won't let you set the validator:

(def counter (atom 0))
(set-validator! counter pos?) ;; 0 is not positive
; Execution error (IllegalStateException) at user/eval8075 (REPL:69).
; Invalid reference state

compare-and-set! — Setting the value if it's a certain value

There's one more function in the atom interface that is way less commonly used, but for completeness, let's talk about it. I've never actually used it. But you can if you need it.

compare-and-set! takes three arguments. The first is the atom you want to modify. The second argument is the value you expect to be in the atom. And the new value is the third argument.

compare-and-set! will only set the value if the current value is identical to the expected value. If it's not, it will do nothing and return false. If it is, it will set the value and return true.

Note that identical means reference equality. That is, the expected value is compared to the current value using identical?. This is important and can cause problems if you're used to thinking in terms of value equality, which uses =.

Conclusion

So that's atoms, the most popular concurrency primitive in Clojure. They let you create a single mutable reference that can be read and modified safely across multiple threads. Their strict interface lets you reason about their behavior easily.

If you're interested in more concurrency primitives, check out my Clojure Concurrency Tutorial, which contains a large catalog of concurrency primitives available in Clojure.

Atoms are used in Reagent for storing state that components react to state changes. You can read about them in my Reagent Tutorial.

If this guide didn't answer your questions, please email me (eric@ericnormand.me) and I'll answer them as best I can. You can also drop into my Office Hours to ask me questions live. I'm happy to help.

Want the best way to learn Clojure?

Invest in yourself with my Beginner Clojure Signature Course.

  • 8 fundamental modules
  • 240 fun lessons
  • 42 hours of video
Beginner Clojure: An Eric Normand Signature Course