Overriding equals, hashCode and toString in a Clojure deftype
I'm trying to create a new type in Clojure using deftype to implement a two dimensional (x,y) coordinate, which implements a "Location" protocol.
I'd also like to have this implement the standard Java equals, hashCode and toString methods.
My initial attempt is:
(defprotocol Location
(get-x [p])
(get-y [p])
(add [p q]))
(deftype Point [#^Integer x #^Integer y]
Location
(get-x [p] x)
(get-y [p] y)
(add [p q]
(let [x2 (get-x q)
y2 (get-y q)]
(开发者_如何学GoPoint. (+ x x2) (+ y y2))))
Object
(toString [self] (str "(" x "," y ")"))
(hashCode [self] (unchecked-add x (Integer/rotateRight y 16)))
(equals [self b]
(and
(XXXinstanceofXXX Location b)
(= x (get-x b))
(= y (get-y b)))))
However the equals method still needs some way of working out if the b parameter implements the Location protocol.
What is the right approach? Am I on the right track?
To test if something satisfies a protocol, there's satisfies?
.
Edit:
Protocols and datatypes are too new in Clojure (and still evolving fast) for me to remark much about what's idiomatic or not. But you should note that defrecord
already implements type-and-value-based equality. Unless you really need a custom hashcode for your objects, you could consider using defrecord
.
(defrecord Point [#^Integer x #^Integer y]
Location
(get-x [p] x)
(get-y [p] y)
(add [p q]
(let [x2 (get-x q)
y2 (get-y q)]
(Point. (+ x x2) (+ y y2)))))
user> (= (Point. 1 2) {:x 1 :y 2})
false
user> (= (Point. 1 2) (Point. 1 2))
true
You also get the added bonus of being able to access your fields via keyword lookup, and being able to put metadata on your objects, which defrecord
gives you for free.
user> (:x (Point. 1 2))
1
It's possible that defrecord
-defined things will have custom reader syntax someday in Clojure, so they can be printed readably and read back in with the Clojure reader. Unless you're really attached to your version of toString
, you might keep this in mind as well. Right now, records already print human-readably if not machine-readably.
user> (Point. 1 2)
#:user.Point{:x 1, :y 2}
精彩评论