KARASZI István
KARASZI István

Reputation: 31477

Why does it call the "wrong" equal method?

Last week I wanted to answer a question here on stackoverflow.com, but after running some tests in irb I've found an interesting thing.

class X
  def ==(other)
    p "X#=="
    super
  end
end

data = [ 1 ]
data.include?(X.new)

I expect here that Array#include? will call Fixnum#== on every item in the array. So the X#== is never get called and the debug message never gets printed.

But actually in my ruby versions (REE 1.8.7, MRI 1.8.7, 1.9.2 and 1.9.3) it prints out the X#== debug message.

If I do that on true or false or nil or even Object.new it never prints out the X#== message.

But if I redefine the Fixnum#== like this:

class Fixnum
  def ==(other)
    p "Fixnum#=="
    super
  end
end

Which actually calls the original implementation after printing a debug message, it prints out Fixnum#== and X#== never gets printed out as I expected originally.

Update

It gets even crazier, when I switch the haystack with the needle:

data = [ X.new ]
data.include?(1)

It prints out X#== even though it called the #== method on the needle before.

Could anybody point out what is the reason behind that? Or simply an optimization issue?

Upvotes: 4

Views: 182

Answers (1)

Marc-André Lafortune
Marc-André Lafortune

Reputation: 79612

So include? will send :== to each element of your array.

If your elements are true, false, and nil, the equality test fails readily because only true is == to true, etc...

For Fixnums, it's not that clear, for example 1 == 1.0 # => true. So Fixnum#== will be polite in case of an unknown argument and reverse the order of the arguments. This would allow you to define your own "numerical" types.

Now what confused you even more, is that in order to understand what's going on, you redefined Fixnum#==. Calling super won't call the original method but Object#== instead. Try alias_method_chain (or prepend if in Ruby 2.0!)

BTW, looking at the actual source, Fixnum will deal directly with Fixnum, Bignum and Float. For other builtin classes (e.g. Rational, Complex, BigDecimal) as well as user classes, Fixnum#== will reverse the receiver and the argument. I wouldn't rely on the fact that it does this for Rational, but all implementations will do this for user classes.

Upvotes: 4

Related Questions