Define delegates around types and records.
[clj-delegate "0.1.5"]
(ns my-ns
(:require [clj-delegate.core :refer [defdelegate]]))
(defprotocol MyProtocol
(my-method [this])
(my-other-method [this]))
(defrecord MyRecord [a b c]
MyProtocol
(my-method [this]
:bonjour)
(my-other-method [this]
:au-revoir))
(defdelegate MyDelegate MyRecord [d]
MyProtocol
(my-other-method [this]
:goodbye))
(let [record (MyRecord. 1 2 3)
delegate (MyDelegate. 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.MyDelegate{:a 1, :b 2, :c 3, :d 4}
;; The underlying instance stays accessible as the `delegate' field
(println (.delegate delegate))
;; => #clj_delegate.explorations.MyRecord{: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 (.my-method delegate))
;; => bonjour
;; And can be redefined at will
(println (.my-other-method 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.MyRecord{: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? MyDelegate MyRecord))
;; => true
;; By default all methods are forwarded to the delegate
(println (meta (MyDelegate. (with-meta (MyRecord. 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
:
(defdelegate MyDelegate MyRecord [field1 field2]
[[matcher1 transformer1]
[matcher2 transformer2]])
A matcher can be :
[fully-qualified-class-symbol method-name param-names]
'[clojure.lang.IObj withMeta [m]]
Note that only the number of parameters and not their name matter in the
signature.A transformer can either be :
These matcher/transformer pairs will be used to process the existing methods
of the delegate in order to adapt them. Whenever a matcher matches a
method, the corresponding transformer is run against it.
If the transformer is or returns nil
/false
the method is removed from
the delegate implementation.
Note that transformers can return a method descriptor or clojure quoted code.
Example:
[
;; Our main strategy is to forward calls to the merge of the delegate and
;; its delegate.
['[java.util.Map
clojure.lang.IHashEq
clojure.lang.ILookup
[clojure.lang.IPersistentCollection count []]
[clojure.lang.IPersistentCollection equiv []]
java.io.Serializable
java.lang.Iterable]
(fn [m]
(assoc m :body
`(~(emit-call m
(emit-merge-with-delegate m
(:this m))))))]
; seq and associative (except assoc)
['[[clojure.lang.Seqable seq []]
[clojure.lang.Associative containsKey [k]]
[clojure.lang.Associative entryAt [k]]]
(fn [m]
(assoc m :body
`(~(emit-call m
(emit-merge-with-delegate m
(emit-fields-map (:this m) fields))))))]
]
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close