Clojure

defrecord with protocol implementation fails when field and method names collide

Details

  • Type: Defect Defect
  • Status: Closed Closed
  • Priority: Major Major
  • Resolution: Declined
  • Affects Version/s: Release 1.2, Release 1.3
  • Fix Version/s: None
  • Component/s: None
  • 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]))

Activity

Hide
Timothy Baldridge added a comment -

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.

Show
Timothy Baldridge added a comment - 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.
Hide
Timothy Baldridge added a comment - - edited

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

Show
Timothy Baldridge added a comment - - edited 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

People

Vote (0)
Watch (2)

Dates

  • Created:
    Updated:
    Resolved: