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 is a website building & hosting documentation for Clojure/Script libraries
× close