Clojure prefers a large library of functions over a small set of types, but supports runtime polymorphism in two ways: protocols, which dispatch on the type of the first argument, and multimethods, which dispatch on any function of the arguments. New types are made with defrecord, deftype and reify.
Protocols
A protocol is a named set of functions with signatures, (defprotocol Name doc-string? option* (method [this args*] [this more*] doc-string?)*). It defines a var for each function. A call dispatches on the class of the first argument (the this); the implementation found for that class is used. The option :extend-via-metadata true lets a value also implement the protocol through its metadata.
(defprotocol Shape
"Things that have an area."
(area [this] "The area.")
(scale [this k] [this kx ky]))
(extend-type String
Shape
(area [s] (count s)))
(extend-protocol Shape
nil
(area [_] 0)
Long
(area [n] n))
| Form | Meaning |
|---|---|
(extend-type Type Protocol (method [this …] body)* …) | implements one or more protocols for one type; nil is a type here |
(extend-protocol Protocol Type (method …)* Type2 …) | implements one protocol for several types |
(extend Type Protocol {:method fn} …) | the underlying function, taking a map of method keywords to functions |
:extend-via-metadata true | a value's metadata may hold the implementations, under keys that are the fully qualified method symbols; direct implementations are checked first |
satisfies? extends? extenders | test whether a value satisfies a protocol, whether a type is extended, and list the types that are |
find-protocol-impl find-protocol-method | look up an implementation |
Records and types
| Form | What it makes |
|---|---|
(defrecord Name [fields*] Protocol-or-Interface (method [this …] body)* …) | a class with the given fields and a full persistent map implementation: value-based equality and hash, metadata, associative lookup, keyword accessors for the fields, and additional keys that can be assoced on. Fields cannot be mutable |
(deftype Name [fields*] Protocol-or-Interface (method …)* …) | a class with the given fields and nothing else the user did not specify except a constructor. Fields may be mutable (^:volatile-mutable, ^:unsynchronized-mutable). Used to build new low-level data structures |
(reify Protocol-or-Interface (method …)*) | an anonymous instance of a new class that implements protocols and interfaces and closes over locals; only methods that the protocols or interfaces declare |
(definterface Name (method [args])) | a Java interface (without implementing it); types in the signatures can be hinted (^long m [^String s]) |
(proxy [Class Interface*] [constructor-args*] (method [this …] body)*) | an instance of a new class that extends a concrete class and implements interfaces, with methods held in a map that can change |
- Both
deftypeanddefrecordgenerate a unique class with a field for each name and an accessible constructor. A field may have a type hint, including a primitive; a hint of a non-primitive type does not constrain the field but is used to optimise the methods. - When a type or record
Foois defined, a function->Foothat passes its arguments to the constructor is defined; for a record,map->Footakes a map and also accepts extra keys (Clojure 1.3 and later). - The reader forms
#my.ns.Foo[1 2]and, for records,#my.ns.Foo{:a 1}call the constructor with unevaluated arguments; the class name must be fully qualified. Record fields missing from the map arenil. - Datatypes are opinionated: they cannot derive from concrete classes, only from interfaces; they cannot expose methods that are not in a protocol or interface; fields are public; immutability is the default.
- Methods may carry Java annotations on the type name, the fields and the methods.
record?andtypeexamine a value;(.method obj)calls a method of an interface the type implements.
Multimethods
A multimethod is a dispatching function plus methods. (defmulti name doc-string? attr-map? dispatch-fn option*) creates it; (defmethod name dispatch-value [params*] body) adds a method. When called, the dispatching function is applied to the arguments and the method whose dispatch value matches the result is called with the same arguments. Matching uses isa?, so a value can match through a hierarchy.
| Part | Meaning |
|---|---|
| dispatch value | a literal, a class or a vector of them; :default (or the value set by :default in defmulti) matches when nothing else does |
isa? | true for equal values, for a class and its ancestors, for entries in a hierarchy made with derive (keyword or symbol with a namespace), and element-wise for vectors |
derive underive parents ancestors descendants make-hierarchy | the global hierarchy and local ones (:hierarchy option of defmulti) |
prefer-method | when two methods match and neither dominates the other, (prefer-method f [::rect ::shape] [::shape ::rect]) says which wins |
methods get-method remove-method remove-all-methods prefers | inspect and change a multimethod |
(defmulti area :shape)
(defmethod area :circle [{:keys [r]}] (* Math/PI r r))
(defmethod area :square [{:keys [s]}] (* s s))
(defmethod area :default [x] :oops)
:shape key of a map.Choosing
Use a protocol when behaviour depends on the type of the first argument and speed matters; use a multimethod when it depends on values, several arguments or a hierarchy; use plain functions and maps when neither is needed.
Not described
Known, not yet covered
- Reference: the full semantics of
proxy(the method map,proxy-super,update-proxy) and ofgen-classare not covered;core_proxy.cljwas downloaded but not read in full. - Reference: how protocol methods are cached and the performance notes of the protocol reference, and the guidelines for extension, were read only in outline.
Unknown
- Reference: the mutable field metadata names
^:volatile-mutableand^:unsynchronized-mutableare from memory of the language; the downloaded datatypes page only says that deftype supports mutable fields.
See also
- Data structures – records are maps
- Java interop – proxies, interfaces and classes
- Common Lisp: CLOS – generic functions, the Common Lisp counterpart