鉴于此脚本
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源代码,但无法弄清楚为什么会这样。这种行为是否记录在案?
答案 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
两个完全不同的对象(main
和Bar
)如何能够调用foo
,就像私有方法调用一样?原因是因为......它是一个私有方法调用。
当您在Ruby脚本的顶级定义方法时,它会被包含在每个对象中(通过Object
)。这就是为什么你可以调用顶级方法,就像它们是全局函数一样。
但为什么这只会打破hash
而不是其他常用方法呢?例如,def to_s;end
不会中断to_s
。原因是因为hash
是递归的:大多数*类实现最终会调用Object#hash
来实现它们。通过重新定义基本情况,您可以全局分解它。对于像to_s
这样的其他方法,您将看不到全局更改,因为它在继承链上方并且不会被调用。
*唯一没有破坏的对象是一些可能具有硬编码哈希值的文字,例如: []
{}
""
true
等。