Part 7 — A complete beginner’s guide to Computer Programming with Clojure: Atom versus Def.

Harvey Ellams
5 min readJan 2, 2021
Photo by Michael Dziedzic on Unsplash

Consider the following

(def x 5)x5(inc x)6x5

Let me explain, x is not a variable i.e. it does not change. For instance, by applying the inbuilt function inc (increment) to the value stored as x (5) we get 6. However, this is a temporary representation of x, as x does not change and will remain 5 unless a new def (definition) for x is created.

Photo by Chris Lawton on Unsplash

This highlights a key point with both Clojure and Functional programming, Mutability. Mutability is derived from the word mutate i.e. change. In Clojure, and functional programming, Mutable symbols are considered bad. In the example above, x cannot be modified. When we applied inc (increment), we simply stated the value of x should we increment it. Then it showed us this, potential, value of 6. Nevertheless, x remains unchanged with a value 5. However, we can use the result of inc x and pass it to another function or another def.

Mutability is considered bad, primarily, for two reasons. Firstly, it makes it difficult to understand some programs as changing variables often result in a change in state or output. Therefore, it is difficult to derive how a specific result or effect has occurred. This may be obvious to the programmer who wrote the code, but not to the person who didn’t. Secondly, the result or output may be wrong. For example, two different processes call upon the same variable. This can occur when a variable changes, and one of the functions access it incorrectly. Perhaps this function needed to access the variable after it changed, not before! Clearly, some sort of coordination is needed.

Consider the following Election scenarios

Photo by Element5 Digital on Unsplash

1. Alice is told the number of votes as 5410. Alice has collected 30 votes. As soon as she is told, she updates the total number of votes. To do this, she presses a button to erase the Total and enters the new total of 5440.

2. Bob is told the number of votes exactly the same time as Alice. Bob takes the 5410 votes and adds the 30 votes he has collected. He presses the erase button and enters the new Total as 5440.

3. Charlie takes the new Total as 5440.

Unfortunately, Charlie is wrong. The new Total should be 5470.

A better method

1. Alice is told the number of votes as 5410. Alice has collected 30 votes. As soon as she is told, she must update the Vote Total. To do this, she presses a button to erase the Total and enters the new total of 5440.

2. Bob is told the number of votes exactly the same time as Alice. Bob takes the 5410 votes and adds the 30 votes he has collected. He presses the erase button and enters the new Total as 5440.

3. Charlie knows that Alice changed the total from 5410 to 5440. This creates a new Total of 5440. Charlie knows that Bob also changed the old value from 5410 to 5440, a difference of 30. Charlie adds this 30 to the previous value of 5440 to creates a new value of 5470. Charlie is Atomic!

The second scenario presents a different approach to ensure the correct value is recorded when information is processed by two entities at the same time. In short, the second approach is able to apply coordination because this method is aware of all the changes made.

In Clojure, an atom is often used to hold values subject to change. An atom has memory. It can recall its previous state or value. In other words, it has greater coordination.

Consider the following

(def y (atom 5))@y5(swap! y inc)@y6

This time we have created an actual true variable using an atom. The @ sign is a short way of writing deref. Dereferencing means you call a mutable reference to an immutable value. The swap! instruction means that the old value is known and the change is done from the old value. This is what the term atomic means. In other words, if lots of simultaneous processes (known as threads) are trying to change the value it will allow each of these processes to make a change in turn. In addition, it remembers the previous change. So each thread will get its chance. This is often referred to as atomic concurrency, so all threads can occur in any order and the result will always be the same. To reiterate, this is the essence of functional programming.

The other option is reset!

Unfortunately, reset! just resets the value without regard to the previous value. Therefore, reset! is not atomic. In addition, swap! allows you to apply a function. For example, you would use inc with swap! because the initial value must be known in order for the function to work.

In the previous example,

(swap! y inc) will work.

Whereas,

(reset! y inc) will not work.

Another way to think of the difference between the two is that swap! cares about the current value whereas reset! does not.

SUMMARY

This is a fairly short post yet just as important as any other, if not more! Atomic concurrency is one of the most important concepts; related to both Clojure and functional programming. As mentioned in Post 1, computer code is now processed by a multitude of multicore-processors. Unfortunately, code produces threads which if not handled properly can cause serious issues.

To understand, allegedly, the consequences of misbehaving or badly written code, I urge you to read the following from Wikipedia:

In short, badly written code can do more harm than an army of state-sponsored hackers!

Previous

Part 8 — Functions

--

--