Skip to content
AgileLogics
6 min read

Protocols

Prerequisites: Java interop

A protocol is a named set of functions that different types can implement — Clojure's answer to interfaces, but open: you can extend a protocol to types you didn't write, including Java classes and nil.

(defprotocol Describable
  (describe [this] "Returns a human-readable description.")
  (tag [this]))

(extend-protocol Describable
  String
  (describe [s] (str "text of length " (count s)))
  (tag [_] :string)

  Number
  (describe [n] (str "the number " n))
  (tag [_] :number)

  clojure.lang.IPersistentMap
  (describe [m] (str "a map with keys " (keys m)))
  (tag [_] :map)

  nil
  (describe [_] "nothing at all")
  (tag [_] :nil))

(doseq [x ["hello" 42 {:a 1} nil]]
  (println (tag x) "-" (describe x)))
:string - text of length 5
:number - the number 42
:map - a map with keys (:a)
:nil - nothing at all
  • defprotocol declares the functions; the first parameter is always the value being dispatched on.
  • extend-protocol implements it for several types at once; extend-type implements several protocols for one type.
  • Dispatch is on the type of the first argument and is very fast.

Calling an unimplemented protocol

(defprotocol Shape (area [s]))

(try
  (area [1 2])
  (catch IllegalArgumentException e
    (println (first (clojure.string/split-lines (ex-message e))))))
(println (satisfies? Shape [1 2]))
No implementation of method: :area of protocol: #'user/Shape found for class: clojure.lang.PersistentVector
false

Protocols and records

Protocols are usually implemented by records (next lesson) right in their definition:

(defprotocol Shape
  (area [s])
  (perimeter [s]))

(defrecord Circle [r]
  Shape
  (area [_] (* Math/PI r r))
  (perimeter [_] (* 2 Math/PI r)))

(defrecord Rect [w h]
  Shape
  (area [_] (* w h))
  (perimeter [_] (* 2 (+ w h))))

(doseq [s [(->Circle 1) (->Rect 2 3)]]
  (println (.getSimpleName (class s))
           (format "area %.2f perimeter %.2f" (double (area s)) (double (perimeter s)))))

(println (reduce + (map area [(->Rect 1 1) (->Rect 2 2)])))
Circle area 3.14 perimeter 6.28
Rect area 6.00 perimeter 10.00
5

When to use a protocol

  • You need polymorphism on type with good performance — for example, a storage API with in-memory and database implementations.
  • You want to add behavior to existing types (Java classes, other libraries' records) without changing them.

If you only need different behavior for different values (like an order's :status), multimethods are more flexible.