The case of case equality operator
Feb 8, 2016 · 3 minute read · Commentsruby
Ruby is a nice and easy to read language. There are, however, some quirks which might surprise you. Some of them are easy to deal with when you start learning it, because they come up often enough. Certain keywords fall into this category, probably the most common is elsif
not being elseif
or else if
. A little less common is the switch statement. Most languages have switch
as a keyword for the whole decision block and case
for each branch, default
for the default catch-all case. Ruby uses case
, when
, and else
respectively.
Some of the strangeness comes from function names in the standard library. Personally, I often struggle with names of String functions. String#replace replaces the whole string, not a matching substring (what’s that even for?). String#tr replaces each character from its first argument by the corresponding character from the second argument, not a matching substring by the second argument. For that, you need String#sub (or String#sub!) and possibly String#gsub (or String#gsub!) to replace all occurrences. Of course, sub stands for substitute and it makes sense, but so does replace. Yet another example of strangely named function is String#strip. Yes, it strips the string of leading and trailing whitespace, but I haven’t yet encountered any other language which doesn’t call it trim.
These are all only cosmetic details and you get used to them as you write more Ruby code. There is, however, a construction which seems crazy when you look at it:
class Cat
end
class Dog
end
foo = Cat.new
case foo.class
when Cat
puts 'Meeow'
when Dog
puts 'Wuff, wuff'
else
puts 'What?'
end
# => What?
How is it, that a comparison of foo
’s class which is clearly an instance of the Cat
class doesn’t get matched? What has gone wrong with the world, especially when you try:
if foo.class == Cat
puts 'Meeow'
elsif foo.class == Dog
puts 'Wuff, wuff'
else
puts 'What?'
end
# => Meeow
The devil is hidden in the case equality operator ===. It is a method defined in the Object
class, i.e. the base class of everything in Ruby, just for case comparison. This method gets internally called when you write a case statement and by default it compares the class attribute of the objects on either side. Therefore, if you pass foo.class
the argument used is the Cat
class object (not foo
, an instance of that class) and Object#class is called on it. Class of Cat
is Class
; therefore, it falls through to the else
branch.
What you need to do, is leave out the call to Object#class
in the case statement and everything will be fine.
case foo
when Cat
puts 'Meeow'
when Dog
puts 'Wuff, wuff'
else
puts 'What?'
end
# => Meeow
There’s a great answer over at StackOverflow by Jörg W Mittag who suggests to think about the case equality operator rather as a set subsumption test operator (a === b
is true when b
can be considered a representative of a
) and demonstrates how this mental model helps to understand less common usage. Luckily, these fancy case
s are rarely seen in the wild except for books or tutorials about Ruby. Then you look at them and think “huh, that’s neat” and forget about it two pages later.
We're looking for developers to help us save energy
If you're interested in what we do and you would like to help us save energy, drop us a line at jobs@enectiva.cz.