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

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

···

On Fri, Aug 20, 2010 at 10:51 PM, Ralph Shnelvar <ralphs@dos32.com> 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

Colin,

Friday, August 20, 2010, 4:29:37 PM, you 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

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, thank you. Very clear.

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

Having read your explanation and Dave Thomas' *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 Fri, Aug 20, 2010 at 10:51 PM, Ralph Shnelvar <ralphs@dos32.com> wrote:

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.

2. 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.

3. 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

···

On Sat, Aug 21, 2010 at 12:13 AM, Ralph Shnelvar <ralphs@dos32.com> wrote:

Having read your explanation and Dave Thomas' *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?

Colin,

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

Having read your explanation and Dave Thomas' *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

Programming Ruby: The Pragmatic Programmer's Guide

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.

2. 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.

3. 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

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.

···

On Sat, Aug 21, 2010 at 12:13 AM, Ralph Shnelvar <ralphs@dos32.com> wrote:

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

···

--
Posted via http://www.ruby-forum.com/.

Hi --

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.

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

···

On Sat, 21 Aug 2010, Ralph Shnelvar wrote:

--
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:

Hi --

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.

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.

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.

David

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

···

On Sat, 21 Aug 2010, Ralph Shnelvar wrote:

Hi --

> 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

Maybe when I understand Ruby a lot better than I do that the
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

···

On Mon, 23 Aug 2010, Ralph Shnelvar wrote:

--
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