Define delegates around Clojure types, records and Java classes.
[clj-delegate "0.1.10"]
(ns my-ns
(:require [clj-delegate.core :refer [defdelegate]]))
(defprotocol SalutationP
(greet [this])
(farewell [this]))
(defrecord French [a b c]
SalutationP
(greet [this]
:bonjour)
(farewell [this]
:au-revoir))
(defdelegate ForcedEnglishFarewell French [d]
MyProtocol
(farewell [this]
:goodbye))
(let [record (French. 1 2 3)
delegate (ForcedEnglishFarewell. record 4)]
;; A record delegate is a record like any other record that stores
;; a delegate instance and supplementary fields.
(println delegate)
;; => #clj_delegate.core_test.ForcedEnglishFarewell{:a 1, :b 2, :c 3, :d 4}
;; The underlying instance stays accessible as the `delegate' field
(println (.delegate delegate))
;; => #clj_delegate.explorations.French{:a 1, :b 2, :c 3}
;; Note that this special field is not accessible via a keyword lookup
(println (:delegate delegate))
;; => nil
;; Original methods are callable
(println (.greet delegate))
;; => bonjour
;; And can be redefined at will
(println (.farewell delegate))
;; => goodbye
;; The same goes for fields : delegate fields are directly accessible to the
;; the delegate.
(println (.a delegate))
(println (:a delegate))
;; => 1
;; And additionnal fields can be defined as well.
(println (.d delegate))
(println (:d delegate))
;; => 4
;; Any modification to the delegate (with the exception of its own fields)
;; impacts the underlying delegate. Here is an example using 'assoc'.
;; The same logic follows with dissoc, conj, cons, with-meta, etc...
(println (.delegate (assoc delegate :x :y)))
;; => #clj_delegate.explorations.French{:a 1, :b 2, :c 3, :x :y}
;; More importantly, delegates are equal to the instance they wrap.
(println (= delegate record))
;; => true
;; And derive from it in the default isa? hierarchy
(println (isa? ForcedEnglishFarewell French))
;; => true
;; By default all methods are forwarded to the delegate
(println (meta (ForcedEnglishFarewell. (with-meta (French. 1 2 3)
{:a :aa})
4)))
;; => {:a :aa}
;; Except if the method has been redefined in the body of the delegate or
;; if it has been subject to a transform.
)
Alternatively a transforms argument can be passed to defdelegate as
a vector or map of matcher/transformer pairs. Place it just after the arg vector.
(defdelegate ForcedEnglishFarewell French [field1 field2]
[[matcher1 transformer1]
[matcher2 transformer2]]
(... optional body in the style of deftype))
Example inspired from the source. This handles smooth integration with records so that assoc, get, etc work on the merge of the
delegate and the wrapped record:
(defdelegate ExampleDelegate WrappedRecord [arg]
{(or| (abstraction?| java.util.Map
clojure.lang.IHashEq
clojure.lang.ILookup
java.io.Serializable
java.lang.Iterable)
(method?| '[clojure.lang.IPersistentCollection count []]
'[clojure.lang.IPersistentCollection equiv []]))
(fn [m]
(assoc m :body
`(~(emit-call
m (emit-merge-with-delegate
m (:this m))))))})
Other example from the tests:
(defdelegate DelegateForTransforms RecordForTransforms []
{(method?| '[ProtoForTransforms method-a []])
(literally| '(method-a [_] :delegate))}
ProtoForTransforms
(method-b [this] :delegate))
Notes:
abtraction?|and so on using and|, or|, not| and other functional combinators.m the transformer function work on look like this:{:name method-a,
:declaring-class clj_delegate.core_test.RecordForTransforms,
:protocol clj-delegate.core-test/ProtoForTransforms,
:params [],
:parameter-types [],
:return-type java.lang.Object,
:this this,
:body ((.method-a (.delegate this)))}
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |