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)]
(Point. (+ 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?
You must override hashCode() in every class that overrides equals(). Failure to do so will result in a violation of the general contract for Object. hashCode(), which will prevent your class from functioning properly in conjunction with all hash-based collections, including HashMap, HashSet, and Hashtable.
Case 1: Overriding both equals(Object) and hashCode() method Whenever it(hashcode) is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified.
If your model class will be used as a key of a Map or a Tree, or in a Set, its best practice to override equals and hashCode method (as it will be needed for comparing 2 model class objects). toString you will need to override, if you want you want to display your model class data in a efficient way.
To test if something satisfies a protocol, there's satisfies?
.
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}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With