Overriding equals, hashCode and toString in a Clojure deftype
Asked Answered
A

1

11

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?

Americium answered 10/6, 2010 at 20:46 Comment(0)
M
7

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}
Melanimelania answered 10/6, 2010 at 21:23 Comment(4)
Does my implementation look pretty sound otherwise?Americium
Not sure I'm qualified to criticize it, but I expanded my answer a bit.Melanimelania
Cool, thanks Brian! Agree that defrecord looks like it will be great for this kind of thing in general, I guess that I'm playing with deftype just to push my understanding of "close to the metal" coding in Clojure as far as possible.Americium
If you haven't already, read the section Why have both deftype and defrecord? at clojure.org/datatypes . deftype seems to be meant for things like implementing bits of Clojure itself, or implementing new abstract data types of your own. defrecord seems to be meant for "data", i.e. your Points, or other data that's meant to be consumed at a high level. If you could easily replace your type with a hash-map, which in your case I think you could, then probably defrecord is what you want. Just my reading of it though.Melanimelania

© 2022 - 2024 — McMap. All rights reserved.