Time, identity, and transition in Clojure

I think I finally understand the atomicity of time in Clojure’s modeling of state. Function application constructs time in Clojure: there is the value of an identity before a function’s application, and there is the value of the identity after the application. The timeline of a program, as far as state is concerned, is not measured in nanoseconds or any other periodic unit, but in the applications of functions that divide time into before and after. The higher-order functions that compose updates to Clojure’s mutable state references actually mark the ticking of coordinated time.

Separating identity from symbol

Idiomatic Clojure programs build new immutable values from old immutable values through the application of functions. We describe code that builds new values as though it were transforming existing ones. For instance, this code “adds one to age”:

(inc age)

and this one “updates my contact information”:

(assoc contact-info :address "123 Awesome Street")

Both expressions yield new values, but we talk about the results as though they were transformations. I think this suggests not that we misunderstand immutable values but that we consider identity as separate from symbol. The identity is the thing the code is modeling, not an artifact of the code. The symbol age and the expression (inc age) form a two-member series of values, each immutable, that share a conceptual identity: they both model someone’s age before and after an increment (i.e. a birthday).

Fixing identity to object

Clojure’s references—atom, ref, and agent—are mechanisms for fixing identities to objects. If I store my age in an atom, or my contact information in a ref, then I can apply functions to yield new values that become the value of the reference. As in the earlier examples, I am assigning an identity to a series of immutable values, but now the new value gives a new state to the reference. This is what mutable state means in Clojure: a single object now holds different, causally related values at different times.

The beauty of atom, ref, and agent is in the unity of their interfaces. You create each type of reference with a simple constructor function: atom, ref, or agent. You yield the value of each with deref. And you alter the value of each with a function that takes as arguments the reference, a function to apply to the reference’s value, and any additional arguments that function needs. I am taken with the form of these alteration functions, swap!, alter, and send. Compare their uses to the code examples above:

(def atom-age (atom 29))
(swap! atom-age inc) ; Thirtieth birthday number eight.

(def ref-contact-info (ref {:address "16 Boring Street"}))
(alter ref-contact-info assoc :address "123 Awesome Street"))

As a programmer, I find swap! and alter to be beautiful abstractions. They, along with dosync in the case of alter, encapsulate a lot of state management (e.g. compare-and-swap operations with spinning or optimistic updates with transaction restarting) and let me focus on composing an update to the reference’s value.

Moving the clock hand forward

The transition from one value to another also marks an atom of time. When you apply swap!, dosync/alter, or another referential update mechanism, you move the hands of a clock one tick forward. When I realized that this was the way time was conceived of in Clojure, Rich Hickey’s claims about introducing time into programs became much clearer.

One thought on “Time, identity, and transition in Clojure

Comments are closed.