为什么称它为“错误的”平等方法?

时间:2013-04-13 03:46:32

标签: ruby equality

上周我想在stackoverflow.com上回答一个问题,但在 irb 中进行了一些测试后,我发现了一件有趣的事情。

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

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

我希望Array#include?会在数组中的每个项目上调用Fixnum#==。因此永远不会调用X#==并且永远不会打印调试消息。

但实际上在我的ruby版本(REE 1.8.7,MRI 1.8.7,1.9.2和1.9.3)中,它打印出X#==调试消息。

如果我在truefalsenil或甚至Object.new上执行此操作,则永远不会打印出X#==消息。

但如果我像这样重新定义Fixnum#==

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

在打印调试消息后实际上调用了原始实现,打印出Fixnum#==并且X#==永远不会按照我原先的预期打印出来。

更新

当我用针切换干草堆时,它变得更加疯狂了:

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

它打印出X#==,即使它之前在针上调用了#==方法。

有人能指出背后的原因是什么?或者只是一个优化问题?

1 个答案:

答案 0 :(得分:4)

因此include?会将:==发送到您阵列的每个元素。

如果您的元素是truefalsenil,则相等测试很容易失败,因为只有true ==true等等......

对于Fixnum,它不是那么清楚,例如1 == 1.0 # => true。因此Fixnum#==在未知参数的情况下将是礼貌的,并且反转参数的顺序。这将允许您定义自己的“数字”类型。

现在让你感到困惑的是,为了理解发生了什么,你重新定义 Fixnum#==。调用super不会调用原始方法,而是调用Object#==。试试alias_method_chain(或prepend如果在Ruby 2.0中!)

BTW,查看实际来源,Fixnum将直接处理FixnumBignumFloat。对于其他内置类(例如RationalComplexBigDecimal)以及用户类,Fixnum#==将反转接收器和参数。我不会依赖它为Rational执行此操作的事实,但所有实现都将为用户类执行此操作。