Friday, August 15, 2008

Regarding Ruby, instance_of?, kind_of?, and ===

So I was reading through some Ruby source code tonight -- primarily because I haven't in a while, thanks to a busy stretch of Java work -- and I ran across an idiom that I found a little confusing at first. The Ruby source code belongs to Why the Lucky Stiff's Shoes project, "a very informal graphics and windowing toolkit" (according to the official website).

One of the first files I looked at, lib/shoes.rb, opens right up with this interesting bit of interestingness:
class Range 
def rand
conv = (Integer === self.end && Integer === self.begin
? :to_i
: :to_f)
((Kernel.rand * (self.end - self.begin))
+ self.begin).send(conv)
end
end
Okay. So we're defining a rand() method on the built-in Ruby class Range, which will return a random value from within the begin and end values of the Range. Neato. And apparently the use of conv is meant to produce a result of a float or an integer, depending on the nature of the endpoints of the Range. Again: neato. But I hadn't seen the use of the === operator in this context before.

The docs and the Pickaxe book are a little obtuse on this, so I did some irb spelunking:
>> r = (1..27)
=> 1..27
>> r.class
=> Range
>> r.begin
=> 1
>> r.begin.instance_of? Integer
=> false
>> r.begin.class
=> Fixnum
>> r.begin.kind_of? Fixnum
=> true
>> r.begin.kind_of? Integer
=> true
Hmm. So instance_of() doesn't mean quite the same in Ruby as, say, the instanceof operator in Java does. (That, or Fixnum doesn't truly inherit from Integer in Ruby.)

Also, as it turns out:
>> Fixnum === r.begin
=> true
>> Integer === r.begin
=> true
So it would seem that the [class] === [value] syntax is syntactic sugar for [value].kind_of? [class]

It's the bit about:
>> r.begin.instance_of? Integer
=> false
that surprised me most, being the pathetic Java programmer that I am. And being that (according to the documentation for Integer on ruby-doc.org):
Integer is the basis for the two concrete classes that hold 
whole numbers, Bignum and Fixnum.
Apparently "is the basis for" != "is a superclass of".

Or is that "!==="...?

7 comments:

Avik Das said...

instance_of? doesn't return true for superclasses, but kind_of? does. They're different methods.

=== is also a method, and for Classes, and it's basically an alias for is_a?, which is an alias for kind_of?.

Avik Das said...

Sorry for the double post, but I forgot to mention something: === is used by the case expression no matter what the objects. In the case of a Class, like Fixnum, it's useful as an alias to kind_of? so you can do the following:

case obj # test obj's type
when Fixnum then treat_as_number(obj)
when String then treat_as_string(obj)
end

Unknown said...

=== is actually pretty powerful and woefully underused. I'm actually writing some posts about using === in Ruby. Here's the first one: Unlocking The Power Of Case Equality: Proc#===

David Rupp said...

@avik das (first comment): Right. So it's not that I expected those two methods to do the same thing. I knew about instance_of? and expected it to do the same thing as Java's instanceof. I didn't know about kind_of? at all.

My fault, I know, for thinking like a Java programmer. This is exactly why I'm making myself read more idiomatic Ruby.

Avik Das said...

@david

Actually, it wasn't until reading your post that I thought about those methods and their differences. For all my Ruby programming days, I've been using is_a?.

But it's the case statement that's an especially powerful use of ===.

David Rupp said...

Yeah -- according to the docs is_a? is an alias for kind_of? (or the other way around, I guess). I'd forgotten about that one too.

David Rupp said...

@farrel: Interesting stuff. I'll look forward to the other installments in the series.