ham-fisted.defprotocol

Alternative protocol implementation.

Major features:

  • Allows subclasses to override only a subset of the methods and if the superclass has overridden the method then the superclasses implementation will be used.

  • Supports primitive typehints on function arguments and return values.

  • Much higher and more predictable multithreaded performance for protocol method invocation due to the fewer number of global variables that are read and written to for a single protocol method invocation. Does not write to global variables on a per-call basis meaning far less cpu/cache traffic in high contention scenarios.

  • Attempting to extend a protocol method that doesn't exist is an error at extension time.

  • Overriding the protocol for the base object array class overrides it for all things convertible to object array while still allowing the concrete array type to match a specific override.

Another design decision is to avoid the interface check - this simplifes the hot path a slight bit at the cost of slightly slower calltimes in the case the interface is used. For those cases often it is possible to simply typehint the interface and call it directly avoiding any protocol dispatch overhead.

Additional call overhead above and beyond a normal fn invocation in an arm mac is -6ns - the time for .getClass call into single concurrent hash map lookup.

defprotocol

macro

added in 1.2

(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"

   ;options
   :extend-via-metadata true

  ;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.

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)

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

extend

added in 1.2

(extend atype & proto+mmaps)

Implementations of protocol methods 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), and one or more protocol + method map pairs. It will extend the polymorphism of the protocol's methods to call the supplied methods when an AType is provided as the first argument.

Method maps are maps of the keyword-ized method names to ordinary fns. This facilitates easy reuse of existing fns and fn maps, for code reuse/mixins without derivation or composition. You can extend an interface to a protocol. This is primarily to facilitate interop with the host (e.g. Java) but opens the door to incidental multiple inheritance of implementation since a class can inherit from more than one interface, both of which extend the protocol. It is TBD how to specify which impl to use. You can extend a protocol on nil.

If you are supplying the definitions explicitly (i.e. not reusing exsting functions or mixin maps), you may find it more convenient to use the extend-type or extend-protocol macros.

Note that multiple independent extend clauses can exist for the same type, not all protocols need be defined in a single extend call.

See also: extends?, satisfies?, extenders

extend-protocol

macro

added in 1.2

(extend-protocol p & specs)

Useful when you want to provide several implementations of the same protocol all at once. Takes a single protocol and the implementation of that protocol for one or more types. Expands into calls to extend-type:

  (extend-protocol Protocol
    AType
      (foo [x] ...)
      (bar [x y] ...)
    BType
      (foo [x] ...)
      (bar [x y] ...)
    AClass
      (foo [x] ...)
      (bar [x y] ...)
    nil
      (foo [x] ...)
      (bar [x y] ...))

expands into:

  (do
   (clojure.core/extend-type AType Protocol
     (foo [x] ...)
     (bar [x y] ...))
   (clojure.core/extend-type BType Protocol
     (foo [x] ...)
     (bar [x y] ...))
   (clojure.core/extend-type AClass Protocol
     (foo [x] ...)
     (bar [x y] ...))
   (clojure.core/extend-type nil Protocol
     (foo [x] ...)
     (bar [x y] ...)))

extend-type

macro

added in 1.2

(extend-type t & specs)

A macro that expands into an extend call. Useful when you are supplying the definitions explicitly inline, extend-type automatically creates the maps required by extend. Propagates the class as a type hint on the first argument of all fns.

  (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] ...)})

extenders

(extenders protocol)

Returns a collection of the types explicitly extending protocol

extends?

(extends? protocol atype)

Returns true if atype extends protocol

find-protocol-cache-method

(find-protocol-cache-method protocol cache x)

find-protocol-method

(find-protocol-method protocol methodk x)

It may be more efficient in a tight loop to bypass the protocol dispatch on a per-call basis.

satisfies?

(satisfies? protocol x)

Returns true if x satisfies the protocol