Liking cljdoc? Tell your friends :D

clj-delegate

Define delegates around types and records.

Usage

[clj-delegate "0.1.5"]
(ns my-ns
  (:require [clj-delegate.core :refer [defdelegate]]))

Example

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

Transforms

Alternatively a transforms argument can be passed to defdelegate:

(defdelegate MyDelegate MyRecord [field1 field2]
  [[matcher1 transformer1]
   [matcher2 transformer2]])

A matcher can be :

  • a list of matchers
  • the fully qualified symbol of a class name
  • a method signature triplet of the form: [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 function accepting one method-descriptor as argument which should return true when the transformer should be applied to this method.

A transformer can either be :

  • a function accepting a method-descriptor as argument that modifies then returns it.
  • quoted clojure code as a replacement for the method's existing implementation.
  • the nil or false value

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