Clojurian thought: A Clojure programming language blog

Fix your Clojure code: Clojure comes with design patterns (Part 1)

Clojure either directly supports, or easily supports the use of common design patterns like singleton, command, observer, visitor

Fix your Clojure code: Clojure comes with design patterns (Part 1)
Photo by Christina @ wocintechchat.com / Unsplash
This post will be part of a series since there are few dozen software design patterns I want to cover, but writing about all of them in one post will take ages.

Patterns

A motivational story

Suzi, after busting her ass at her first Clojure job, lands her second Clojure job at StartupAI. This company moved her half-way across the country to the Big City™, filling her with naive excitement. When she finishes company on-boarding, Suzi submits her first pull request after several days of staggering through the code. While the code is the same Clojure she has always known and loved, the repository gives her stress headaches.

The persistence namespace, the one with all the database queries, has eight different functions to get the same data using same query. Next, a minuscule change in the application layer causes dozens unit tests to fail, and to top it off, it takes three weeks to a pull request to get approved for the change.

"I moved across the country for this?!".

Here we go again

Although I'm sure some people will say it is, Suzi's dilemma isn't unique. Large Clojure codebases sometimes suffer from a lack proper care and abstraction. I suspect a lot of people rely too heavily on the pure function. Functions are great, but over time they create a codebase laden with high coupling and low cohesion. So, how do we help Suzi create a codebase satisfying our desired "ilities"? Lucky for Suzi, Clojure either directly supports, or easily supports design patterns like singleton, command, observer, visitor, and iterator. Before we get into those patterns though, we need to get little more abstract than the pure function.

We're speaking the same language

Let's start with the abstraction just above the function: Protocols. Protocols are Clojure's analogue to the interface. At a higher level, we can call a bag of function inputs and their outputs (or function signatures) an interface. Without diving into full-blown type theory, when something satisfies the interface, we can also say that something is a type of that interface. I'll even argue that we don't need a formal programming construct to achieve this "typehood". On a basic level, by definition, every Clojure sequence supports the functions cons ,first ,rest , and  count. Does that mean we should go out and create our own collection types? Not likely, the built-in types are quite good, but we do have few handy constructs for creating or extending types if we want.

(defprotocol MoneySafe
  "A type to convert other money type to bigdec"
  (make-safe [this] "Coerce a type to be safe for money arithmetic"))

(extend-protocol MoneySafe
  java.lang.Number
  (make-safe [this] (bigdec this))

  java.lang.String
  (make-safe [this]
    (try
      (-> this
          (Double/parseDouble)
          (bigdec))
      (catch NumberFormatException nfe
        (println "String must be string of number characters"))
      (catch Exception e
        (println "Unknown error converts from string to money")
        (throw e))))

  clojure.lang.PersistentVector
  (make-safe [this]
    (try
      (let [num-bytes (->> this (filter int?) (count))]
        (if (= (count this) num-bytes)
          (->> this
               (map char)
               (clojure.string/join)
               (Double/parseDouble)
               (bigdec))
          (throw (ex-info "Can only convert from vector of bytes"
                          {:input this}))))
      (catch NumberFormatException nfe
        (println "Vector must be bytes representing ASCII number chars")
        (throw nfe))
      (catch Exception e
        (println "Error converting from vector of bytes to money")
        (throw e)))))

(defn ->money
  [x]
  (make-safe x))
  
;; (+ (->money "0.1") (->money 0.1) (->money [0x30 0x2E 0x31])) => 0.3M
Dispatching on Protocols

While we could define types with functions like sequence, we would lose the biggest benefit of the protocol construct, Polymorphism.


Null Object

Create an empty implementation for an interface.

By my "interface is a bag of functions" reasoning, nil makes a good Null Object for sequences, collections, and maps (or maybenil is a sequence, collection, and map, but I'll leave that to the Philosophers of Computer Science ;).

When to use it

When simulating the behaviour of a type without affecting the process.

Clojure analogue

nil - Clojure sequence, collection, and map operations all support nil as their input sequence, collection, and map, respectively, either returning an empty sequence, collection, map, or nil.

Keep in mind

Not everything supports nil, and nil is different than Java's null pointer reference, yet some gotchas with nil can throw NullPointerExecption, so some defensive programming with if or some->/some->> might be needed to avoid pitfalls.

Sample Code

Repl output:

user> (:keyword nil)
;; nil
user> (get nil :hi)
;; nil
user> (assoc nil :hi "bye")
;; {:hi "bye"}
user> (first nil)
;; nil
user> (rest nil)
;; ()
user> (next nil)
;; nil
user> (count nil)
;; 0
user> (nth nil 10)
;; nil
user> (pop nil)
;; nil
user> (peek nil)
;; nil
user> (println "You get the idea.")
You get the idea.
;; nil

Singleton

Ensure a class has one instance, and provide a global point of access to it.

A less philosophical pattern, the singleton appears a lot in Clojure codebases. Thanks to software transactional memory, we don't have to worry about non-atomic operations, but it's still an anti-pattern because one piece of client code may update the atom with something another piece of client code doesn't expect, causing a hard-to-debug error.

When to use it

Just don't.

Clojure analogue

defonce macro and an atom/agent

Keep in mind

Global state is best to be avoided unless absolutely necessary. Global configuration, database connection, and web servers are arguably important enough objects to use this, since they're the stateful sole instance and must stick around for the process runtime, but most Clojurians go in for a state management library or framework like Component, Mount, or Integrant.

Sample Code
(defonce web-server (atom nil))

(defn start-server
  []
  (if @web-server
    (log/warn "Server is already running!")
    (reset! webserver (run-jetty routes {:port (parse-int (:port config))
                                         :join? false}))))


Command

Encapsulate a request as an object, thereby letting users parameterize clients with different requests, queue or log requests, and support undoable operations.

The errors from functions are a lot easier to debug though because of stack traces. Our command pattern, the object-oriented version of callbacks, can be implemented in Clojure via higher-order functions (obviously).

When to use it

When you need to decouple the execution of a block of code from when it needs to be called.

Clojure analogue

Higher-order functions. All functions in Clojure can be passed around as arguments, and support closures.

Keep in mind

If you're passing around lambdas a lot, use the fn form and give it a name to help with debugging. The road to callback hell is paved with #(...) lambdas.

State with Callbacks are typically considered an anti-pattern, but JavaScript promises are a thing, so pick your poison.

Sample Code
;; Trivial example
(defn fire
  "fire!!!"
  []
  (println "Fire the gun"))

(defn jump
  "jump!!!"
  []
  (println "Jump"))

(defn run
  "run!#!#"
  []
  (println "Run enabled"))

(defn diagonal-aim
  "Shoot things on the walls"
  []
  (println "Aiming diagionally"))

(defn remap-button
  "Remap a player's button to a function.
  Can be used on the remap menu screen, or
  for temporary game mechanics."
  [player button f]
  (swap! player assoc-in [:controls button] f))

;; (remap-button player :a-button fire)

(defn do-buttons!
  "Execute the button actions assigned to the
  players controller map."
  [button-pressed controllor-map]
  (let [{:keys [a-button
                b-button
                y-button
                x-button]} controller-map]
    (case button-pressed
      "A" (a-button) ;; "Execute" the command!
      "B" (b-button)
      "X" (x-button)
      "Y" (y-button))))

Doing, redoing, and undoing

Because I know people will say it, here is an example of the command pattern supporting undo with all the previously made edits. Supporting redo will be left as an exercise for the reader.

(defn do-undo
  "Create a command and undo over a pure function"
  [f]
  (let [state (atom nil)]
    {:exec (fn execute!
             [& args]
             (let [v (apply f args)]
               (swap! state conj v)
               v))
     :undo (fn undo!
             []
             (swap! state pop)
             (peek @state))}))

(defn change-cell
  "A supposedly pure function"
  [s coord]
  (assoc sheet coord s))

(let [{:keys [exec undo]} (do-undo change-cell)]
  (def change-cell-cmd exec)
  (def change-cell-undo undo))

Observer

Define a one-to-many dependency between objects so that when one object changes state, all the dependents are notified and updated automatically.

I see a lot of functions written to watch for something to happen. Typically, developers will use a loop over a function until some state changes or "event" transpires. This works, but couples the event to the event reaction. Object-oriented programming addresses this with the Observer pattern, but we can address it with a Clojure watch.

When to use it

When you want to decouple changes in state from what the state affects, also known as a publish/subscribe pattern.

Clojure analogue

There are two ways to do this in Clojure:

  • Clojure watches. You can add a watch to any atom, agent, var, or ref with add-watch and remove it with remove-watch.
  • core.async's pub and sub. pub creates a channel from a existing channel with optional topic, and then other channels can sub to the pub channel.
Keep in mind

When the watch target (the var/atom/agent/ref) changes, all the watch functions will be called syncronously even if multiple threads are manipulating the watch target. So, it's best to use the new-state argument in all your watch functions.

Sample Code
(defn unlock
  "Unlocks the achiement given"
  [player k]
  (when-not (unlocked? player k)
      (do-unlock! player k)))

(defn on-bridge?
  "predicate to see if player is on the bridge"
  [player]
  (is-colliding? (get @entities :bridge) @player))

(defn achievement-toaster
  [k]
  (let [a (get achievements :player-fell-achievement)]
      (when a
        (swap! entities :current-toaster a))))

(defn unlock-achievement
  [key player old-state new-state]
  (case (:last-event new-state)
    :player-fell (when (on-bridge? new-state)
                   (unlock player :player-fell-achievement)
                   (achievement-toaster :player-fell-achievement))))

(defn watch-actions
  [player]
  (add-watch player :achievements unlock-achievement))

State

Allow an object to alter its behavior when its internal state changes. The object will appear to change it's class.

We might not care about reacting to the state change though. Heck, we might not even care how the state happens, we just care about the results. A state pattern can give use a nice function to conceal our messy state transitions, so we don't have to write giant cond statements.

When to use it

The real thing to remember here is that we want to model a finite state machine (FSM) with our functions.

Clojure analogue
  • (atom f) - An atom over a function is the simplist version of the state pattern.
  • (loop [state (init-state data)] (recur (state data))) - Alternatively, the the current state could be re-bound a loop binding since the current state function always returns the next state.

This take uses a multimethod over an atom. This certainly models an FSM, but we kind of lose our ability to decouple the state from the context.

Keep in mind
  • The current state will always return the next state in the machine. Modeling state this way can be tricky, but can be super helpful if you find yourself with many nested case or cond forms. You'll have to decide of the cyclomatic complexity is worth the trade-off.

  • When your state transition returns one state, this method is fine. If you need more functions in a single 'state', I'd consider using protocols to create states. I intentionally left this out of the sample code because it was huge.

  • If you need a history for your state machine, like a push-down automata, you can conj each state function to a vector and peek at the top.

  • The atom-free state pattern with loop bindings can be problematic if the current state needs to modify the binding itself. Use the atom version instead.

Sample Code
(defn play-game
  [game]
  (let [{:keys [components player]} game]
    (do
      (doseq [component components]
        (update! component))
      (draw-game game)
      (if (= :paused (:action @player))
        pause-menu
        play-menu))))

(defn start-menu
  [game]
  (let [{:keys [menu player]} game]
    (do
      (draw-game game)
      (if (= :affirm (:action @player))
        (when-let [menu-item (collision player)]
          (cond ;; our state transition
            (= menu-item :start-game) play-game
            (= menu-item :options) options-menu
            (= menu-item :quit-game) quit-game
            :else start-menu))))))

(let [game-state (atom start-menu)]
  (defn start-game
    [game]
    (reset! game-state (@game-state game))))
    
(defn main
  []
  (let [game-engine (game-engine)]
    (loop [quit? (:quit game-engine)]
      (when-not quit?
        (start-game game-engine)
        (recur (:quit game-engine))))))
                   
;; or stateless 'state'
(defn stateless-start-game
  [game]
  (start-menu game))

(defn stateless-main
  []
  (let [game-engine (game-engine)]
    (loop [game-state (start-game game-engine)]
      (when-not (:quit game-engine)
        (recur (game-state game-engine))))))

Visitor

Represent an operation to be performed on elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.

Giant case statements are sometimes addressed with multimethods. That's a valid strategy, but multimethods give us multiple dispatch for free.

When to use it

When you want to create new functionality without changing the existing code structure.

Clojure analogue

multimethods. Clojure's multimethods offer dynamic dispatch functionality right out of the box, so we can put a multimethod inside of a multimethod to get the double dispatch functionality.

Keep in mind

Multimethods have a performance penalty. You're unlikely to experience it, but it is there, so just be mindful of it in performance sensitive applications. If you need fast dispatching, consider dispatch on protocols instead.

Sample Code
(defrecord Equipment [elements])
(defrecord Sword [name])
(defrecord QuestItem [name])
(defrecord Armor [name])

(defmulti visit (fn [f x y] [(class x) (class y)]))

(defmethod visit :default [f x y])

(defmethod visit [Equipment Object] [f equipment other]
  (let [{:keys [elements]} equipment]
    (doseq [element elements]
      (f element other))))

(defmulti do-stuff (fn [x y] [(class x) (class y)]))

(defmethod do-stuff :default [x y]
  (println (format "You sit there bewildered, unable to understand what to do with %s and %s" x y)))

(defmethod do-stuff [Sword Long] [x y]
  (println (format "Swing your sword %s does %d damage" (:name x) y)))

(defmethod do-stuff [Sword String] [x y]
  (println (format "Using your sword %s, you make a noble gesture %s" (:name x) y)))

(defmethod do-stuff [Armor Long] [x y]
  (println (format "Your armor %s has prevented %d damage" (:name x) y)))

(defmethod do-stuff [Armor String] [x y]
  (println (format "You remove armor %s as a noble gesture %s" (:name x) y)))

(defmulti store-stuff (fn [x _] (class x)))

(defmethod store-stuff :default [x y]
  (println "You stored: " x))

Repl output:

user> (def equipment (->Equipment [(->Sword "Excalibur") (->Sword "Gramr") (->Sword "Zulfiqar") (->Sword "Durendal") (->QuestItem "dungeon key") (->Armor "Achilles")]))
;; #user/equipment
user> (visit store-stuff equipment 10)
You stored:  #user.Sword{:name Excalibur}
You stored:  #user.Sword{:name Gramr}
You stored:  #user.Sword{:name Zulfiqar}
You stored:  #user.Sword{:name Durendal}
You stored:  #user.QuestItem{:name dungeon key}
You stored:  #user.Armor{:name Achilles}
;; nil
user> (visit store-stuff equipment nil)
;; nil
user> (visit do-stuff equipment 50)
Swing your sword Excalibur does 50 damage
Swing your sword Gramr does 50 damage
Swing your sword Zulfiqar does 50 damage
Swing your sword Durendal does 50 damage
You sit there bewildered, unable to understand what to do with user.QuestItem@bb0c2cb0 and 50
Your armor Achilles has prevented 50 damage
;; nil

Iterator

Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.

Like our Visitor pattern, the Iterator doesn't really reveal anything special. We can use it without any extra effort which makes this section fluff. Enjoy!

When to use it

When you want to have an ordered, sequential container for other objects which sounds an awful lot like a sequence or collection.

Clojure analogue

Lists and Vectors - Clojure list and vector types are sequence and collection, respectively, and support arbitrary typed elements. They have a rich set of functions for operating on them like doseq for map filter etc.

Keep in mind

Make sure you use doseq for side-effects. Some sequences (and transforms from collections) are Chunked, and can create strange behavior if using side-effects with map or filter.

A Java Iterator (supporting the java.util.Iterator interface) is not a sequence in Clojure. If you are working with a Java Iterators, be sure to call the iterator-seq function on your Java Iterator to get a (chunked) sequence. Some Java types do return collections though, and those should be fine in map or filter since Clojure collections inherit from the Java collections.

Sample code
(->> (range 1000) ;; Range gives us a list of numbers
     (filter even?) ;; Plugs into 
     (map inc))

I've want to write this series for posts for years, but never got around to it, so thanks for reading. I think you'll enjoy the stuff I plan to write in the future, my next post will be Part 2 of this series. Subscribe for the next post or follow me on twitter @janetacarr , or don't ¯\_(ツ)_/¯ . You can also join the discussion about this post on twitter, hackernews, or reddit if you think I'm wrong.

1. Because it's my favorite book on this subject, the inspiration for this post by and large came from Game Programming Patterns (print). The sample code for Command and Observer, as well as each pattern's section structure, was adapted for this post's theme.
2. This post is also largely a rebuttle to mishadoff's Clojure design patterns.
3. Except for the Null Object, all quotes underneath pattern names come directly from Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison Wesley. ISBN 0-201-63361-2.

Subscribe to Janet A. Carr

Sign up now to get access to the library of members-only issues.
Jamie Larson
Subscribe