在Ruby中,为什么与nil相等的操作(“Date.new == nil”)会返回nil?

17

今天我在写一些rspec时,遇到了比较Date(和Time)实例与nil的一些意外行为。这里是一个使用原生Ruby(没有Rails或其他库)的示例:

user@MacBook-Work ~ $ ruby -v
ruby 1.8.7 (2008-08-11 patchlevel 72) [universal-darwin10.0]
user@MacBook-Work ~ $ irb
>> 1 == nil
=> false
>> "string" == nil
=> false
>> :sym == nil
=> false
>> false == nil
=> false
>> [] == nil
=> false
>> {} == nil
=> false
>> Proc.new {} == nil
=> false

目前为止,一切都还不错,对吧?

>> Date.new == nil
=> nil
>> Time.new == nil
=> nil

Date对象实现了自己的 === 操作符,并且运行良好:

>> Date.new === nil
=> false

有没有解释为什么会发生这种情况,或者为什么这是期望的行为?== 似乎是从 Comparable 实现的。然而,关于此文档没有提供任何指示它会返回 nil。这方面的设计决策是什么?

更新!在1.9.2中不是这种情况:

$ irb
ruby-1.9.2-p136 :001 > require 'date'
 => true 
ruby-1.9.2-p136 :002 > Date.new == nil
 => false 
ruby-1.9.2-p136 :003 > Time.new == nil
 => false 
4个回答

12
我查看了源代码,得出以下结论:
Comparable 定义的比较运算符都使用函数 rb_cmpint 以及 <=>。当操作数之一是 nil 时,rb_cmpint 函数会引发异常。
因此,如果 rhs 无法与 lhs 进行比较,则 Comparable 的运算符会引发异常。例如,5 < 2 返回 false,但 5 < "la" 则会引发异常。这样做是为了区分不真实的情况,其中 < 不成立是因为 rhs 更小,以及不真实的情况,其中 < 不成立是因为 rhs 无法进行比较。或者换句话说:当 x < y 为 false 时,这意味着 x >= y 为 true。因此,在这些情况下,它会抛出异常。
如果 == 引发异常,则会产生问题,因为 == 通常不需要(也不应该)要求其操作数可以比较。然而,== 使用与其他操作数相同的方法,该方法确实会引发异常。因此,整个函数只是包含在 rb_rescue 中。如果引发异常,则会返回 nil。
请注意,这仅适用于 Ruby 1.8。在 1.9 中已经修复了这个问题,现在 == 永远不会返回 nil(除非您定义自己的 == 函数)。

1
顺便说一下,1.8.7(可能还有更早的版本)在您提供的比较示例中引发了ArgumentError:
5 < "la" ArgumentError: comparison of Fixnum with String failed from (irb):41:in `<' from (irb):41
不过,您的解释确实有道理,似乎Date#<=>从未更新(故意吗?谁知道!)以引发而不是返回nil。我想现在我的主要问题是Ruby文档(通常太简洁),没有提到这种行为:
  • http://ruby-doc.org/core/classes/Date.html#M000673
  • http://ruby-doc.org/core/classes/Comparable.html(页面上没有提到“nil”)
- Gabe Martin-Dempesy
我来这里是因为我正在通过Ruby Koans的学习,其中包括一个问题:“使用obj.nil?还是obj == nil #为什么更好?”我希望有一个比“nil?更好,因为它不那么容易出错”的更好的答案。 - Isaac Rabinovitch

7
如果您需要检查代码是否为空,您可以使用任何Ruby对象都会响应的.nil?方法。
>> Date.new.nil?
=> false

1
就我个人而言,在阅读了这篇文章之后,我再也不会使用“== nil”了! - Shadowfirebird

4

Date类包含了Comparable#==方法,但该方法会调用接收者的<=>方法。在这种情况下,接收者是Date#<=>,它期望另一个Date对象作为参数。当它接收到nil时,它会返回nil。这种行为似乎不一致,我不知道背后的原因。


0

这种情况发生是因为你无法比较未定义的东西。这是值得期望的,因为如果你的操作数中至少有一个未定义,则不能得出任何关于结果的结论,这与断言真实不同。

许多语言将 nil 和 false 视为相同的,这只是出于方便考虑而已,这显然不是数学上正确的。


1
如果这是原因,那么仅适用于与nil进行比较。但事实并非如此。当您将可比较的对象与不同类型的任何内容进行比较时,会得到nil,因此例如Date.new == [1,2,3]也返回nil(除了在1.9中,==永远不会返回nil)。 - sepp2k
好的观点。那么,“发生这种情况是因为您无法比较两个不同类型的对象,其中一个子集是与nil进行比较。” :) OP问为什么不能与nil进行比较,我认为我的答案在这种情况下是正确的;但是,这并不是全部原因。 - Duncan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接