11

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?

5 Answers 5

10

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.

Sign up to request clarification or add additional context in comments.

2 Comments

This would be my solution as well - I think map can be leveraged to remove duplication, e.g. (map #(extend % P common-P-impl) [R S T])
Sorry, doseq, or an enclosing doall should be used re: map being lazy..
7

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.

1 Comment

Thanks. I think this is the best approach in my case - the analogy with the sequence library works well here!
3

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 :)

Comments

1

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. 

Comments

0

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.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.