views:

253

answers:

4

When writing some rspec today, I came across some unexpected behavior with comparing Date (and Time) instances to nil. Here's a sample using raw ruby (no Rails or other libraries):

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

So far, so good, right?

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

Date does implement its own ===, which works fine:

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

Is there any explanation as to why this happens or why this is desired behavior? == seems to be implemented from Comparable.==, however documentation on that doesn't given any indication that it would ever return nil. What's the design decision to this?

+4  A: 

The Date class includes the Comparable#== method, but that method invokes the <=> method of the receiver. In this case that's Date#<=>, which expects another Date object. When it receives nil it returns nil. This behavior certainly seems inconsistent, and I don't know the reasons behind it.

Alex Reisner
+7  A: 

Edit: After you mentioned that x actually raises an exception, I checked the source and here's what I found out:

The comparison operators defined by Comparable all use the function rb_cmpint together with <=>. rb_cmpint raises an exception when one of the operands is nil.

So the operators of Comparable raise an exception if the rhs is not comparable to the lhs. I.e. 5 < 2 is false, but 5 < "la" raises an exception. They do this to differentiate between cases where < is not true because the rhs is smaller and cases where it's not true because the rhs is not comparable. Or in other words: When x < y is false that implies that x >= y is true. So in cases where that would not be the case, it throws an exception.

However == raising an exception would be bad, because == usually does not (and should not) require its operands to be comparable. However == uses the same method as the other operands, which does raise an exception. So the whole function is simply wrapped in an rb_rescue. And that returns nil if an exception is thrown.

sepp2k
FWIW, 1.8.7 (and possibily earlier) raises ArgumentError in the comparison example you gave: >> 5 < "la" ArgumentError: comparison of Fixnum with String failed from (irb):41:in `<' from (irb):41However, your explanation does make sense, and it seems Date#<=> was never updated (on purpose? who knows!) to raise instead of return nil.I guess my main beef now is with the (all too typically) terse Ruby docs which don't mention this behavior:* http://ruby-doc.org/core/classes/Date.html#M000673* http://ruby-doc.org/core/classes/Comparable.html (no mention of 'nil' on the page)
Gabe Martin-Dempesy
+5  A: 

If you're depending on this for code, you can always use the .nil? method which any Ruby Object responds to.

>> Date.new.nil?
=> false
Beerlington
Personally, on reading this, I'm never going to use '== nil' again!
Shadowfirebird
+1  A: 

It happens because you can't compare things that are not defined. It's desirable because if at least one of your operands is not defined then you can't draw any conclusion about the result, which is different from asserting truth.

A lot of languages treat nil and false the same, which is suspect is purely for convenience. It's certainly not mathematically correct.

Duncan