Details
-
Type:
Defect
-
Status:
Closed
-
Priority:
Major
-
Resolution: Declined
-
Affects Version/s: Release 1.2, Release 1.3
-
Fix Version/s: None
-
Component/s: None
-
Labels:None
-
Approval:Not Approved
Description
The following produces a NullPointerException:
(defprotocol Foo (act [this]))
(defrecord Fred []
Foo
(act [this] (println "done.")))
(defrecord Bar [act] ;; <<<<<======= Field name is the same as method name.
Foo
(act [this] (act (Fred.)))) ;; <<<<<====== Behaves as (nil (Fred.))
(act (Bar. nil))
Produces:
Exception in thread "main" java.lang.NullPointerException
at protocol.Bar.act(protocol.clj:8)
at protocol$eval66.invoke(protocol.clj:9)
at clojure.lang.Compiler.eval(Compiler.java:6465)
at clojure.lang.Compiler.load(Compiler.java:6902)
at clojure.lang.Compiler.loadFile(Compiler.java:6863)
at clojure.main$load_script.invoke(main.clj:282)
at clojure.main$script_opt.invoke(main.clj:342)
at clojure.main$main.doInvoke(main.clj:426)
at clojure.lang.RestFn.invoke(RestFn.java:408)
at clojure.lang.Var.invoke(Var.java:401)
at clojure.lang.AFn.applyToHelper(AFn.java:161)
at clojure.lang.Var.applyTo(Var.java:518)
at clojure.main.main(main.java:37)
–
The following works as expected:
(defprotocol Foo (act [this]))
(defrecord Fred []
Foo
(act [this] (println "done.")))
(defrecord Bar [x] ; <<== Field name is the DIFFERENT than method name
Foo
(act [this] (act (Fred.))))
(act (Bar. [1 2 3]))
Produces:
"done."
–
The following also works:
(defprotocol Foo (act [this]))
(defrecord Fred [])
(defrecord Bar [act]) ; <<== Field name is the same as method name
(extend-protocol Foo
Fred
(act [this] (println "done."))
Bar
(act [this] (act (Fred.))))
(act (Bar. [1 2 3]))
The thing to remember here is that protocol functions are not (only) methods on a class, they are functions global to the namespace. So notice the subtle difference here:
user=> (defprotocol Foo (act [this]))
Foo
user=> (defrecord Fred [act] Foo (act [this] (act this)))
user.Fred
user=> (Fred. 42)
#user.Fred{:act 42}
user=> (act (Fred. 42))
ClassCastException java.lang.Long cannot be cast to clojure.lang.IFn user.Fred (NO_SOURCE_FILE:1)
user=> (defrecord Fred [act] Foo (act [this] (user/act this)))
user.Fred
user=> (act (Fred. 42))
StackOverflowError user.Fred (NO_SOURCE_FILE:1)
So, it you want to access the data, you can use "act" directly, if you want to recursively call act (the protocol function), you can use recur, or the fully qualified name. Also, since these are records we're talking about, the following also works:
user=> (defrecord Fred [act] Foo (act [this] (:act this)))
user.Fred
user=> (act (Fred. 42))
42