Handling Changes with Reference Types

Malina Tran
Tech and the City
Published in
3 min readJul 28, 2016

We gotta make a change…
It’s time for us as a people to start makin’ some changes.
Let’s change the way we eat, let’s change the way we live
and let’s change the way we treat each other.
You see the old way wasn’t working so it’s on us to do
what we gotta do, to survive.
Some things will never change…

– 2Pac, Changes

My first introduction to Clojure was by of a talk by the creator, Rich Hickey. It was a very philosophical lecture, and stretched my brain in unexpected ways. Now that I’ve got a better handle of functional programming language, the underlying metaphysics of it and comparison with object-oriented programming make more sense.

In object-oriented programming, objects exist and do things. They act upon each other, change state, and lead other things to happen. And this makes sense because it matches our sense of reality. Daniel Higginbotham profoundly states: “[Objects] act on each other, changing state as the program runs. Again, this conforms to our intuitive sense of the world: change is the result of objects acting on each other.”

In Clojure, things are not represented as objects. Rather, they are a succession of values, which are “atomic” (represent a single irreducible unit or component in a larger system) and unchanging and stable entities. Examples of values are numbers and data structures, which don’t change when mutated. Instead, they derive a new number or new map when you apply a “process” to value. That’s sensible enough.

With this in mind, let’s talk about identity, which is not tied to a single albeit changing object. In Clojure, “identity” refers to a succession of unchanging values produced by a process over time; a “name” helps to designate identity. And lastly, a “state” is the value of an identity at a point in time, and identity is a handy way to refer to a succession of values produced by some process.

You might be wondering how anything happens in Clojure, if values don’t act on each other and can’t be changed. Changes occur when: 1) a process generates a new value, and 2) there is an association between the identity and new value. Clojure uses the following reference types to navigate these changes:

  • Atoms set a succession of related values with an identity. To get an atom’s current state, you would dereference it (which will never block). To update the atom so it refers to a new state, you use `swap!` which receives an atom and a function as arguments. Technically speaking, the atomic values do not change — but the atom reference type can be updated and assigned a new value. To update an atom without checking its current value, you can use `reset` function.
  • Refs are used when you want to update more than one identity using transaction semantics. Refs are modified with `alter!` and it must be used within a transaction that is initiated by `dosync`. Similarly, `commute!` allows updating a ref’s state within a transaction but its behavior at commit time is different. Whereas `alter` compares the current state to the original state and make the transaction retry if there is a difference, `commute` is run and committed. You’d use `commute` to help with performance, but you must be vigilant that refs do not lead to an invalid state.
  • Vars are associations between symbols and objects, and can be created with `def`. They’re used for dynamic binding and their “roots” can be altered. Vars are used to name a resource that one or more functions target. When a new var is created, its initial value is considered the root; to change it, you’d use the `alter-var-root` function or temporarily alter the root `with-redefs`. Because of its effects, `with-redefs` is used only in testing as mock data (I was so excited to learn about this and use it earlier this week!).

There is tons more that I didn’t include in my recap of chapter 10 of Clojure for the Brave and True. Hope you enjoyed my summary of how to safely handle concurrent tasks and refer to new values!

--

--