views:

208

answers:

1

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?

+6  A: 

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}
Brian Carper
Fantastic - that is just what I was looking for!
mikera
Does my implementation look pretty sound otherwise?
mikera
Not sure I'm qualified to criticize it, but I expanded my answer a bit.
Brian Carper
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.
mikera
If you haven't already, read the section *Why have both deftype and defrecord?* at http://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.
Brian Carper

related questions