`===` 是如何工作的?

How does `===` work?

我查看了 Rails 上的 Ruby 源代码,发现代码如下:

case options
when /\A([a-z][a-z\d\-+\.]*:|\/\/).*/i
  ...
when String
  ...
when :back
  ...
when Proc
  ...
end

其中 options 可以是 StringSymbolProcHash 对象。 === 比较只会在一种情况下 return true

'string' === /string/ # => false
:back === :back # => true
(Proc.new {}) === Proc # => nil
Hash.new === Hash # => false

Ruby case 是如何工作的,允许匹配这些不同的情况?

这可能有助于理解:

"foo" === /foo/

其实是另一种写法:

"foo".===(/foo/)

所以它实际上是在"foo"(它是class String的一个实例)上调用实例方法String#===,传递给它/foo/ 作为参数。因此,将要发生的事情完全由 String#===.

定义

在你的代码中,实际发生的是这样的:

if /\A([a-z][a-z\d\-+\.]*:|\/\/).*/i === options
  # ...
elsif String === options
  # ...
elsif :back === options
  # ...
elsif Proc === options
  # ...
else
  # ...
end.delete("[=12=]\r\n")

因此,您的 case 语句实际上是对(按出现顺序)的方法调用:

  1. Regex#===
  2. Module#===
  3. Symbol#===
  4. 然后又是Module#===

关于你问题中的第二个例子:

'string' === /string/ # => false

以上是调用 String#===,根据文档:

Returns whether str == obj, similar to Object#==.

If obj is not an instance of String but responds to to_str, then the two strings are compared using case equality Object#===.

Otherwise, returns similarly to #eql?, comparing length and content.

这就是它不匹配的原因,因为它实际上调用了Object#==

同样:

# this calls `Proc#===`, not `Module#===`
(Proc.new {}) === Proc # => false

# however, if you want to test for class equality you should do:
Proc === (Proc.new {}) # => true, calls `Module#===`

Hash.new === Hash也是如此。

您(以及许多初学者)似乎做出的错误假设是 === 是对称的。实际上不是。 x === y 的工作方式不同,不取决于 y,而是取决于 x

不同的classes对===有不同的定义。表达式 x === y 等同于:

  • y == x(对于 xStringSymbolHash 等的实例)
  • y =~ x(对于 xRegexp 的实例)
  • y.kind_of?(x)(对于 xClass 的实例)

此外,您可能会混淆 class 及其实例。 /regexp/ ===Regexp === 不同。 "string" ===String ===不一样,等等