Does Ruby Have Too Many Equality Tests?
This guest post is by Eric Anderson, who develops web-based applications for small businesses though his company Pixelware, LLC in Atlanta, GA. He also runs SaveYourCall.com which allows people to record phone calls from any phone without the need for any complicated hardware.
probably started using
== out of habit from other languages. It seems to
work and that seems good enough. But then you might start seeing
equal? and wonder what the heck those are about? How are
they different and why does Ruby make you insane with so many equality
This article will explain the purpose of each test. It will help you understand the intention of the test by looking at how the standard library defines them. Once you understand the intent you will know how to define that method in your own objects and what you should expect when you are using standard library and 3rd party objects.
== is your main equality test. Most of the time when you want to test
for equality this is what you will use. The intent of this method is “do
the objects have the same value regardless of class?” The following are
a few examples that will illustrate the behavior:
a = Object.new a == a # true a == Object.new # false "foo" == "foo" # true "foo".object_id == "foo".object_id # false 1 == 1.0 # true 1.class == 1.0.class # false
The first two examples show the default behavior when comparing
instances of class
Object. If the two objects are the same object in
memory it will return
true. But if it is compared to another instance of
Object it will return
The next example compares two
String instances with the same value. As
you can see when compared they return true even though they are
different objects in memory (as noted by the fact that their object id
false when compared). So even though the default behavior is
fairly strict subclasses should open up if they are able to decide the
objects have the same value.
The final example shows us the value comparison carries across different
1 is a
1.0 is a
Float. But since they still have
the same value they are considered equal. This means when implementing
your own objects you should ignore the class of the other objects and
concentrate solely on the value of the objects.
Also when implementing
== it is important to ensure the equality test is
a == b should return the same value as
b == a.
One last note on
==. You may occasionally see the
!= is not
a method you can define but a language feature. It will call your
method and then return the opposite boolean value returned by
eql? operates slightly more restrictive than
== it is concerned
with the value of the object and not concerned if two objects are the
same instance. But unlike
eql? does care about the class. Let’s look
at some examples:
a = Object.new a.eql? a # true a.eql? Object.new # false "foo".eql? "foo" # true "foo".object_id == "foo".object_id # false 1.eql? 1.0 # false
As you can see eql? starts out like ==, but when comparing the Fixnum to the Float the result is false even though they have the same value. An object is data plus behavior. Since == is intended to be ignorant of class it is basically ignoring differences in behavior and only concerned with differences in data. By using eql? you are indicating you not only care that the value is the same but that the behavior of the object is the same.
equal? is the most strict of any equality tests. It will test to ensure
that the objects being compared are the same instances in memory. This
means the value of their object id will be same as they are literally
two pointers to the same instance.
a = Object.new a.equal? a # true a.equal? Object.new # false "foo".equal? "foo" # false "foo".object_id == "foo".object_id # false 1.equal? 1 # true 1.object_id == 1.object_id # true
So our default behavior is like the other equality tests but the
similarities end there. When you compare two difference instances of
String with the same value you get
false. This is because despite having
the same value they are two different instances in memory.
The last example is an interesting quirk of Ruby. For performance and
memory optimization reasons there is only once instance of each value of
Fixnum. This means that even though we created our instances separately
they have the same object id and therefore
equal? will return
equal? conform to this intent. You should not override
this method in your subclasses. Doing so might cause unpredictable
behavior by the Ruby runtime.
=== can allow you to write code in a very concise and readable way.
is a way you can compare two objects using a single operator but having
the intent change depending on the context of the comparison. Lets give
a = Object.new a === a # true a === Object.new # false "foo" === "foo" # true "foo".object_id == "foo".object_id # false 1 === 1.0 # true 1.class == 1.0.class # false Fixnum === 1 # true (1..10) === 5 # true /o/ === 'foo' # true
As we can see this starts out looking a LOT like
==. We can see that the
comparison is concerned with value not the instance in memory. It also
doesn’t care about class. The last three examples really show this
method’s uniqueness. When comparing a class with an instance of that
class we get
true. When comparing a range with a value in that range we
true. When comparing a
regexp with a string it returns
true if the
regexp matches the string. Why does
this way? Because the
=== operator is used in the case control flow
statement. Review the following example:
case obj when "foo" then ... when Fixnum, Float then ... when 1..10 then ... when /o/ then ... end
This is the magic of
===. It lets the intent change depending on the
context of the comparison. So sometimes you want a simple value
==. Other times you want to know if an object is an
instance of the given class. Other times you want to know if a value is
in a range and sometimes you want to know if a regexp matches an object.
You have one operator the case statement can use that works for all
these types of comparisons.
It is important to note that just because
a === b, this does not imply
=== a. The order of the comparison is very important. For example
Fixnum will return
Fixnum === 1 returns
=~ is a pretty special equality operator.
Object defines it to always
false (even if comparing two objects that are the same instance).
Where this operator gets interesting is with the
Regexp class. Take a
look at the following examples:
a = Object.new a =~ a # false /o/ =~ 'foo' # true 'foo' =~ /o/ # true Mime::Type.new('application/xml') =~ 'text/xml' # true
=~ as alias to the match method. This provides the
=~ syntax found in languages like Perl while still providing a
fully object-oriented regexp library. Note that
reverse the operands. So
'foo =~ obj
will executeobj =~ ‘foo’
to be reversible when comparing aString
even though=~` is
not generally reversible.
Regexp is the primary place
=~ is used but the last example shows where
=~ is defined by a class in the Rails framework to allow mime type
aliases to match a specific mime type object.
== method there is an inverse of
=~ which is a language
feature and not a method that can be overridden. So if you see
=~ method will be called then the inverse of the result will be
So does Ruby have too many equality tests? I think not!
== obviously is
the work horse equality test. But the case statement would not be nearly
as elegant without
===. Testing a regexp would not be nearly as concise
== is great most of the time but sometimes it is too broad.
equal? are great way to be more precise.
When creating your own classes try to make sure that these methods are
conforming to their intent and not just inheriting the default behavior
Object. This can often make your API much more elegant.
I hope you found this article valuable. Feel free to ask questions and give feedback in the comments section of this post. Thanks!
Do also read these awesome Guest Posts:
- Why Use Single Sign-in Solutions in Rails?
- How does your code smell?
- Do YOU know Resque?
- Do You Understand Ruby’s Objects, Messages and Blocks?
- How Does One Use Design Patterns In Ruby?
- Do you know what’s new in Ruby 1.9?
- The value of a personal bug log
- Do You Enjoy Your Code Quality?
Also check out the free and paid Ruby-related eBooks from RubyLearning.