9

I'm using Ruby's case syntax to set up some simple logic based on self.class as follows:

case self.class
 when FirstClass
   do stuff....
 when SecondClass
   do other stuff...
 end

I soon realized this always returns nil. Upon closer investigation, I found that case uses === rather than == to check equality. When running self.class == FirstClass in my terminal I get true as expected, however self.class === FirstClass returns false.

Looking into the ruby docs, I found the following explanation of ===:

Case Equality – For class Object, effectively the same as calling #==, but typically overridden by descendants to provide meaningful semantics in case statements.

Can anyone out there shed some light on what may be happening? Thanks in advance.

Andrey Deineko
  • 51,333
  • 10
  • 112
  • 145
Zensaburou
  • 875
  • 7
  • 8
  • This wasn't enlightening to me, but Class descends from Module, whose === impl is here: http://ruby-doc.org/core-2.2.0/Module.html#3D-3D-3D-method – Shea Levy May 15 '15 at 20:38
  • http://stackoverflow.com/questions/4467538/what-does-the-operator-do-in-ruby – Dreamweaver May 15 '15 at 20:44
  • http://truffles.me.uk/rubys-equality-operator – robbyphillips May 15 '15 at 20:45
  • It may help to know that the `#` in front of `==` just indicates that `==` is an instance method (much in the way that you often see things like `SomeClass#an_instance_method`). If literally wrote `#==` you would just end up commenting out the rest of the line. – neuronaut May 15 '15 at 20:45
  • Thanks for the tip - using that info, I found that `MyClass === my_instance` returns `true`, while `my_instance === MyClass` still returns `false`. Since I'm trying to set `case` based on `self.class`, I can't use the first case and the second still doesn't work. – Zensaburou May 15 '15 at 20:46
  • You wouldn't expect `a-b` to equal `b-a` or `a/b` to equal `b/a`, so why should you expect `a===b` to equal `b===a`? The reason, I expect, is simply because the method names `===` and `==` have a similar appearance and `a==b` if `b==a`, and because `===` is sometimes (unfortunately, perhaps) referred to as "case equality". If `===` were instead named `method7` and executed `a.method7(b)` would you expect it to return the same value as `b.method7(a)`? Probably not. – Cary Swoveland May 15 '15 at 23:50

1 Answers1

5

The clue is in “typically overridden by descendants to provide meaningful semantics in case statements”, in particular Module overrides it:

Case Equality — Returns true if obj is an instance of mod or one of mod’s descendants. Of limited use for modules, but can be used in case statements to classify objects by class.

So for modules === acts very much like the is_a? method (in fact it just calls the same implementing function in MRI Ruby, rb_obj_is_kind_of). In your example it evaluates to false because self.class isn’t an instance of FirstClass. It’s likely to just be an instance of Class. self alone however could be an instance:

case self
when FirstClass
  do stuff....
when SecondClass
  do other stuff...
end

(Although I think your design might not be quite right, testing the class of an object is usually a code smell. Instead you probably should have different implementations of a method in the objects.)

matt
  • 78,533
  • 8
  • 163
  • 197
  • Nice, works like a charm (great explanation as well). Re. the code smell: I definitely agree, it's kind of a funky use case - the `case` checks Class and then calls the appropriate variation of a method. Normally I'd just throw methods in their respective classes, but in this case I'm trying to get all the logic related to a specific feature in a single place. – Zensaburou May 15 '15 at 20:57
  • Good answer. Another example is [Regexp#===](http://ruby-doc.org/core-2.1.4/Regexp.html#method-i-3D-3D-3D). For example, `/a/==='cat' #=> true`, but it's really only used in `case` statements: `case 'cat'; when /a/...`. – Cary Swoveland May 16 '15 at 16:28