Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clojure Protocol Implementation not Found for Record Type in other namespace

We are having a few issues with records and protocols in different namespaces.

We have a protocol in namespace foo.proto.

(ns foo.proto)

(defprotocol Proto
   (do-stuff [this x y]))

I have a record RecordA in namespace foo.record:

(ns foo.record
  (:require [foo.proto :as proto]))

(defrecord RecordA [bar])

;; RecordA implements the protocol:

(extend-type RecordA
    proto/Proto
    (do-stuff [this x y] (* x y (:bar this))))

This works fine as long as we are in the repl. Now if we on the otherhand make an uberjar and run the code we get:

No implementation of method: :do-stuff of protocol: #'foo.proto/Proto found for class

If we on the other hand implement the protocol in the type declaration like so:

(defrecord RecordA [bar]
    proto/Proto
    (do-stuff [this x y] (* x y (:bar this))))

We no longer get the error (which took some time to figure out). Also if we move the declaration of Proto into the same ns as RecordA we do not get the error either.

My questions:

  1. What is the difference between implementing in the declaration and in extend-type or extend-protocol?

  2. Why would it work if we move the Record and Protocol declarations into the same ns?

Thanks

like image 585
Thomas Børlum Avatar asked Sep 24 '13 15:09

Thomas Børlum


2 Answers

The issue may be in how you are including the record and protocol within the file you are using them. The following works for me:

record.clj

(ns testclojure.record
  (:require [testclojure.proto :as proto]))

(defrecord RecordA [bar])

(extend-type RecordA
  proto/Proto
  (do-stuff [this x y] (* x y (:bar this))))

proto.clj

(ns testclojure.proto)

(defprotocol Proto
  (do-stuff [this x y]))

core.clj

(ns testclojure.core
  (:require [testclojure.record :refer [->RecordA]]
            [testclojure.proto :refer :all])
  (:gen-class))

(defn -main [& args]
  (-> (->RecordA 2)
      (do-stuff 2 6)
      (println)))

After lein uberjar and running the jar directly, I get the correct answer of 24.

As for why it works with different combinations of namespaces and extending, defrecord creates a Java class while extend-type creates an entry in the :impls collection of the protocol.

like image 86
Jared314 Avatar answered Sep 29 '22 13:09

Jared314


I'm running into what looks like the same issue:

I'm trying to extend a protocol to a Record I have defined separate from the Protocol implementation, i.e. I'm using (extend-protocol) rather than defining the implementation inline with the record. Both the Record, the protocol and the implementation are in the same namespace. However when I attempt to call it, it complains that no implementation exists.

(extend-protocol MyProtocol
  myns.message.Message
 (my-protocol-function [this] (println "got here")))


(my-protocol-function new-msg)

=> IllegalArgumentException No implementation of method: :my-protocol-function of protocol: #'myns.connector/MyProtocol found for class: myns.message.Message  clojure.core/-cache-protocol-fn (core_deftype.clj:544)

However if I look at extenders, I see my record there

(extenders MyProtocol)
=> (myns.message.Message)

However (extends?) is false

(extends? MyProtocol myns.message.Message)
=> false

If I inline the protocol definition into the record, all works as expected.

like image 20
Adam K Avatar answered Sep 29 '22 11:09

Adam K