Ruby Equality And Object Comparison

ComparisonJust like other object oriented languages, Ruby gives an object ways to find out if it is equal to, greater or less than another object. Object comparison is extremely important, not only do we tend to often explicitly compare objects to each other e.g.:

string1 = "abc"
if "abc" == string1
  puts 'they are equal'
end

but objects are frequently compared and tested for equality ‘behind the scenes’, by core and library classes (i.e. ordering of objects in collections etc.). So, lets not waste any time and jump straight in.

Testing Objects For Equality

Ruby has three main equality test methods, ==, eql? and equal?. These methods normally live in the Object class and since all other Ruby classes inherit from Object, they automatically gain access to these three methods. Inside the Object class all there methods do exactly the same thing, they test if two objects are exactly the same object. That is to say, both objects must have the same object id. We can easily demonstrate this e.g.:

string1 = "abc"
class MyObject
end
object1 = MyObject.new
object2 = object1
object3 = MyObject.new
 
puts "Object 1 is == to object 2: #{object1 == object2}"
puts "Object 1 is eql? to object 2: #{object1.eql? object2}"
puts "Object 1 is equal? to object 2: #{object1.equal? object2}"
puts "Object 1 is == to object 3: #{object1 == object3}"
puts "Object 1 is eql? to object 3: #{object1.eql? object3}"
puts "Object 1 is equal? to object 3: #{object1.equal? object3}"

The output is:

Object 1 is == to object 2: true
Object 1 is eql? to object 2: true
Object 1 is equal? to object 2: true
Object 1 is == to object 3: false
Object 1 is eql? to object 3: false
Object 1 is equal? to object 3: false

As you can see, when two variables are referencing the same object, calling any of the three equality methods will tell us that the two object are equal. As soon as two variables reference different objects (even if everything about the objects is otherwise identical), all three methods will tell us that the objects are unequal.

Of course, it is not very useful to have 3 different methods that do the same thing. Usually with Ruby we will tend to leave the equal? method alone to always give us the ability to find out if two objects are exactly the same. The eql? and == methods however are open to be redefined in any way we like. It might still seem a little strange to have two methods that can ostensibly do the same thing. I will come back to this point a little later. In the meantime lets look at the == method (or operator if you prefer).

You can redefine the == method to give your objects custom behavior when it comes to equality testing. Lets do this for a Sock class:

class Sock
  attr_reader :size
  def initialize size
    @size = size
  end
 
  def ==(another_sock)
    self.size == another_sock.size
  end
end

You may now compare your Sock objects in an intuitive fashion based on the size. More than that, by defining the == method on your object, you also get the != method for free which allows you to test your objects for inequality, very handy e.g.:

sock1 = Sock.new(10)
sock2 = Sock.new(11)
sock3 = Sock.new(10)
 
puts "Are sock1 and sock2 equal? #{sock1 == sock2}"
puts "Are sock1 and sock3 equal? #{sock1 == sock3}"
puts "Are sock1 and sock2 NOT equal? #{sock1 != sock2}"

Which produces:

Are sock1 and sock2 equal? false
Are sock1 and sock3 equal? true
Are sock1 and sock2 NOT equal? true

But, what if you want to compare two objects to find out which one is greater?

Comparing Ruby Objects

The == is not only an equality method, it is also part of a family of comparison methods that also include, >, <, >=, <=, and !=. Whenever you need to be able to compare your object and not just test for equality, redefining the == method is no longer enough and you must take a different approach.

In order to give your object the ability to be compared to other objects, you need to do two things:

  • Mix in the the Ruby Comparable module into your class
  • Define a method called <=>, this is known as the comparison method or ‘spaceship method’

Mixing in the module is pretty simple, but how do you define the <=> method? This is also fairly intuitive, if you’re familiar with any other object oriented language. If you’ve ever used the Comparable interface in Java for example, you’ll be right at home. Essentially, the method must return –1 when you think your current object is smaller than the one you’re comparing against. If you think it is larger you need to return +1, otherwise you return zero (the objects are equal). Let’s have a look at an example:

class Sock
  include Comparable
  attr_reader :size
  def initialize size
    @size = size
  end
 
  def <=>(another_sock)
    if self.size < another_sock.size
      -1
    elsif self.size > another_sock.size
      1
    else
      0
    end
  end
end

Defining the spaceship method allows your object to use the whole suite of comparison methods (==, >, <, >=, <=, and !=) e.g.:

sock1 = Sock.new(10)
sock2 = Sock.new(11)
sock3 = Sock.new(10)
 
puts "Are sock1 and sock3 equal? #{sock1 == sock3}"
puts "Are sock1 and sock2 NOT equal? #{sock1 != sock2}"
puts "Is sock1 > sock3? #{sock1 > sock3}"
puts "Is sock1 < sock2? #{sock1 < sock2}"
puts "Is sock1 >= sock3? #{sock1 >= sock3}"
puts "Is sock1 <= sock2? #{sock1 <= sock2}"

Which produces:

Are sock1 and sock3 equal? true
Are sock1 and sock2 NOT equal? true
Is sock1 > sock3? false
Is sock1 < sock2? true
Is sock1 >= sock3? true
Is sock1 <= sock2? true

If you ask me, this is a pretty decent amount of functionality to get out of defining just one method. It is interesting to note that you can easily define all those comparison methods separately, instead of defining the spaceship, if you were so inclined.

Also, notice that you automatically get the == method by defining the spaceship, so you don't need to provide separate equality behavior for your object any more. Although the Ruby built-in String class does define the == method separately from that provided by the <=> method, so you're still able to customize the behavior of comparison methods even if you've already defined the <=> method.

Eql? vs ==

Equality

I did say I would come back to this one :). As I mentioned in core Ruby classes the equal? method is used to to find out if two objects have the same object id (are the same object). The == is normally used to find out if two objects have the same value (as you would expect). The third method, eql? is normally used to test if two object have the same value as well as the same type. For example:

puts "Does integer == to float: #{25 == 25.0}"
puts "Does integer eql? to float: #{25.eql? 25.0}"

This gives:

Does integer == to float: true
Does integer eql? to float: false

You are of course welcome to reproduce similar behavior in your classes when you define your equality methods. However it does seem a little redundant to me. If you need two object to be equal then you will probably want them to be the same type in the first place (the above example with integers and floats is the only exception I can think of), in which case you would make sure they were the same type even in the == method. If you don't really care about type and just care about the behavior of the objects (which is entirely possible considering Ruby's dynamic nature, with duck typing and all), then you probably wouldn't test for type equality in either the == method or the eql? method. Basically the eql? becomes redundant.

There is only one scenario that I can see where the eql? method might come in handy. If you have to defined the <=> method, therefore giving your object an implicit == method, it may no longer make sense to redefine == separately (although as I mentioned above, nothing stops you from doing so). But if for some reason you need to keep the <=> equality behavior but still need even more custom equality behavior, you can always use the eql? method for this purpose. I can't really envisage a situation where you might want to do this, but the capability is certainly there.

This leaves the eql? method as a rather useless one. From what I can see, it is defined and used by many of the core Ruby classes as well as the library classes so you need to be aware of it and know when it is likely to come into play. However I can't really see you ever needing to define it for your own classes given that you have equal? and == already. I would welcome some discussion on this one. If someone known of a good reason to have the eql? method around then I would love to hear about it. As it stands, this is all you really need to know about equality and object comparison in Ruby. More Ruby discussion is coming up, so don't forget to subscribe to my feed if you don't want to miss it.

Images by The Artifex and rstrawser

  • JL

    Two requests:
    short term: please write something on ‘module’
    long term: write a book.

    • http://www.skorks.com Alan Skorkin

      The short term one is no problem, I can certainly write up a post about modules and related stuff (infact probably several posts :)). It is actually a good idea and I’ll put it on my list (which is a thousand miles long already, but who is counting :)).

      The long term is a little more difficult as you might expect, but I do appreciate the sentiment :).

  • weepy

    I was just having this problem with ActiveRecord. I’m not sure if it’s my mistake or a bug.

    http://gist.github.com/187951

    • http://www.skorks.com Alan Skorkin

      I am not a Rails expert, but from what I see this is what I think is happening. I don’t believe active record keeps a table of references to objects which should be the same objects. Here is what I mean:

      1) As far as your == method is concerned they are the same object
      2) When you look up a parent object from a child object, active record does not have an internal table to know that the parent object you get from the child is pointing to the same object as the parent object you already have
      3) Essentially whenever you get to a particular object through a different means, what you’re actually seeing will be a clone of the object as far as Ruby is concerned, so it will have a different object id
      4) As far as Active Record is concerned, it is the same object and if you look up the database id property on the two objects, just like you did the object_id property I think you will find they are the same. Which is why == reports true

      I wouldn’t worry too much about it, chances are they are doing it this way for performance reasons. If I think about it I can see how it may be difficult to implement active record in such a way as to keep object_id consistent.

      • http://outoftime.github.com Mat Brown

        Not keeping an object store for performance reasons? I’m not sure how performance benefits by re-instantiating the same object over and over – which may be why every (other?) serious ORM I’ve worked with guarantees one instance per row per session. And have fun with situations like this:

        blog.name = “My Blog”
        blog.posts.first.blog.name #=> Not “My Blog”!

        • http://www.skorks.com Alan Skorkin

          I see what you’re getting, like I said I am not a rails expert (yet :)), so was just giving my view on what I thought was happening, would be happy to hear a definitive answer from someone with skillz :).

  • steved

    Save your self a little typing on Sock#()

    def (another_sock)
    size another_sock.size
    end

  • steved

    My space ship got eaten.

    Above example is defining the comparison method. Imagine a <=> after the “def” and between “size” and “another_sock.size”.

    • http://www.skorks.com Alan Skorkin

      I agree you can certainly do that, I was trying to illustrate the point of how you would normally write the spaceship in a more complex implementation. In the wild if I had to do something as simple as what I did for Sock, I would do it the way you suggest.

  • apeiros

    My apologies if I’ve overseen something. But you seem to have missed the main purpose of eql?. #eql? in conjunction with #hash is used to determine hash-key equality and hence is actually probably the one you use the most often – even unknowingly and behind the scenes.
    Also might want to add === to your list of comparison operators here (I know you’ve covered it in another article). For one as it is used differently than in e.g. javascript or php which might confuse some new ruby users, so that can be set straight here. For another because it’s a rather tricky one. === is used in case/when operations (where you covered it), also by some methods like Enumerable#grep.

    Regards
    Stefan Rusterholz, @apeiros

    • http://www.skorks.com Alan Skorkin

      Ahh yes, you’re right I did totally miss that one :(. If the #eql? is the partner of the #hash then it is certainly very important especially if you want your objects to play nice with collections. A bit of a major oversight on my part, thanks for pointing this out.

  • Mabed

    You series on Ruby has been immensely helpful for me. Thank you!

    • http://www.skorks.com Alan Skorkin

      You’re welcome :)

  • Tarun Mittal

    A great post mann.. Cleared almost all my doubts.. And comment by apeiros was actually useful. Am new to ruby, and have started reading your posts. Thanks a lot :)

  • Sahil Verma

    So in summary regarding equality:

    * equal? is reference equality
    * == is value equality
    * eql? is value and type equality

  • Pedro Salgado

    A shorter version and more complete version of the code above could be done using

    def (another_sock)
    return self.size another_sock.size
    end

    I also found that sometimes you may want to compare against other classes so this would work better.

    def (other)
    if other.instance_of? self.class
    return self.size other.size
    elsif other.instance_of? Fixnum or other.instance_of? Integer or other.instance_of? Float
    return self.size other
    end
    raise ArgumentError.new(‘comparison of %s with %s failed!’ % [self.class, other.class])
    end

  • Pedro Salgado

    (on my previous comment, some characters were removed so here is another attempt)

    A shorter version and more complete version of the code above could be done using

    def (another_sock)
    return self.size <=> another_sock.size
    end

    I also found that sometimes you may want to compare against other classes so this would work better.

    def (other)
    if other.instance_of? self.class
    return self.size <=> other.size
    elsif other.instance_of? Fixnum or other.instance_of? Integer or other.instance_of? Float
    return self.size <=> other
    end
    raise ArgumentError.new(‘comparison of %s with %s failed!’ % [self.class, other.class])
    end

  • Pedro Salgado

    (on my 2 previous comments, some characters were removed so here is another attempt)
    A shorter version and more complete version of the code above could be done using

    def <=>(another_sock)
    return self.size <=> another_sock.size
    end

    I also found that sometimes you may want to compare against other classes so this would work better.

    def (other)
    if other.instance_of? self.class
    return self.size <=> other.size
    elsif other.instance_of? Fixnum or other.instance_of? Integer or other.instance_of? Float
    return self.size <=> other
    end
    raise ArgumentError.new(‘comparison of %s with %s failed!’ % [self.class, other.class])
    end

  • Pedro Salgado

    (on my 3 previous comments, some characters were removed so here is another attempt)
    A shorter version and more complete version of the code above could be done using

    def <=>(another_sock)
    return self.size <=> another_sock.size
    end

    I also found that sometimes you may want to compare against other classes so this would work better.

    def <=>(other)
    if other.instance_of? self.class
    return self.size <=> other.size
    elsif other.instance_of? Fixnum or other.instance_of? Integer or other.instance_of? Float
    return self.size <=> other
    end
    raise ArgumentError.new(‘comparison of %s with %s failed!’ % [self.class, other.class])
    end

  • Pingback: The more you know | steveshaw.ca

  • Pingback: RSpec fails when matching two classes, even though the classes are _identical_ | PHP Developer Resource

  • Pingback: » Ruby: Testing objects for equality SnowCrash