Reloaded a protocol and "No implementation of method"?
While protocols provide a nice way to program to interfaces, they do have some unfortunate interactions with repl driven development. It's annoying to be working along, reload a file, and then BOOM.
1. Unhandled java.lang.IllegalArgumentException No implementation of method: :draw of protocol: #'z.draw/Drawable found for class: z.core.Rectangle
But this code was just working! My
Drawable. It's right there. I can see it in the code!
Having code that looks like it should work, but getting runtime errors is annoying. Getting ones that aren't immediately understandable, about code that was just working, can frustrate anyone.
So what causes this error?
To duplicate this error imagine working on two files.
(ns z.core (:require [z.draw :as d] [clojure.string :as str])) (defrecord Rectangle [width height] d/Drawable (draw [t] (str "I'm a " width "x" height " rectangle!"))) (defn draw-many-rectangles  (let [rs (map ->Rectangle (range 3) (range 3))] (d/draw-many rs)))
(ns z.draw) (defprotocol Drawable (draw [t])) (defn draw-many [shapes] (map draw shapes))
Loading these files into a repl works. Calling
draw-many-rectangles outputs something expected.
z.core> (draw-many-rectangles) ("I'm a 0x0 rectangle!" "I'm a 1x1 rectangle!" "I'm a 2x2 rectangle!")
Now imagine having to change what the output is. Perhaps instead of a list of strings
draw-many needs to return a single string.
(ns z.draw (:require [clojure.string :as str])) (defprotocol Drawable (draw [t])) (defn draw-many [shapes] (str/join "\n" (map draw shapes)))
A common workflow is to reload a file into a repl once editing it is done. This could be with cider's
C-c C-x, or clojure's
load-file functions. Doing this for the
z.draw namespace and attempting to run
(draw-many-rectangles) will cause an error.
1. Unhandled java.lang.IllegalArgumentException No implementation of method: :draw of protocol: #'z.draw/Drawable found for class: z.core.Rectangle core_deftype.clj: 554 clojure.core/-cache-protocol-fn draw.clj: 4 z.draw/eval8029/fn/G core.clj: 2622 clojure.core/map/fn LazySeq.java: 40 clojure.lang.LazySeq/sval LazySeq.java: 49 clojure.lang.LazySeq/seq LazySeq.java: 71 clojure.lang.LazySeq/first RT.java: 653 clojure.lang.RT/first core.clj: 55 clojure.core/first string.clj: 185 clojure.string/join draw.clj: 8 z.draw/draw-many core.clj: 11 z.core/draw-many-rectangles REPL: 1 z.core/eval8047 ...
This happens because when the
z.draw namespace was reloaded it created a new protocol with the same name. But the
z.core namespace was evaluated with the previous reference. It's
defrecord referred to the old version of
Drawable. When creating a
Rectangle it will implement the old protocol.
How to fix
z.core namespace needs to be reloaded. Then it will redefine
Rectangle and refer to the correct
Drawable. This can be done by using the same load-file technique that loaded
Doing this by hand can be annoying. There are techniques to reduce the possibility of encountering this. One is to only define a protocol in a other wise empty namespace. All other functions go elsewhere. Then these types of errors can only occur when changing the protocol definition.
A tooling based solution is to use tools.namespace and something similar to reloadable systems in order to reload your entire application at once. I've been told cursive's "Load File in REPL" command implements a similar namespace reloading mechanism.