Preface
Just 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
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}"
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 do the same thing. I will come back to this 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
- 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}"
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:
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
- 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}"
f 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 ==
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}"
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. However, 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.
Supplement
* Stackoverflow - What is the Ruby <=> (spaceship) operator?
* [ Ruby Gossip ] Basic : 類別 - 物件相等性
沒有留言:
張貼留言