Clojure - Protocols (original) (raw)

Clojure is written in terms of abstractions. There are abstractions for sequences, collections, callability, etc. In addition, Clojure supplies many implementations of these abstractions. The abstractions are specified by host interfaces, and the implementations by host classes. While this was sufficient for bootstrapping the language, it left Clojure without similar abstraction and low-level implementation facilities. The and features add powerful and flexible mechanisms for abstraction and data structure definition with no compromises vs the facilities of the host platform.

There are several motivations for protocols:

| | Protocols were introduced in Clojure 1.2. | | -------------------------------------------- |

Basics

A protocol is a named set of named methods and their signatures, defined using defprotocol:

(defprotocol AProtocol
  "A doc string for AProtocol abstraction"
  (bar [a b] "bar docs")
  (baz [a] [a b] [a b c] "baz docs"))

defprotocol will automatically generate a corresponding interface, with the same name as the protocol, e.g. given a protocol my.ns/Protocol, an interface my.ns.Protocol. The interface will have methods corresponding to the protocol functions, and the protocol will automatically work with instances of the interface.

Note that you do not need to use this interface with deftype , defrecord , or reify, as they support protocols directly:

(defprotocol P
  (foo [x])
  (bar-me [x] [x y]))

(deftype Foo [a b c]
  P
  (foo [x] a)
  (bar-me [x] b)
  (bar-me [x y] (+ c y)))

(bar-me (Foo. 1 2 3) 42)
= > 45

(foo
 (let [x 42]
   (reify P
     (foo [this] 17)
     (bar-me [this] x)
     (bar-me [this y] x))))

> 17

A Java client looking to participate in the protocol can do so most efficiently by implementing the protocol-generated interface.

External implementations of the protocol (which are needed when you want a class or type not in your control to participate in the protocol) can be provided using the extend construct:

(extend AType
  AProtocol
   {:foo an-existing-fn
    :bar (fn [a b] ...)
    :baz (fn ([a]...) ([a b] ...)...)}
  BProtocol
    {...}
...)

extend takes a type/class (or interface, see below), a one or more protocol + function map (evaluated) pairs.

(extend-type MyType
  Countable
    (cnt [c] ...)
  Foo
    (bar [x y] ...)
    (baz ([x] ...) ([x y zs] ...)))

  ;expands into:

(extend MyType
  Countable
   {:cnt (fn [c] ...)}
  Foo
   {:baz (fn ([x] ...) ([x y zs] ...))
    :bar (fn [x y] ...)})

Guidelines for extension

Protocols are an open system, extensible to any type. To minimize conflicts, consider these guidelines:

Extend via metadata

As of Clojure 1.10, protocols can optionally elect to be extended via per-value metadata:

(defprotocol Component
  :extend-via-metadata true
  (start [component]))

When :extend-via-metadata is true, values can extend protocols by adding metadata where keys are fully-qualified protocol function symbols and values are function implementations. Protocol implementations are checked first for direct definitions (defrecord, deftype, reify), then metadata definitions, then external extensions (extend, extend-type, extend-protocol).

(def component (with-meta {:name "db"} {`start (constantly "started")}))
(start component)
;;=> "started"