<< Back to previous view

[CLJ-906] defrecord with protocol implementation fails when field and method names collide Created: 01/Jan/12  Updated: 01/Mar/13  Resolved: 27/Nov/12

Status: Closed
Project: Clojure
Component/s: None
Affects Version/s: Release 1.2, Release 1.3
Fix Version/s: None

Type: Defect Priority: Major
Reporter: Brian Stiles Assignee: Unassigned
Resolution: Declined Votes: 0
Labels: None


 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]))



 Comments   
Comment by Timothy Baldridge [ 27/Nov/12 2:03 PM ]

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

Comment by Timothy Baldridge [ 27/Nov/12 2:07 PM ]

Declined since this is not really a bug. There is a work-around and no obvious solution to the more general problem of defrecord name collisions. If this still bugs you, please feel free to bring it up on clojure-dev, and we'll open a new ticket once a discussion has been had.

Generated at Fri Dec 19 08:33:17 CST 2014 using JIRA 4.4#649-r158309.