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?
In Clojure, a protocol is like an interface which can be extended to existing types. It defines a named type, together with functions whose first argument is an instance of that type.
Clojure allows you to create records, which are custom, maplike data types. They're maplike in that they associate keys with values, you can look up their values the same way you can with maps, and they're immutable like maps.
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.
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 :)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With