Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 487
mikera Avatar asked Nov 04 '11 02:11

mikera


People also ask

What is a Clojure protocol?

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.

What is Defrecord Clojure?

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.


3 Answers

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.

like image 163
Alex Taggart Avatar answered Oct 14 '22 06:10

Alex Taggart


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

like image 33
fmnoise Avatar answered Oct 14 '22 07:10

fmnoise


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.

like image 26
Jonas Avatar answered Oct 14 '22 05:10

Jonas