Reputation: 106351
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
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 usingextend-type
orextend-protocol
, then those instances will NOT be caught by the extension of a protocol interface. So, you really shouldn’t do this :)
Upvotes: 3
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
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
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
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