Case statement pitfall when migrating to Ruby 1.9.2

Note that the pitfall is limited to MRI (standard Ruby) version 1.9.2. MRI 1.9.3, JRuby and Rubinius does not have this behavior.

I have been using Rubinius 2.0 to run machine learning experiments with libsvm lately. When running in Ruby 1.9.2, I noticed that my classifier always classified all samples as negative. I though this was caused by issues with libsvm-ruby-swig so I recompiled libsvm-ruby-swig from scratch including rerunning swig, but nothing changed. Next, I changed to use libsvmffi instead, but the result was the same. Realizing that I actually had some tests running av very simple classifier and that these tests passed on 1.9.2 made me look closer at the code. What I found was that the behavior of the Ruby case statement has changed from 1.8.7 to 1.9.2.

For if statements, 1 is equal to 1.0 in both 1.8.7 and 1.9, but while 1 matches 1.0 in 1.8.7 case statements, it does not in 1.9.2.

Code snippet that shows the difference:

#!/usr/bin/env ruby

puts case 1.0
when 1
  "yay"
else
  "nay"
end

First the output of irb when running 1.8.7:

$ rvm use ruby-1.8.7
Using /usr/local/rvm/gems/ruby-1.8.7-p334
$ ./case.rb 
yay

And the same in 1.9.2:

$ rvm use ruby-1.9.2
Using /usr/local/rvm/gems/ruby-1.9.2-p180
$ ./case.rb 
nay

Needless to say, I was puzzled by this result, but I was more surprised by the 1.8.7 behavior than 1.9.2. My assumption when I wrote the code was that I was dealing with integer values and since it worked, I forgot about it. Next time you see different behavior between 1.8.7 and 1.9.2 it might be worth reviewing case statements.

2 thoughts on “Case statement pitfall when migrating to Ruby 1.9.2

  1. The expected result here would be “yay” (and it works for me using 1.9.3). You might also want to note that (1.0 == 1), strangely, returns true. For a slightly stronger measure of “equivalence” use eql?, or stronger still is “equal?”, which verifies whether “two” objects are actually “one and the same”. For example, (1.0).eql?(1) returns false and “test”.equal?(“test”) is also false.

    In Ruby, the case statement uses triple equals (or “case equals”) === to evaluate the equalities. By default, === has the same implementation as ==. There are some classes (e.g., Range) that override this to provide convenient ways to use case statements.

  2. I see the same thing in 1.9.3, Justin, the result is the expected “yay”. In ruby 1.9.2-p290 I still get “nay”. So I checked a few more versions:

    1.9.2-p290: “nay”
    1.9.2-p320: “nay”
    1.9.3-p194: “yay”
    JRuby-1.6.7.2 (ruby-1.9.2-p312): “yay”
    Rubinius head (1.8.7): “yay”
    Rubinius head (1.9.3): “yay”

    So it seems like all patch levels of MRI 1.9.2 says “nay” while other rubies say “yay”. To further figure out what’s happening, I checked the various comparisons you mention and they act the same for 1.9.2 and 1.9.3. That is, (1.0 == 1) is true, (1.0 === 1) is true while both (1.0).eql?(1) and (1.0).equal?(1) are false.

    Adding a comment to the blog text to point out that the odd behavior seems to be restricted to 1.9.2. Thanks for pointing this out.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.