Atomicity to the Rescue
Functional programming languages are known to be stateless and immutable. Essentially, they do not store states and values. Very explicitly, Clojure documentation says: “Clojure does not have mutable local variables.” Ruby, for example, allows you to have instance variables, which you can easily get and set— perhaps too easily. Clojure has functions that allow you to create new values from existing ones. For non-local mutable state, there are definitely options, which I describe in a prior post that thinly veiled my obsession and love for Tupac.
I found this very fact about functional languages to be a non-issue until I stumbled upon a dilemma. I was passing the value of the computer player’s marker to several functions that didn’t require it, all for the sake of it eventually reaching a function that would use it. Using atoms seemed to be the best tool for the job and its implementation is pretty straightforward.
Atoms allow you to store and update values instantaneously. Remember how, back in the day, we defined atoms as representing “a single irreducible unit or component in a larger system”? In computer science, being atomic means that you’re guaranteeing isolation from concurrent processes. Let me delve into this further, since I consistently read about things being “atomic” without fully understand its meaning. Operations that are considered atomic either succeed or fail, meaning they either change the state of the system or have no effect whatsoever. For example, “atomic commits” will push updates to a database and either all of those changes will be submitted (success) or none of them will (failure). This allows data to not be corrupted. Clojure manages the mutation of the value and guarantees atomicity.
Here’s how you get started with atoms. To define an atom, you invoke the atom function value with an initial value. In the example of my computer player’s marker, I define it with `nil`.
(def computer-marker (atom nil))
To reference the atom, you could use `@computer-marker` or `(deref computer-marker)`. For testability purposes, I created a method that handles the actual updating of the atom’s value. It looks like this:
(reset! computer-marker marker))
There are two ways to apply state changes to atoms; from my previous post, I mention swap and reset. From the above example, you can see that I used `reset!` rather than `swap!` (the latter receives an atom and function as its arguments whereas the former receives the atom and a new value). Why, you may ask? Based on the name alone, you get the sense that you’re resetting the atom back to its initial state. And that’s exactly what happens: you don’t care about or rely on the previous value (just as I don’t care about `nil` being the placeholder value). Also, the case for not using `swap!` is simply because I’m not passing in a function. If I tried swapping instead of resetting, I get this error:
Exception in thread “main” java.lang.ClassCastException: java.lang.String cannot be cast to clojure.lang.IFn, compiling:(/private/var/folders/dy/x769x24n53xbsxphz77svwdc0000gn/T/form-init3976670291605213294.clj:1:125)
Yikes. So, I wrote a couple of tests to ensure that reset was accomplishing its job. The first test assures that the return value is the marker being passed in. The second test assures that the atom is successfully set to “X” when its initial value was `nil`.
(it “returns new value of computer marker”
(should= "X" (update-computer-marker “X”)))))
(it "returns updated value of computer marker"
(with-redefs [computer-marker (atom nil)]
(should= (deref computer-marker)