2016-01-09 119 views
1

我承担了Ruby on Rails的源代码查看,发现这样的代码:`===`如何工作?

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

其中options可以是StringSymbolProc,或者Hash对象。该===比较只会在一种情况下返回true

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

如何红宝石case工作,让比赛具有这种不同的情况?

回答

4

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

不同类别对===的定义不同。表达x === y相同:

  • y == x(对于x:的String实例,SymbolHash等)
  • y =~ x(对于x:的Regexp实例)
  • y.kind_of?(x)(对于xClass

此外,您可能会混淆一个类和它的实例。 /regexp/ ===Regexp ===不一样。 "string" ===String ===等不一样

+1

操作数顺序也可能令人困惑。 'case obj;当模式; end'调用'pattern === obj',而不是'obj === pattern''。 – Stefan

3

这可能有助于理解:

"foo" === /foo/ 

实际上是另一种方式来写:

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

所以它实际上是一个方法调用实例方法String#==="foo"(这是一个实例String),将其作为参数传递给/foo/。因此将发生什么完全由String#===定义。

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

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

因此您case声明实际上是方法调用(按出场顺序):

  1. Regex#===
  2. Module#===
  3. Symbol#===
  4. ,然后再次Module#===

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

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

这上面呼唤String#===其中,根据文档:

返回是否海峡== OBJ ,类似于Object#==。

如果obj不是字符串的一个实例,但会响应to_str,则使用案例相等Object#===比较两个字符串。

否则,返回类似于#eql?,比较长度和内容。

这就是为什么它不匹配,因为它实际上调用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