主模块中名为`hash`的方法会覆盖一些对象的`hash`方法

时间:2017-12-14 18:48:03

标签: ruby

鉴于此脚本

def hash
  puts "why?"
end

x = {}
x[[1,2]] = 42

输出以下内容

why?
/tmp/a.rb:6:in `hash': no implicit conversion of nil into Integer (TypeError)
    from /tmp/a.rb:6:in `<main>'

在这种情况下,脚本中定义的hash函数似乎覆盖了Array#hash。由于我的hash方法的返回值为nil而不是Integer,因此会抛出异常。以下脚本似乎确认了这个

puts [1,2,3].hash

def hash
  puts "why?"
end

puts [1,2,3].hash

输出

-4165381473644269435
why?
/tmp/b.rb:6:in `hash': no implicit conversion of nil into Integer (TypeError)
    from /tmp/b.rb:6:in `<main>'

我试着查看Ruby源代码,但无法弄清楚为什么会这样。这种行为是否记录在案?

2 个答案:

答案 0 :(得分:4)

您没有覆盖Array#hash,而是通过创建Kernel#hash来隐藏Object#hash

puts method(:hash)
def hash   
  puts "why?"
end
puts method(:hash)

打印:

#<Method: Object(Kernel)#hash>
#<Method: Object#hash>

修复它以便我们可以看到更多:

def hash
  puts "why?"
  super
end

x = {}
x[[1,2]] = 42

现在输出是:

why?
why?

没有错误。尝试使用x[[1,2,3,4,5,6,7]] = 42,然后您会看到why?打印次。对于每个数组元素,因为数组的哈希方法使用其元素的哈希值。并且Integer#hash不存在,它会从hash / Object继承其Kernel方法,因此您的方法会被使用。

答案 1 :(得分:3)

这是由于Ruby顶级的一种黑客行为。你有没有想过它是如何工作的?

def foo
end
p self
foo

class Bar
  def test
    p self
    foo
  end
end

Bar.new.test # no error

两个完全不同的对象(mainBar)如何能够调用foo,就像私有方法调用一样?原因是因为......它是一个私有方法调用。

当您在Ruby脚本的顶级定义方法时,它会被包含在每个对象中(通过Object)。这就是为什么你可以调用顶级方法,就像它们是全局函数一样。

但为什么这只会打破hash而不是其他常用方法呢?例如,def to_s;end不会中断to_s。原因是因为hash是递归的:大多数*类实现最终会调用Object#hash来实现它们。通过重新定义基本情况,您可以全局分解它。对于像to_s这样的其他方法,您将看不到全局更改,因为它在继承链上方并且不会被调用。

*唯一没有破坏的对象是一些可能具有硬编码哈希值的文字,例如: [] {} "" true等。