2

I'm trying to extend a simple java class toxi.color.ColorList with this protocol:

(defprotocol countable
  (count [this]))

(extend-protocol countable
  ColorList
  (count [this]
    (.size this)))

when i evaluate this code i see these warning

Warning: protocol #'dat00.protocols/countable is overwriting function count

WARNING: count already refers to: #'clojure.core/count in namespace: dat00.protocols, being replaced by: #'dat00.protocols/count

But this works fine:

(count (ColorList.))
=> 0

But if I try this in same file (or namespace)

(count (range 5))
=> IllegalArgumentException No implementation of method: :count of protocol: #'dat00.protocols/countable found for class: clojure.lang.LazySeq  clojure.core/-cache-protocol-fn (core_deftype.clj:541)

So my question is:
Am i misunderstanding some detail about protocols?

Thanks!

tangrammer
  • 3,041
  • 17
  • 24
  • 1
    Protocol methods cause functions of the same name to be defined in the current namespace. When you call a protocol method from some Clojure code, what you're actually calling is a generated function that looks up and dispatches to the appropriate implementation. – Alex Apr 23 '14 at 19:37
  • Thanks in advance Alex, but how would you solve this case "current namespace conflict"? – tangrammer Apr 23 '14 at 20:34
  • 1
    Same as you would any other namespace conflict, as described in the answers below. The fact that it happens to be a protocol method causing the conflict is of little importance. – Alex Apr 23 '14 at 21:02
  • Thanks @Alex I've published below the 2 working solutions for this case, with multimethods and extending protocol, both changing the function name – tangrammer Apr 24 '14 at 11:04

3 Answers3

4

You have a namespace collision.

When you define a protocol, you are defining dispatch functions in the current namespace. If you really want to use "count", you'll have to exclude the clojure.core version in your namespace declaration.

(ns so.protocols 
  (:refer-clojure :exclude [count]))

Now in that namespace you can define your protocol with a "count" method. If you then want the core version of count in that namespace, you can namespace prefix it clojure.core/count.

Users of your protocols will then want to alias your namespace. For example,

(ns user 
  (:require [so.protocols :as p]))

So that p/count is your protocol method and count is core.

A. Webb
  • 26,227
  • 1
  • 63
  • 95
  • thanks for explain the details to solve this namespace overlapping! But my problem (not very well explained) was also related to the expression problem http://stackoverflow.com/questions/3596366/what-is-the-expression-problem , then I wanted to use the same name-method-interfaz in the same case. – tangrammer Apr 24 '14 at 00:36
4

There is the function clojure.core/count and the method count defined in your protocol countable. Like the warning says: You overwrite the alias named count to clojure.core/count by creating an interface which has a method named count.

The LazySeq object returned by (range 5) doesn't implement your countable protocol. You could still count it with (clojure.core/count (range 5)).

What you probably want to do is to implement the clojure.lang.Counted interface instead of your own.

Leon Grapenthin
  • 9,246
  • 24
  • 37
  • 1
    "What you probably want to do is to implement the clojure.lang.Counted interface instead of your own." - Unfortunately OP wants to extend the protocol to an existing Java class - there's no indication that class can be modified. – Alex Apr 23 '14 at 19:39
  • Then he should create a Clojure wrapper via defrecord or using reify. – Leon Grapenthin Apr 23 '14 at 19:54
  • sorry @lgrapenthin i've unchecked your solution because I can not modify the Java class and, defrecord or reify don't work either beacuse there are times that i don't create the instances :( – tangrammer Apr 24 '14 at 11:01
  • You have two options: 1. You create a fully featured Clojure wrapper of the ToxicLib (at least the functionality you are using directly or indirectly). It's a lot of work and should only be done if there are significant benefits. 2. This is my advice: Just use Java Interop directly which is a common idiom in Clojure - `ColorList` is not even immutable, so why would you want to make it fit into Clojure? Just use `.size` - everything else will confuse readers of your code (I'd think - It's it a Clojure coll?). – Leon Grapenthin Apr 24 '14 at 11:40
0

Thanks to all,

I published here 2 working solution I've reached for this case, thanks to all for your comments

In all cases I have had to change the name of the function

It seems that multimethods also (I'm not yet sure about performance consequences...) can solve the expression problem here

(defmulti count type)

(defmethod count toxi.color.ColorList [a]
  (.size a))
(defmethod  count clojure.lang.LazySeq [a]
  (count a))

(defprotocol countable
  (get-count [this]))

(extend-protocol countable
  ColorList
  (get-count [this]
    (.size this))
  clojure.lang.LazySeq
    (get-count [this]
    (.count this))
  )

(get-count (ColorList.)) => 0
(get-count (range 5)) => 5

that works for me, although what I wanted to do (and with the ns colission I realized my conceptual error) was to use the "same-name-method" of different interfaces in the same ns :) ... suppose I was influenced by clojure/java interop sintaxis (the parameter defines the fn instead of the contrary) –

tangrammer
  • 3,041
  • 17
  • 24