defprotocol

added
1.2

ns
clojure.core

type
macro

(defprotocol name & opts+sigs)

A protocol is a named set of named methods and their signatures:
(defprotocol AProtocolName

;optional doc string
"A doc string for AProtocol abstraction"

;method signatures
(bar [this a b] "bar docs")
(baz [this a] [this a b] [this a b c] "baz docs"))

No implementations are provided. Docs can be specified for the
protocol overall and for each method. The above yields a set of
polymorphic functions and a protocol object. All are
namespace-qualified by the ns enclosing the definition The resulting
functions dispatch on the type of their first argument, which is
required and corresponds to the implicit target object ('this' in 
Java parlance). defprotocol is dynamic, has no special compile-time 
effect, and defines no new types or classes. Implementations of 
the protocol methods can be provided using extend.

defprotocol will automatically generate a corresponding interface,
with the same name as the protocol, i.e. 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 should not use this interface with deftype or
reify, as they support the protocol directly:

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

(deftype Foo [a b c] 
P
(foo [this] a)
(bar-me [this] b)
(bar-me [this 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

                (defprotocol Fly
  "A simple protocol for flying"
  (fly [this] "Method to fly"))

(defrecord Bird [name species]
  Fly
  (fly [this] (str (:name this) " flies...")))

(extends? Fly Bird)
-> true

(def crow (Bird. "Crow" "Corvus corax"))

(fly crow)
-> "Crow flies..."
            
                ;; from Stuart Halloway's examples:

(defprotocol Player
  (choose [p])
  (update-strategy [p me you]))

(defrecord Stubborn [choice]
  Player
  (choose [_] choice)
  (update-strategy [this _ _] this))

(defrecord Mean [last-winner]
  Player
  (choose [_]
          (if last-winner
            last-winner
            (random-choice)))
  (update-strategy [_ me you]
                   (->Mean (when (iwon? me you) me))))

            
                ;; defprotocol does NOT support interfaces with variable argument lists, 
;; like [this & args]
;; (this is not documented anywhere... )

;; The workaround is to define the interface with the variable arg list in a fn
;; separately outside of the protocol, which then calls the protocol interface
;; with a slightly different name and an array in place of the variable list,
;; like:

(defprotocol MyProtocol
  (-my-fn [this args]))

(defn my-fn [this & args] (-my-fn this args))
            
                ;; Protocols allow you to add new abstractions to existing types in a clean way.
;; Polymorphic functions are created in namespaces as opposed to
;; having the polymorphism live on Classes as typically done in OO.

;; example from: 
;; https://speakerdeck.com/bmabey/clojure-plain-and-simple?slide=230
(ns abstraction-a)

(defprotocol AbstractionA
  (foo [obj]))

(extend-protocol AbstractionA
  nil
  (foo [s] (str "foo-A!"))
  String
  (foo [s] (str "foo-A-" (.toUpperCase s))))

(ns abstraction-b)

(defprotocol AbstractionB
  (foo [obj]))

(extend-protocol AbstractionB
  nil
  (foo [s] (str "foo-B!"))
  String
  (foo [s] (str "foo-B-" (.toLowerCase s))))


user=> (require '[abstraction-a :as a])

user=> (require '[abstraction-b :as b])

user=> (a/foo "Bar")
"foo-A-BAR"

user=> (b/foo "Bar")
"foo-B-bar"

user=> (a/foo nil)
"foo-A!"

user=> (b/foo nil)
"foo-B!"

            
                ;; Note these differences between defprotocol and definterface:

;; defprotocol requires that methods specify a first parameter, which 
;; will be the record object, while definterface requires that this
;; parameter be left out:
(definterface I (fooey []))
;=> user.I
(defprotocol P (fooey []))
;=> IllegalArgumentException Definition of function fooey in protocol P must take at least one arg.  clojure.core/emit-protocol/fn--5964 (core_deftype.clj:612)
(defprotocol P (fooey [this]))
;=> P

;; However, defrecord requires that a parameter for the record object
;; be used, even with interfaces.  (A similar point applies to deftype.)
(defrecord Irec [stuff] I (fooey [] "foo"))
;=> CompilerException java.lang.IllegalArgumentException: Must supply at least one argument for 'this' in: fooey, compiling:(NO_SOURCE_PATH:1:1) 
(defrecord Irec [stuff] I (fooey [this] "foo"))
;=> user.Irec
(defrecord Prec [stuff] P (fooey [this] "foo"))
;=> user.Prec

;; Using an interface, only the dot form of the method is available with 
;; defrecord, while the protocol also allows use of normal Clojure function
;; syntax.  (Similar points apply to deftype.)
(.fooey (Irec. 42))
;=> "foo"
(fooey (Irec. 42))
;=> IllegalArgumentException No implementation of method: :fooey of protocol: #'user/P found for class: user.Irec  clojure.core/-cache-protocol-fn (core_deftype.clj:544)
(.fooey (Prec. 42))
;=> "foo"
(fooey (Prec. 42))
;=> "foo"