Protocols: state, and default implementations

Hello there. I find myself needing some state management, which I currently do with a global atom, but it feels messy. I was thinking about putting state inside a record, OO style. Is this an anti-pattern? What could go wrong?

(defprotocol Stateful
  (do-something [this a] "")
  (get-state [this] "")
  (update-state [this] "")
  (reset-state! [this new-state] ""))

;; Define a record that implements the protocol
(defrecord Counter [id state-atom]
  Stateful
  (do-something [this a] (str "id" a))
  (get-state [this] @state-atom)
  (reset-state! [this new-state] (reset! state-atom new-state)))

Separately is there a good pattern for default implementation of protocols? I imagine in the example above if I had a few different Counter records all implementing the same protocol, get-state and reset-state! would be the same across. I’ve read about extend but it doesn’t seem to do exactly that?

Thank you!!

A proper answer to the “what to use for state” question depends entirely on the specifics of what that state is, how it’s used, what uses it.

Of course, ideally there’s no state at all. But it’s not always feasible.

A single atom is perfectly fine in some scenarios. If it feels messy for some reason, there might be ways to make it less so. E.g. if you write things like (get-in @state [:a :b :c]) everywhere, maybe it makes sense to define such things in a single location so that users of state only have to use (get-c @state). Or if you feel like too many functions concern themselves with the fact that it’s an atom, just don’t change the state at all - make functions that need changing the state return the new state instead, and update the actual state when there’s nothing else to change. Depending on the application, you might end up with a plain loop binding instead of an atom.

A single atom is not so fine when you have multiple independent states. Especially in multithreaded code where there can be atom contention. If state is composed of independent parts, each independent part could be in its own atom. Otherwise, you could use multiple refs.

For a “default implementation”, you could extend your protocol to Object, but Clojure discourages the sort of hierarchy you asked about, with a common Counter implementation: inheritance should not be for implementation reuse (even in other languages – it’s a code smell).

I think the sort of wrapping you’re doing here is generally discouraged in Clojure too: it doesn’t add any real value, and it introduces types that can make code overly proscriptive, brittle, and hard to change/extend. You’re not introducing a useful abstraction here – you’re really just creating Yet Another Atom type, when Clojure already has things like IDeref.

Thanks both for your help.

I guess I’m thinking too much in OO patterns, and I’m looking for multiple inheritance and some state in my objects.

Let me think it through - I don’t have (I think) atom contention so it works in the current implementation with all the shared state in a big atom, re-frame like, but indeed I write a lot of (get-in @state [counter-record-id :b :c] hence my idea to put that state inside the Counter.

One thing you might try is to lift the values out of your functions as parameters – so your code doesn’t end mixing the dereference and the navigation – and write some small, domain-specific fns to do the navigation so it’s easier to read/understand.

Right now, you’re complecting mutable state and structure, so try to tease that apart – this is part of the big, big shift from traditional OOP, where you usually have mutable state inside an object and all of the operations on it are also in the object. In Clojure, we try to keep mutable state at the edges, and have pure functions in the middle of our apps.