WTF? Case statement disfunctional?

Hi

I have a simple problem with the class of an object not being recognised
by the case statement. Am I just being an idiot again or is there
something more sinister going on here?

a=1
case a.class
  when Fixnum
    puts "It's a number silly"
  else
    puts "Can't figure it out"
end

This snippet of code returns "Can't figure it out" which is just wrong?

a.is_a? Fixnum => true and a.class => fixnum and a.class == Fixnum =>
true, so why dont my case statement work?

case a.class.to_s
  when "Fixnum"
...

does the trick, but it just offends my aesthetics.

Any ideas? Comments?

Regards

Pieter Hugo
South Africa

···

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

Change it to
a = 1
case a
when Fixnum
   puts "It's a number silly"
else
   puts "Can't figure it out"
end

Case statement turns into ===
and Fixnum === Fixnum is false, however, Fixnum === 1 is true

···

On Tue, Jul 6, 2010 at 6:21 AM, Pieter Hugo <pieter@internext.co.za> wrote:

Hi

I have a simple problem with the class of an object not being recognised
by the case statement. Am I just being an idiot again or is there
something more sinister going on here?

a=1
case a.class
when Fixnum
   puts "It's a number silly"
else
   puts "Can't figure it out"
end

This snippet of code returns "Can't figure it out" which is just wrong?

a.is_a? Fixnum => true and a.class => fixnum and a.class == Fixnum =>
true, so why dont my case statement work?

case a.class.to_s
when "Fixnum"
...

does the trick, but it just offends my aesthetics.

Any ideas? Comments?

Regards

Pieter Hugo
South Africa
--
Posted via http://www.ruby-forum.com/\.

In a case expression, to decide which of the branches should be executed, ruby
calls the === method of the object after each when clause, passing as argument
the object after the case clause: the branch which will be executed is the
first one for which this method returns true.

What does this mean in your code? It means that ruby will call Fixnum.===
passing it a.class, which means Fixnum. But Fixnum.===, which is the same as
Class#=== since Fixnum is an instance of Class, returns true if the argument
is an instance of Fixnum and false otherwise. Of course, Fixnum is not an
instance of Fixnum, so that branch is not executed.

To achieve what you want, you need to write

case a

rather than

case a.class

This way, the object passed to Fixnum.=== will be a, which is indeed an
instance of Fixnum.

It is very easy to be confounded by this issue (it also happens to me from
time to time).

I hope this helps

Stefano

···

On Tuesday 06 July 2010, Pieter Hugo wrote:

>Hi
>
>I have a simple problem with the class of an object not being recognised
>by the case statement. Am I just being an idiot again or is there
>something more sinister going on here?
>
>a=1
>case a.class
> when Fixnum
> puts "It's a number silly"
> else
> puts "Can't figure it out"
>end
>
>This snippet of code returns "Can't figure it out" which is just wrong?
>
>a.is_a? Fixnum => true and a.class => fixnum and a.class == Fixnum =>
>true, so why dont my case statement work?
>
>case a.class.to_s
> when "Fixnum"
>...
>
>does the trick, but it just offends my aesthetics.
>
>Any ideas? Comments?
>
>Regards
>
>Pieter Hugo
>South Africa

Hi guys

Thanks for the prompt and helpful responses. Your proposals work, but I
am still unhappy about spending an hour tracking this bug.

So CASE uses '===' and not '==', am I correct? So why does

a = 1
a === Fixnum

==> false

Furthermore, In my original example, one would expect a.class === Fixnum
to return true in any case, as '===' is supposed to be more forgiving
than '==', but it doesn't.

This is all a bit counter intuitive to me and !(least surprising).

Pieter

···

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

Hi guys

Thanks for the prompt and helpful responses. Your proposals work, but I
am still unhappy about spending an hour tracking this bug.

So CASE uses '===' and not '==', am I correct? So why does

a = 1
a === Fixnum

==> false

but,
  Fixnum===a #=>true

=== is not symmetric with respect to its arguments. I forget what the
right word for that is.

Furthermore, In my original example, one would expect a.class === Fixnum
to return true in any case, as '===' is supposed to be more forgiving
than '==', but it doesn't.

Don't think of === as a 'more forgiving' ==. It doesn't provide a
superset of =='s behavior; it's more like a better equality operator
for use in case expressions. === actually has rather complicated
semantics which depend on the type of its left-side argument, so it's
hard to come up with a pithy one-line description.

···

On 7/6/10, Pieter Hugo <pieter@internext.co.za> wrote:

>Hi guys
>
>Thanks for the prompt and helpful responses. Your proposals work, but I
>am still unhappy about spending an hour tracking this bug.
>
>So CASE uses '===' and not '==', am I correct? So why does
>
>a = 1
>a === Fixnum
>
>==> false

Because that uses

1 === Fixnum

that is it calls the === method of 1, rather than the === method of Fixnum.
Fixnum#=== works like Fixnum#== (note here we're speaking of the === instance
method of Fixnum, as shown by the use of the # symbol, rather than of the ===
class method I spoke of in my other message and which is indicated using the .
symbol), so 1 === Fixnum returns false because Fixnum is not a number and so
can't be equal to 1.

>Furthermore, In my original example, one would expect a.class === Fixnum
>to return true in any case, as '===' is supposed to be more forgiving
>than '==', but it doesn't.

You shouldn't think of === as being more forgiving than ==. According to the
documentation of Object#===:

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

This means that === is not a more forgiving version of ==. It's an operator
which is written so that it makes things easier in a case statement. For
example, take the class Regexp. Suppose I have a string str and I want to test
which regexp matches it. I can write:

case str
when /a/
  ...
when /b/
  ...
when /c/
  ...
else
  ...
end

This can be done because Regexp#=== returns true or false depending on the
value returned by its match? method.

The same happens for Ranges. If n is an integer, I can write:

case n
when 1..3
  ...
when 5..6
  ...
else
  ...
end

Again, this works as I expect because Range#=== uses the include? method to
determine whether to return true or false.

For certain classes, instead, the more sensible behaviour for this method is
to work like ==. This is what happens with Integer, String and Array, for
example.

Now, regarding your case. When you put a class in a When clause, what do you
usally want to do? Check whether the object being tested is an instance of
that class. So Class#=== returns true if the object is an instance of the
class and false if it isn't. And since Fixnum is not an instance of Fixnum,
you get false.

>This is all a bit counter intuitive to me and !(least surprising).

Once learnt that case statements use the === operator (and I think most of the
ruby books mention it when they speak of case), I had no issue with this.
Indeed, I think it's a simple way to be able to use the case statement in a
meaningful way also with custom classes (otherwise, you'd be limited to use it
only with built-in classes, as in C, where you can't even use it with
strings).

I hope what I said clarified things at least a bit.

Stefano

···

On Tuesday 06 July 2010, Pieter Hugo wrote:

Hi Guys

Once again thanks for your patience with me. I have been programming in
ruby for about 1.5 years now and would like to start thinking that I'm
not quite a newbie anymore, but then something like this ups and smacks
me on the chin. I'm on track again though and loving it.

Regards

Pieter

···

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

Caleb Clausen wrote:

Don't think of === as a 'more forgiving' ==. It doesn't provide a
superset of =='s behavior; it's more like a better equality operator
for use in case expressions. === actually has rather complicated
semantics which depend on the type of its left-side argument, so it's
hard to come up with a pithy one-line description.

I think of "pattern === val" as a general pattern matching predicate, which makes the asymmetry more palatable. It's kind of unfortunate that the "===" string itself is symmetrical.

Maybe you meant "noncommutative".

Kind regards

robert

···

2010/7/6 Caleb Clausen <vikkous@gmail.com>:

On 7/6/10, Pieter Hugo <pieter@internext.co.za> wrote:

Thanks for the prompt and helpful responses. Your proposals work, but I
am still unhappy about spending an hour tracking this bug.

So CASE uses '===' and not '==', am I correct? So why does

a = 1
a === Fixnum

==> false

but,
Fixnum===a #=>true

=== is not symmetric with respect to its arguments. I forget what the
right word for that is.

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

Just making sure we do not overlook anything: you are aware of the
other form of case, are you?

irb(main):001:0> case
irb(main):002:1* when 1 > 2 then puts "silly"
irb(main):003:1> when 1 > 20 then puts "extraordinary silly"
irb(main):004:1> when 1 > 0 then puts "that's just right"
irb(main):005:1> else
irb(main):006:1* puts "now what?"
irb(main):007:1> end
that's just right
=> nil
irb(main):008:0>

Note, I am not saying that this would be more appropriate in your case
- just trying to take care of your chin. :slight_smile:

Kind regards

robert

···

2010/7/6 Pieter Hugo <pieter@internext.co.za>:

Once again thanks for your patience with me. I have been programming in
ruby for about 1.5 years now and would like to start thinking that I'm
not quite a newbie anymore, but then something like this ups and smacks
me on the chin. I'm on track again though and loving it.

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/