Hi Jim:
I think the problem is that you are actually calling the
getDistance protocol function with only 2 arguments in the line in bold below:
(defrecord LevenshteinDistance []
IDistance
(getDistance [_ s1 s2]
(getDistance s1 s2)) ;; <- Calling a getDistance function with 2 args
(getDistance [_ s1 s2 weight]
(getDistance s1 s2 weight)))
While in the protocol definition there's only a 3 and 4 arguments declarations for getDistance.
(defprotocol IDistance
(getDistance
[this s1 s2] ; 3 args
[this s1 s2 m-weight])) ; 4 args
You could add a 2 arguments override for the getDistance protocol function and it would work by calling the 2 args implementation added to String, which is actually never registered in the protocol and goes unnoticed for the reason that follows, which I myself found out while experimenting with your code, you can skip it if you like, I just had a little fun investigating some Clojure code :).
When using extend-type, any implementation for a protocol function with a number of args not present in the protocol's declaration doesn't seem to produce any errors or warnings (extend presents the same behavior, which makes sense since extend-type uses it).
(defprotocol SomeProtocol
(some-function [this x] [this x y]))
(defrecord SomeRecord [])
(extend-type SomeRecord
SomeProtocol
(some-function
([_] (println "1-arg")) ; this is not declared
([_ _ _ _ _] (println "5-arg")) ; this is not declared
([_ x y]
(some-function x y)))
However, a CompilerException is thrown when implementing a protocol using the defrecord macro, and declaring a non-existing override for the function. I looked a little bit into the code of defrecord and the reason for this seems to be that it ultimately uses deftype which actually creates a class that implements the methods for the Java interface that the protocol defines, the compiler checks in this case if there's any method with the name and arity with the supplied implementation, and throws an exception if it doesn't.
(defprotocol SomeProtocol
(some-function [this x] [this x y]))
(defrecord SomeRecord []
SomeProtocol
(some-function [_] (println "1-arg")) ; this is not declared
(some-function [_ x y]
(some-function x y)))
#<CompilerException java.lang.IllegalArgumentException: Can't define method not in interfaces: some_function, compiling: (file.clj)>
This seems to indicate that using extend (or extend-type) vs. deftpye (or defrecord) for implementing a protocol yields two different results, the former registers the function implementations in the protocol using the map supplied and the latter actually creates a class method for the record or type class generated.
This was not obvious at all to me and I think I even recall reading somewhere (can't remember exactly where and can't find it right now) that the defrecord "inline" implementation was just a convenience form for extend/extend-type, but is it possible that it's actually more performant to use the defrecord/deftpye?
Hope it helps,
Juan