Case and class

Why does “YYY” get printed out here?


class XXX
end

x = XXX.new

case x.class
when XXX
puts “XXX”
else
puts “YYY” # surprise!
end

On Fri, Aug 20, 2010 at 10:51 PM, Ralph S. [email protected]
wrote:

when XXX
puts “XXX”
else
puts “YYY” # surprise!
end

It’s because of the definition of “===” which is used in the case
statement.
Does the following help?

class XXX
end

x = XXX.new

case x.class
when XXX then puts “XXX”
else puts “YYY” # surprise!
end #=> YYY

case x
when XXX then puts “XXX 2”
else puts “YYY 2”
end #=> XXX 2

case x.class
when Class then puts “XXX 3”
else puts “YYY 3”
end #=> XXX 3

Colin,

Friday, August 20, 2010, 4:29:37 PM, you wrote:

CB> On Fri, Aug 20, 2010 at 10:51 PM, Ralph S. [email protected]
wrote:

Why does “YYY” get printed out here?


class XXX
end

x = XXX.new

case x.class
when XXX
puts “XXX”
else
puts “YYY” # surprise!
end

CB> It’s because of the definition of “===” which is used in the case
statement.
CB> Does the following help?

CB> class XXX
CB> end

CB> x = XXX.new

CB> case x.class
CB> when XXX then puts “XXX”
CB> else puts “YYY” # surprise!
end #=>> YYY

CB> case x
CB> when XXX then puts “XXX 2”
CB> else puts “YYY 2”
end #=>> XXX 2

CB> case x.class
CB> when Class then puts “XXX 3”
CB> else puts “YYY 3”
end #=>> XXX 3

Colin, thank you. Very clear.

Now it’s my turn to complain about the unexpected behavior.

Having read your explanation and Dave T.’ Programming Ruby 1.9
book about the use of === in comparisons in case statements …

Is it he fact that case uses == (two ='s) for non-Class when’s and
uses === (three ='s) for when’s that are Classes?

It just seems to me to be an unnecessary surprise to do things
differently for objects of class Class.

Indeed
x.class == XXX # => true

X.class is XXX … and yet the when does not pick it up because ===
is being used for comparison.

Anyway, thank for your lucid and well-written response.

On Sat, Aug 21, 2010 at 12:13 AM, Ralph S. [email protected]
wrote:

Having read your explanation and Dave T.’ Programming Ruby 1.9 book
about the use of === in comparisons in case statements …

Is it he fact that case uses == (two ='s) for non-Class when’s and uses
=== (three ='s) for when’s that are Classes?

No (I think the following is right, but I’d welcome any corrections) -
case
always uses “===”, it’s just that the meaning of “===” may be different
to
“==”.

  1. case always uses “===” for comparisons; in
    case x
    when y then
    the test is y === x

http://ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html

Common comparison operators, Operator Meaning
== Test for equal value.
=== Used to test equality within a when clause of a case statement.

case operates by comparing the target (the expression after the keyword
case) with each of the comparison expressions after the when keywords.
This
test is done using comparison === target. As long as a class defines
meaningful semantics for === (and all the built-in classes do), objects
of
that class can be used in case expressions.

Ruby classes are instances of class Class, which defines === as a test
to
see if the argument is an instance of the class or one of its
superclasses.

  1. class Object - RDoc Documentation
    obj === other => true or false
    Case Equality—For class Object, effectively the same as calling #==, but
    typically overridden by descendents to provide meaningful semantics in
    case
    statements.

  2. class Module - RDoc Documentation
    (remembering that Class is a subclass of Module, which I forget when I
    initially couldn’t find the “===” method in Class)
    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.

Putting that all together, and using the String class as an example:
x = “Karel Capek”
y = “Karel Capek”
p x.object_id #=> 1880688
p y.object_id #=> 1880656, so x & y are different objects
p x == y #=> true
p x === y #=> true, so for String objects “===” is the same as “==”
# at least for comparing String objects with String objects

p x == String #=> false
p x === String #=> false

p x.class == String #=> true
p String == String #=> true
p x.class === String #=> false
p String === String #=> false

p String == x #=> false
p String === x #=> true; this is what is being used in the case
statement
# case x; when String then true; else false; end
p String == x.class #=> true
p String === x.class #=> false; same as String === String

I think of === as meaning ‘matches’ rather than ‘equals’. This is
especially useful as in

case str
when /^foo/
… do something
when /^bar/
… something else
end

For classes, you have
case obj
when String

when Integer

end

Colin,

Friday, August 20, 2010, 8:05:36 PM, you wrote:

CB> On Sat, Aug 21, 2010 at 12:13 AM, Ralph S. [email protected]
wrote:

Having read your explanation and Dave T.’ Programming Ruby 1.9 book
about the use of === in comparisons in case statements …

Is it he fact that case uses == (two ='s) for non-Class when’s and uses
=== (three ='s) for when’s that are Classes?

CB> No (I think the following is right, but I’d welcome any corrections)

  • case
    CB> always uses “===”, it’s just that the meaning of “===” may be
    different to
    CB> “==”.

CB> 1. case always uses “===” for comparisons; in
CB> case x
CB> when y then
CB> the test is y === x

CB> Programming Ruby: The Pragmatic Programmer's Guide

CB> Common comparison operators, Operator Meaning
CB> == Test for equal value.
CB> === Used to test equality within a when clause of a case statement.

CB> case operates by comparing the target (the expression after the
keyword
CB> case) with each of the comparison expressions after the when
keywords. This
CB> test is done using comparison === target. As long as a class defines
CB> meaningful semantics for === (and all the built-in classes do),
objects of
CB> that class can be used in case expressions.
CB> …
CB> Ruby classes are instances of class Class, which defines === as a
test to
CB> see if the argument is an instance of the class or one of its
superclasses.

CB> 2. class Object - RDoc Documentation
obj === other =>> true or false
CB> Case Equality—For class Object, effectively the same as calling #==,
but
CB> typically overridden by descendents to provide meaningful semantics
in case
CB> statements.

CB> 3. class Module - RDoc Documentation
CB> (remembering that Class is a subclass of Module, which I forget when
I
CB> initially couldn’t find the “===” method in Class)
mod === obj =>> true or false
CB> Case Equality—Returns true if anObject is an instance of mod or one
of mod‘s
CB> descendents. Of limited use for modules, but can be used in case
statements
CB> to classify objects by class.

CB> Putting that all together, and using the String class as an example:
CB> x = “Karel Capek”
CB> y = “Karel Capek”
p x.object_id #=>> 1880688
p y.object_id #=>> 1880656, so x & y are different objects
p x == y #=>> true
p x === y #=>> true, so for String objects “===” is the same as “==”
CB> # at least for comparing String objects with String
objects

p x == String #=>> false
p x === String #=>> false

CB> p x.class == String #=> true
CB> p String == String #=> true
CB> p x.class === String #=> false
CB> p String === String #=> false

p String == x #=>> false
p String === x #=>> true; this is what is being used in the case
statement
CB> # case x; when String then true; else false; end
CB> p String == x.class #=> true
CB> p String === x.class #=> false; same as String === String

What a great answer. And, again, clear.

I can, reluctantly, see the use of === in an if statement.

To me,
case x
when XXX then …
end

would be MUCH clearer as
case x.class
when XXX.arrayOfDecendants
end

Of course, the code above is illegal … but would have been clearer …
at least to me.

Hi –

On Sat, 21 Aug 2010, Ralph S. wrote:

end

Of course, the code above is illegal … but would have been clearer … at least to me.

It doesn’t suggest the same functionality, though. This:

case obj
when XXX

runs XXX === obj (as per Colin’s explanation), and that examines whether
or not XXX is in the method look-up path of obj. This may or may not
have anything to do with obj’s class – for example:

class C; end
=> nil
module M; end
=> nil
c = C.new.extend(M)
=> #<C:0x14cd14>
case c
when M; 1
end
=> 1

If you want to know whether a given class has a certain ancestor, you
can do that too, but with a different technique:

if C.ancestors.include?(B)

etc.

In general, the === mechanism is advantageous because it means you
always know what’s going on in a case statement, and because it gives
you control over how your own objects behave in case statements.

David


David A. Black, Senior Developer, Cyrus Innovation Inc.

The Ruby training with Black/Brown/McAnally
Compleat Philadelphia, PA, October 1-2, 2010
Rubyist http://www.compleatrubyist.com

David,

Sunday, August 22, 2010, 5:15:16 PM, you wrote:

DAB> Hi –

DAB> On Sat, 21 Aug 2010, Ralph S. wrote:

I can, reluctantly, see the use of === in an if statement.

To me,
case x
when XXX then …
end

would be MUCH clearer as
case x.class
when XXX.arrayOfDecendants
end

Of course, the code above is illegal … but would have been clearer … at least to me.

DAB> It doesn’t suggest the same functionality, though. This:

DAB> case obj
DAB> when XXX

DAB> runs XXX === obj (as per Colin’s explanation), and that examines
whether
DAB> or not XXX is in the method look-up path of obj. This may or may
not
DAB> have anything to do with obj’s class – for example:

DAB> >> class C; end
DAB> => nil
DAB> >> module M; end
DAB> => nil
DAB> >> c = C.new.extend(M)
DAB> => #<C:0x14cd14>
DAB> >> case c
DAB> >> when M; 1
DAB> >> end
DAB> => 1

DAB> If you want to know whether a given class has a certain ancestor,
you
DAB> can do that too, but with a different technique:

DAB> if C.ancestors.include?(B)

DAB> etc.

DAB> In general, the === mechanism is advantageous because it means you
DAB> always know what’s going on in a case statement, and because it
gives
DAB> you control over how your own objects behave in case statements.

Responding to this last paragraph …

If I were the language designer I’d have two “cases”
(1) case== which would use the normal == semantics
(2) case=== which would use the === semantics.

Again … at least this would be clearer to ME.

DAB> David

Maybe when I understand Ruby a lot better than I do that the explanation
you provided will make more sense.

Hi –

On Mon, 23 Aug 2010, Ralph S. wrote:

DAB> => nil
explanation you provided will make more sense.
Here’s some annotation:

Every object has a lookup path, consisting of classes and modules in a
particular order, which it traverses when it’s trying to resolve a
method name. If you extend an object with a module (see above), you
insert that module into the lookup path of the object. Any module or
class that’s in the lookup path of an object (including, but not limited
to, the object’s class and modules included in that class) will match
the object for purposes of case equality.

So what I was getting at in my example is that knowing an object’s
class, and even that class’s ancestors, doesn’t tell you everything that
Module#=== tells you. In the example, the “1” shows that M === c, even
though c’s class does not include M.

David


David A. Black, Senior Developer, Cyrus Innovation Inc.

The Ruby training with Black/Brown/McAnally
Compleat Philadelphia, PA, October 1-2, 2010
Rubyist http://www.compleatrubyist.com