Hash Maps as Mini State Machines

While working with Ryan Neufeld last Friday, we came across a problem that I had experienced more than once in my programming career: how to toggle a hash map value between two (or more) states.

As a starting example, let's assume that we have a hash map that contains a single key/value, and that we want to toggle the value between -1 and 1. We could write the function thusly:

(defn toggle-value
[m]
 (update-in m [:value]
 (fn [v]
 (if (>= v 0)
 -1

1))))

Now while this code will work, it's not exactly elegant, nor concise. And if we had more than two states (-1, and 1) then we would be forced to change the "if" into a "case" (or a "cond") and then add more and more state transitions to the "if" expression, and our already ugly code would get that much uglier.

It was at this part of my pairing session with Ryan that I remembered an old trick from when I first learned to program in QBasic (more than a few years ago). The trick was that multiplying either -1 or +1 by -1 will cause the resulting value to "toggle" between the two numbers. Actually this works for any states that need to toggle between -N and N, but we'll use -1 today to demonstrate:

(defn toggle-value [m]

(update-in m [:value] (partial * -1)))

This code is much more concise, but perhaps not as clear. However, for the problem Ryan and I were dealing with on Friday, we couldn't use numbers: we needed to use keywords. We had two states: ":checked" and ":unchecked" and possibly nil. We needed a clear, concise way to toggle between these three states, without writing an ugly case/cond block. In my mind I thought, "we need a function that will return a new state, given an old state. A function that takes a single arg, and returns the new state value." And then I remembered: "Clojure hash maps are functions! And they take a single value (key) and return a single result (value)." Well then, we can write our state transitions with this little bit of code:

(def toggle-states
 {:checked
:unchecked
 :unchecked :checked
 nil :checked})

(defn
toggle-value [m]
 (update-in m [:value]
toggle-states))

This is what I consider good Clojure code. It's simple, clean, concise, and easily extensible. And hopefully it comes in as handy for you as it did for me.

Get In Touch