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

Fix your Clojure code: Clojure comes with design patterns (Part 2)
Photo by Edho Pratama / Unsplash
This post is the second in a series. You can find the first post in the series here where I covered Null Object, Singleton, Command, Observer, State, Visitor, and Iterator.

Patterns

Our story continues

Edmund, a bright, awkward young man with a masters degree from a prestigious University, loves functional programming, but his views are too pure for Suzi's taste. He always drones on about how we don't need to think about any of those design patterns things because "that's only for object-oriented programming".

However, Edmund's latest project with an intern, a load testing application, was taking far too long, like most projects at StartupAI. Despite the mythical (wo)man-month, Suzi's manager asks her to join the project, and she agrees.

"We see the heart of this project as state machine" Edmund says, during a code walk through.

To Suzi surprise, she sees a dense case form. "Uh oh" thinking to herself.

Next Edmund shows her the dozens of functions created to manufacture types to kick start the state machine. Of course, whenever they have to change these functions, they have to change the state machine and vise-versa. To top it all off, they're using Clojure futures to generate the load tests, attempting to coordinate all the futures and their requests. Lucky for Suzi, she knows why they're not progressing on the project, and why it doesn't work at all.

"I have an idea, why don't you replace this case form with a state pattern, and these functions with a builder?"  Suzi says gently presenting the idea.

"Oh, I just prefer not to do those kind of things in functional programming." He replies.

"Right, but it would decouple the state machine from the this namespace, and you can decouple your functions here from this code down here." She says while pointing with her pen at the functions on the screen from earlier, and gesturing downward at the state machine. Continuing, "And, you can ditch this whole futures thing. Just use core.async".

"What's core.async?" Edmund asks no one in particular while typing 'clojure core async' into the browser search bar.

"Communicating Sequential Processes in Clojure. It handles thread pooling and everything, all we have to do is dispatch work to the 'go loops'." Suzi answers.

Oh.

Builder

Separate the construction of a complex object from its representation so that the same construction process can create different representations.

A lot of patterns become thinking about, and structuring of, the flow of higher-order functions and their composition. While the functions may seem vague, we need to keep in mind our function's intent. With the builder pattern we want to delegate object (or function) construction (composition) .

When to use it

When you want to decouple the construction of a complex object from the context it's used in.

Clojure analogue
  • partial or fn and comp - Most design patterns are a response to missing features from functional programming. The command pattern address the lack of higher order functions in Object Oriented programming languages. So in this respect, the builder pattern addresses the lack of currying. Unfortunately, Clojure also doesn't support currying, but we can get a close approximation with partial function composition using partial or fn and comp.
  • cond-> and assoc and related map functions - In practice most "builders" are really just methods to set some kind of member values rather than delegating object construction. We see this a lot in something like Go where a struct has receivers that return a new struct, allowing for chaining of call like MyCoolStruct.WithFun("stuff").WithHydration("Water").WithCode("Clojure"). If you're idea of a builder looks like this, you can probably get pretty far with threading macros and map functions like assoc, update, merge since they all take a hash map as an argument and return a hash map, or just make up your own functions!
Keep in mind
  • Partial function composition can get hairy since you can't name the function produced by either partial or comp.
  • When using partial and comp, take care to remember that transducers exist. Depending on your use case composing a transform from transducers (map, filter, etc) will be much more performant than creating partial functions over them.
Sample code
;; builder
;; now we can delegate our map construction to this
;; function.
(defn build-s3-client
  [mock-creds ssl default-bucket sts]
  (let [if-fn (fn [cond f] (if cond f identity))]
    (comp
     (if-fn (not (empty? creds))
       #(let [secret (s/gen :specs/client-secret)
              access-key (s/gen :specs/access-key)]
          (merge % {:client-secret (g/generate secret)
                    :access-key (g/generate access-key)})))

     (if-fn ssl #(assoc % :ssl true))

     (if-fn (and (string? default-bucket)
                 (not (empty? default-bucket)))
       #(assoc % :default-bucket default-bucket))

     (if-fn (and (string? sts)
                 (not (empty? sts))
                 (some? (re-matches #"." sts)))
       #(assoc % :sts-grant sts)))))
   
(def s3-client 
  (build-s3-client {} false "my-bucket" "real-token"))

;; Chaining technique
(defn ->s3-client
  []
  (let [{:keys [aws-secret-key
                aws-access-key]} env]
    {:client-secret client-secret
     :access-key access-key}))

(defn with-ssl
  [m]
  (assoc m :ssl true))

(defn with-default-bucket
  [m bucket]
  (assoc m :default-bucket bucket))

(defn with-sts
  [m token]
  (assoc m :sts-grant token))

(defn with-mock-credentials
  [m]
  (let [secret (s/gen :specs/client-secret)
        access-key (s/gen :specs/access-key)]
    (merge m {:client-secret (g/generate secret)
              :access-key (g/generate access-key)})))

(defn build-s3-client
  [bucket]
  (-> (->s3-client)
      (with-ssl)
      (with-default-bucket bucket)))

(defn mock-s3-client
  [bucket]
  (-> (->s3-client)
      (with-mock-credentials)
      (with-bucket bucket)))

Chain of Responsibility

Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.

The structure of this pattern seems similar to the builder, but our intent for our functions is different. And, If I'm being honest, the only Chain of Responsibility I've ever seen, even outside of Clojure, is the middleware chain in ring applications.

When to use it

When you want to decouple a request from its handler.

Clojure analogue
  • -> or ->> and fn - Using the thread macro through lambdas that have the same function signature, creating closures over closures until its final form.
Keep in mind
  • Chain of Responsibility in Clojure has the same caveats as the command pattern from the last post. Be sure to name your fn forms because the stack traces from the Chain of Responsibility are challenging to debug as the chain can sometimes be quite long.
  • Like all threading macros, If the chain order matters, you will have to structure your handlers in reverse order. For example, in the case of ring middleware you want the function returned by wrap-authentication to evaluate before the function returned by wrap-authorization.
Sample code

Ring middleware gives us the best and most prevalent example of a Chain of Responsibility in Clojure. In this example wrap-authentication and wrap-authorization are adapted from buddy.auth.

(defn wrap-authentication
  "Ring middleware that enables authentication for your ring
  handler. When multiple `backends` are given each of them gets a
  chance to authenticate the request."
  [handler & backends]
  (fn authentication-handler
    ([request]
     (handler (apply authentication-request request backends)))
    ([request respond raise]
     (handler (apply authentication-request request backends) 
              respond 
              raise))))
    
;; lots of code in between. Just pretend it's here for
;; the sake of this example.

(defn wrap-authorization
  "Ring middleware that enables authorization
  workflow for your ring handler.
  The `backend` parameter should be a plain function
  that accepts two parameters: request and errordata
  hashmap, or an instance that satisfies IAuthorization
  protocol."
  [handler backend]
  (fn authorization-handler
    ([request]
     (try (handler request)
          (catch Exception e
            (authorization-error request e backend))))
    ([request respond raise]
     (try (handler request respond raise)
          (catch Exception e
            (respond (authorization-error request e backend)))))))
     
;; In another namespace:
;; Middleware usage
(def app
  (-> compojure-routes
      (wrap-authorization jwe)
      (wrap-authentication jwe)
      (wrap-cors :access-control-allow-origin [#".*"]
                 :access-control-allow-methods [:get :post])
      (wrap-json-response {:pretty false})
      (wrap-json-body {:keywords? true})
      (wrap-accept {:mime ["application/json" :as :json
                           "text/html" :as :html]})
      (wrap-cookies)
      (wrap-params)))

Proxy

Provide a surrogate or placeholder for another object to control access to it.

Unlike the only Chain of Responsibility I've seen, I have seen a proxy once or twice. Usually developers don't use the proxy form, rather opting to wrap a function in another function to restrict access, lazy typing, or virtualization.

When to use it

The Gang of Four quote is pretty legible on this one. Use a proxy as a 'middleman' for (controlling) access to another object. You can also use it for creating a lazily loaded object if a LazySeq doesn't do it for you.

Clojure analogue
  • proxy - Clojure ships with a whole set of proxy-related functions. They are seldom used as developers typically prefer to use reify(and related forms). Unlike reify, proxycan be used to instantiate Java classes, not just interfaces and protocols.
  • You could use higher-order fn to have a proxy too, but at this point the idea is getting repetitive, so I'm just going to keep it to the quirky, interesting stuff.
Keep in mind
  • The proxy functions proxy, proxy-super, init-proxy, update-proxy, construct-proxy, get-proxy-class, and proxy-mapping are old (Clojure 1.0) and reminiscent of Object-oriented programming. If you didn't know what a proxy was, you might end up doing double the work of plain old interop when using proxy.
  • Some of the proxy functions like proxy-super expand to use reflection on their this argument unless type hinted.
  • Like all extensibility on the JVM, proxy can't inherit from a final class which also means no proxies to Clojure records.
  • You can't proxy a proxy, meaning you can't use proxy on something that already implements Clojure's IProxy interface. If you could, that'd be OOP. This isn't Common Lisp.
Sample code
(defn access-controlled-input-stream
  "Returns an access controlled InputStream"
  [user]
  (proxy [java.io.InputStream] []
    (read
      ([]
       (if (allowed? user)
         (let [^java.io.InputStream this this]
           (proxy-super read))
         (throw (ex-info "Unauthorized"
                         {:user user}))))
      ([^bytes b]
       (if (allowed? user)
         (let [^java.io.InputStream this this]
           (proxy-super read b))
         (throw (ex-info "Unauthorized"
                         {:user user}))))
      ([^bytes b off len]
       (if (allowed? user)
         (let [^java.io.InputStream this this]
           (proxy-super read b off len))
         (throw (ex-info "Unauthorized"
                         {:user user})))))))

Adapter

Convert the interface of a class into a another interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.

You don't need to always use a function to wrap something, you can also use an adapter. Adapters are the ultimate glue code and de facto polymorphism (protocols) in Clojure. If we consider a type an interface (or bag of functions™), any library or API could be considered a interface, so we can wrap it in an adapter for core business logic for when we want to swap it out1. Or not, if you're an "aren't going to need it" person.

When to use it

When you want to use an implementation with a different interface. Also called a wrapper.

Clojure analogue
  • protocols - Any Clojure type can be extended to support a particular protocol using extend-type.
Keep in mind
  • Using an adapter can be a symptom of a leaky abstraction.
Sample code
;; Shamelessly using protocols example from last post

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

;; our conversions, could also extend Java objects here.
(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)))))

;; our client
(defn ->money
  [x]
  (make-safe x))

Template Method

Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine steps of an algorithm without changing the algorithm's structure.

Yet lots of people need template methods (functions?) since I've seen plenty of template functions in Clojure codebases over the years. Template functions get pretty hairy when the delegate functions get spread out over the codebase, doubly so if unamed lambdas are used. If you need to write functions across the vast expanse of your namespaces, use a strategy instead.

When to use it

Use a template method when you want to decouple primitives or subroutines of an algorithm from the overaching structure.

Clojure analogue
  • Variodic function arguments with function composition - We could call this a template function rather than a template method. Variodic functions will give us the ability to define defaults as well as mandatory implementations.
  • do-template - do-template evaluates an expression, grouping and feeding it's arguments into the template. It's good for a "template method" where the overrides are primitives or all "subtypes" of the template method/function are evaluated in the same client.
Keep in mind
  • Template methods are very similar to the strategy pattern, but tightly couple the subtypes to the template, implying function definition locality is your friend.
  • Depending on how important isolation of your variodic arguments is, You can swap out traditional variodic arguments for variodic keyword arguments in Clojure 1.11. For example, in our sample code below if we want to change the implementation of build-windows without changing build-foundation:
Sample code
;; our template defaults
(defn- build-foundation-default
  []
  (println "Building foundation with cement iron rods and sand"))
  
(defn- build-windows-default
  []
  (println "Building glass windows"))

;; The "template"
(defn build-house-template
  ([build-pillars build-walls]
   (build-house-template build-pillars
                         build-walls
                         build-foundation-default
                         build-windows-default))
  ([build-pillars build-walls build-foundation]
   (build-house-template build-pillars
                         build-walls
                         build-foundation
                         build-windows-default))
  ([build-pillars build-walls build-foundation build-windows]
   (build-foundation)
   (build-pillars)
   (build-walls)
   (build-windows)
   (println "House is built.")))

(defn wooden-house
  []
  (build-house-template #(println "Building pillars with wood coating")
                        #(println "Building wooden walls")))

(defn glass-house
  []
  (build-house-template #(println "Building pillars with glass coating")
                        #(println "Building glass walls")))

;; our client
(defn house-builder
  [build-house]
  (build-house))

Flyweight

Use sharing to support large numbers of fine-grained objects efficiently.

Naturally, the Strategy pattern would follow from the template function, but I decided to do flyweight next. Flyweight is basically a cache. If you want to get fancy, you can use the Clojure core.cache library where you get a cache protocol, conveinence macros, and eviction algorithms. Real caching. If you need to share some good ol' intrinsic state, here is a couple flyweight ideas for you.

When to use it

Use a Flyweight when you have overlapping object usage between clients. Flyweights typically destinguish between extrinsic state and intrinsic state where the extrinsic state is context dependend and intrinsic is context independent. So, the intrinsic state gets shared between client contexts.

Clojure analogue
  • memoize - memoize will store the return value of a function mapped to it's arguments, conflating the two parts of the flyweight, the flyweight factory and operation, into one single memoize call.
  • fn over an atom - We might not necessarily want to use our extrinsic state as our factory function key. In normal dev speak, that means we might not necessarily want our function arguments to determine what state gets shared between clients.
Keep in mind
  • Introducing state (not the pattern) into your application means you have the potential to introduce certain bugs you wouldn't otherwise have. Use a watch to keep track of state changes if you're concerned about it.
  • Using the atom version of the ->flyweight can be use in a local binding, but it will create a new atom, so be mindful of that.
Sample code
(defprotocol Sprite
  "Describes a totally real sprite type"
  (draw! [this x y height width]
    "draw something to the screen."))

(deftype PlayerSprite [sprite-sheet x y height width]
  Sprite
  (draw! [this x y height width]
    "Drew the player on the screen."))

(deftype EnemySprite [sprite-sheet x y height width]
  Sprite
  (draw! [this x y height width]
    "Drew the enemy on the screen."))

;; Our "flyweights"
;; could store anything in memoize.
(def player-sprite
  (memoize #(->PlayerSprite (slurp "./spritesheet.png")
                            {:x 0 :y 0 :height 32 :width 32})))

(def enemy-sprite
  (memoize #(->EnemySprite (slurp "./spritesheet.png")
                           {:x 33 :y 33 :height 32 :width 32})))


;; Atom example
;; our flyweight function is similar to the body of memoize
;; except we want to use a keyword to get value instead
(defn ->flyweight
  []
  (let [mem (atom {})
        player-coords {:x 0 :y 0 :height 32 :width 32}
        enemy-coords {:x 33 :y 33 :height 32 :width 32}
        sprites {:player #(->PlayerSprite (slurp "./spritesheet.png")
                                          player-coords)
                 :enemy #(->EnemySprite (slurp "./spritesheet.png")
                                        enemy-coords)}]
    (fn [k & args]
      (if-let [e (find @mem k)]
        (val e)
        (when-let [f (get sprites k)]
          (let [ret (apply f args)]
            (swap! mem assoc k ret)
            ret))))))

(def flyweight (->flyweight))

Strategy

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

There's no official Clojure library for Strategy though. This is kind of a boring pattern in Clojure because it's just a lambda, so I'm including it completeness, but what blog post doesn't have fluff.

When to use it

Use a Strategy when you need to decouple algorithms from where they're being used.

Clojure analogue
  • fn (again) - This time you'll have think real hard about the input and output of the higher-order functions you're passing around. sort-by allows you to pass in a key-fn as a strategy for getting the values to pass to the compare operation (or would that be a template method? Hmm....).
Keep in mind
  • The usual stuff with fn. Name your fn for debugging and recursion. I suppose you could name them blah-blah-strategy, but you might get flak for saying the pattern name in the code.
Sample code
;; I'm not going to type out the sorts because I haven't
;; been in a CS classroom for years.
(defn quick-sort
  [coll])

(defn merge-sort
  [coll])

(defn radix-sort
  [coll])

(defn find-key
  "Performs a binary search on coll after calling
  sort-fn on coll."
  [key coll sort-fn]
  (find (sort-fn coll) key))
  
;; client somewhere
(find-key :fun-key (get-list) radix-sort)
Thanks for reading. As I said earlier, this is the second post in a series, and I think there might be one or two more, so subscribe for the next one 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. Some people call this ports and adapters architecture.
2.The template method example was inspired by this Digital Ocean blog post.

Subscribe to Janet A. Carr

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