I’ve not found this stated clearly enough elsewhere so I’m doing so myself.
Ruby’s case statement calls the ===
method on the argument to each of the when statements
So, this example:
case my_number
when 6883
:prime
end
Will execute 6883 === my_number
This is all fine and dandy, because the ===
method on a Fixnum instance does what you’d expect in this scenario.
However, the ===
method on the Fixnum class does something different. It’s an alias of is_a?
That is cute, because it allows you to do this:
case my_number
when Fixnum
"Easy to memorize"
when Bignum
"Hard to memorize"
end
But it won’t work as you might expect in this scenario:
my_type = Fixnum
case my_type
when Fixnum
"Fixed number"
end
This won’t work because Fixnum === Fixnum
returns false
because the Fixnum
class is not an instance of Fixnum
.
My workaround for this is to convert it to a string first. Not sure if that’s the best solution, but it works for me(tm).
my_type = Fixnum
case my_type.to_s
when "Fixnum"
"Fixed number"
end
Comments
Any idea of the original rationale for the crazed implementation of Fixnum.=== ?
>> Fixnum === Fixnum
=> false
doesn’t seem like a good move to me!
Rahim, it’s possible that it’s optimized for case statement usage, which is pretty neat to be honest. Was just unexpected to me until I understood what was going on.
In fact, the docs suggest that:
http://www.ruby-doc.org/core/classes/Object.html#M000345
=)
I read the docs and still couldn’t see when that choice would help for case statements (perhaps because I was biased by your example…)
Another, at first glance odd, result this leads to is eg:
>> Fixnum === 2
=> true
>> 2 === Fixnum
=> false
I think misread your initial post, I thought you were saying your second example didn’t work as expected (which looking at it again looks like a pretty common pattern which made it all the more surprising).
Googling gives a few people who’ve discussed the same behavior (http://www.justskins.com/forums/doing-a-case-on-class-23663.html, http://blog.jayfields.com/2007/03/ruby-operator.html), both of whom quote the ri docs for Module#=== which explains my observations :
——————————————- Module#===
mod === obj => true or false
—————————————————–
Case Equality—Returns +true+ if _anObject_ is an instance of
_mod_ or one of _mod_’s descendents. Of limited use for modules,
but can be used in +case+ statements to classify objects by class.
Do this =>
my_type = Fixnum
case my_type
when Fixnum:
“Fixed number”
end
Nope, that returns nil.
The point of confusion is that ‘===’ is a poor mnemonic for the particular kind of binary relation that it represents. It gives the impression that it acts as an identity check or some other equivalence relation, with which Ruby’s conception of case checking is incompatible.
The point, then, is that the method ‘===’ is arbitrarily named, and might have been named more aptly. For example, ‘case_of?’ conveys that the object is being tested as belonging to a group of objects of which it is a special case. 2 is a special case of 2, just as it is a special case of Fixnum or Numeric, or even (1..7).
Thank you for this insight! After banging my head against why a case statement on my_object.class comparing class types wasn’t working, this post saved the day.