mikera
mikera

Reputation: 106351

How can you extend a Clojure protocol to another protocol?

Suppose I have two protocols:

(defprotocol A 
  (f [this]))

(defprotocol B 
  (g [x y]))

And I want to extend protocol B to all instances that support protocol A:

(extend-protocol A 
  String 
    (f [this] (.length this)))

(extend-protocol B 
  user.A
    (g [x y] (* (f x) (f y))))

The primary motivation is to avoid having to extend B separately to all the possible classes that A may be extended to, or even to unknown future classes that other people may extend A to (imagine if A was part of a public API, for example).

However this doesn't work - you get something like the following:

(g "abc" "abcd")
=> #<IllegalArgumentException java.lang.IllegalArgumentException: 
No implementation of method: :g of protocol: #'user/B found for 
class: java.lang.String>

Is this possible at all? If not, is there a sensible workaround to achieve the same objective?

Upvotes: 11

Views: 3388

Answers (5)

fmnoise
fmnoise

Reputation: 109

in "Clojure applied" there's a recipe of extending protocol by protocol

(extend-protocol TaxedCost
  Object
  (taxed-cost [entity store]
    (if (satisfies? Cost entity)
      (do (extend-protocol TaxedCost
            (class entity)
            (taxed-cost [entity store]
              (* (cost entity store) (+ 1 (tax-rate store))))) 
          (taxed-cost entity store))
      (assert false (str "Unhandled entity: " entity)))))

actually nothing prevents you from simply extending protocol by another one

(extend-protocol TaxedCost 
  Cost
  (taxed-cost [entity store]
    (* (cost entity store) (+ 1 (tax-rate store)))))

while book says it's not possible. I've talked with Alex Miller about this and he said the following:

It really doesn’t work in all cases. The protocol generates a Java interface and you can for sure, extend a protocol to that interface. The problem is that not every protocol implementation implements that interface - only records or types that do so with an inline declaration like (defrecord Foo [a] TheProtocol (foo ...)). If you are implementing a protocol by using extend-type or extend-protocol, then those instances will NOT be caught by the extension of a protocol interface. So, you really shouldn’t do this :)

Upvotes: 3

marctrem
marctrem

Reputation: 850

From what I see, protocols can indeed extend protocols. I made an example here: https://github.com/marctrem/protocol-extend-protocol-example/blob/master/src/extproto/core.clj

In the example, we have an Animalia protocol (which allow its members do dream) which is extended by a Erinaceinae protocol (which allows its members to go-fast).

We have a record Hedgehog which is part of the Erinaceinae protocol. Our instance of the record can both dream and go-fast.

(ns extproto.core
  (:gen-class))


(defprotocol Animalia (dream [this]))

(defprotocol Erinaceinae (go-fast [this]))

(extend-protocol Animalia 
  extproto.core.Erinaceinae
  (dream [this] "I dream about things."))

(defrecord Hedgehog [lovely-name]
  Erinaceinae
  (go-fast [this] (format "%s the Hedgehog has got to go fast." (get this :lovely-name))))



(defn -main
  [& args]  
  (let [my-hedgehog (Hedgehog. "Sanic")]
    (println (go-fast my-hedgehog))
    (println (dream my-hedgehog))))

;1> Sanic the Hedgehog has got to go fast.
;1> I dream about things.

Upvotes: 1

Julien Chastang
Julien Chastang

Reputation: 17774

Although I do not completely understand what you are trying to do, I wonder if clojure multimethods would be a better solution for your problem.

Upvotes: 0

Jonas
Jonas

Reputation: 19632

It seems to me that you can implement the function g in terms of f. If that is the case you have all the polymorphism you need without the protocol B.

What I mean is the following, given that f is polymorphic, then

(defn g [x y]
  (* (f x) (f y)))

yields a function g which supports all types which implements the protocol A.

Often, when protocols are at the very bottom, simple functions defined only in terms of protocol functions (or on other functions which themself use the protocol) makes the whole namespace/library/program very polymorphic, extendable and flexible.

The sequence library is a great example of this. Simplified, there are two polymorphic functions, first and rest. The rest of the sequence library is ordinary functions.

Upvotes: 7

Alex Taggart
Alex Taggart

Reputation: 7825

Protocols are not types, and do not support inheritance. A protocol is essentially a named collection of function definitions (and a dispatch mechanism when those functions are called).

If you have multiple types that all happen to have the same implementation, you can simply call a common function. Alternately, you can create a method map and extend each type with that map. E.g.:

(defprotocol P
  (a [p])
  (b [p]))

(deftype R [])
(deftype S [])
(deftype T [])

(def common-P-impl
  {:a (fn [p] :do-a)
   :b (fn [p] :do-b)})

(extend R
  P common-P-impl)
(extend S
  P common-P-impl)
(extend T
  P common-P-impl)

If you provide some more detail on your actual scenario, we may be able to suggest the correct approach.

Upvotes: 10

Related Questions