6
votes

I'm trying to extend library DomKM/silk.

Specifically there's deftype Route which implements protocol Pattern, which has method implementations, which I'd like to reuse in my custom implementation of Pattern protocol.

https://github.com/DomKM/silk/blob/master/src/domkm/silk.cljx#L355

(deftype Route [name pattern]
  Pattern
  (-match [this -url]
          (when-let [params (match pattern (url -url))]
            (assoc params ::name name ::pattern pattern)))
  (-unmatch [this params]
            (->> (dissoc params ::name ::pattern)
                 (unmatch pattern)
                 url))
  (-match-validator [_]
                    map?)
  (-unmatch-validators [_]
                       {}))

Ok so my implementation would look somehow like this, but I'd like to "inherit" Route's methods. I mean execute some custom logic first and then pass it to original Route methods.

(deftype MyRoute [name pattern]
  silk/Pattern
  (-match [this -url] 
    true) ;match logic here
  (-unmatch [this {nm ::name :as params}]
    true) ;unmatch logic here
  (-match-validator [_] map?)
  (-unmatch-validators [_] {}))

How is this done in clojure / clojurescript?

1
Clojure's designers are not fond of inheritance of methods. I don't think you can do this in a very natural way. You can obviously create a Route object and let MyRoute's methods delegate to the Route's. Objects created by deftype are Java classes, so you might be able do what you want by using one of the two Java interop macros that allow inheritance: proxy and gen-class. gen-class is probably overkill, so if you take this path, I suggest proxy. However, this goes against the design goals of Clojure, so you might want to simply reimplement the code you need. - Mars
The functions in Route are not large. If you want to do this a lot, you could write functions outside of the deftype objects and call them using Pattern methods. - Mars
@Mars Thanks for an answer! When I tried to use proxy: (proxy [Route] [name pattern] (-match [])) I get an error java.lang.VerifyError: Cannot inherit from final class. Is it even possible to use proxy on deftype? - ma2s
Maybe not. I wasn't sure. It doesn't surprise me that it doesn't work, I guess. Although it doesn't say you can't extend deftypes, the "Datatypes and protocols are opinionated" section of Clojure's datatypes page will give you the idea. Clojure is intended to encourage certain kinds of programming, while retaining a lot of flexibility. Sometimes there's less flexibility than one wanted in a particular situation, but languages are full of tradeoffs. - Mars
I ended up using juxt/bidi. It's better designed for extensibility. I already achieved elegant solution for my problem with it. Thanks again - ma2s

1 Answers

2
votes

There's no need to redef the type. Referring to "ancestor" protocol implementation is possible, from another namespace.

user=> (ns ns1)
nil
ns1=> (defprotocol P (f [o]))
P
ns1=> (deftype T [] P (f [_] 1))
ns1.T
ns1=> (f (T.))
1
ns1=> (ns ns2)
nil
ns2=> (defprotocol P (f [o]))
P
ns2=> (extend-protocol P ns1.T (f [o] (+ 1 (ns1/f o))))
nil
ns2=> (f (ns1.T.))
2

Keep in mind that ns1.P and ns2.P are totally different cats, both called P.