2010-02-08 20 views
16

Al escribir algunos rspec hoy, me encontré con un comportamiento inesperado al comparar las instancias de fecha (y tiempo) en nil. He aquí una muestra usando rubí en bruto (sin rieles u otras bibliotecas):En Ruby, ¿por qué una igualdad con nil ("Date.new == nil") devuelve nil?

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

Hasta ahora, todo va bien, ¿verdad?

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

Fecha hace poner en práctica su propia ===, que funciona bien:

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

¿Hay alguna explicación de por qué sucede esto o por qué se desea esto comportamiento? == parece implementarse desde Comparable. ==, sin embargo, la documentación sobre eso no da ninguna indicación de que alguna vez devolvería nulo. ¿Cuál es la decisión de diseño para esto?

¡Actualización! Este no es el caso en 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 

Respuesta

12

he comprobado el origen y esto es lo que descubrí:

Los operadores de comparación definidos por Comparable todo el uso de la función junto con rb_cmpint<=>. rb_cmpint genera una excepción cuando uno de los operandos es nulo.

Por lo que los operadores de Comparable plantean una excepción si el rhs no es comparable al lhs. Es decir. 5 < 2 es falso, pero 5 < "la" genera una excepción. Lo hacen para diferenciar entre los casos donde < no es verdadero porque el rhs es más pequeño y los casos en los que no es verdadero porque el rhs no es comparable. O en otras palabras: cuando x < y es falso, implica que x >= y es verdadero. Entonces, en los casos en que ese no sería el caso, arroja una excepción.

== levantar una excepción sería malo, porque == generalmente no requiere (y no debería) que sus operandos sean comparables. Sin embargo, == usa el mismo método que los otros operandos, lo que genera una excepción. Entonces, toda la función simplemente está envuelta en un rb_rescue. Y eso devuelve nil si se lanza una excepción.

Tenga en cuenta que esto solo se aplica a ruby ​​1.8. Esto se ha corregido en 1.9 y ahora == nunca devuelve nil (excepto, por supuesto, si define su propio == que sí lo hace).

+1

Fwiw, 1.8.7 (y eventualmente de antes) plantea ArgumentError en el ejemplo de comparación que diste: >> 5 < "la" ArgumentError: comparación de Fixnum con la cadena no \t a partir de (IRB): 41: en '<' \t desde (irb): 41 Sin embargo, su explicación sí tiene sentido, y parece que la Fecha # <=> nunca se actualizó (¿a propósito ?, quién sabe!) Para aumentar en lugar de devolver nada. Supongo que mi carne de res principal ahora es con los (muy generalmente) escuetos documentos de Ruby que no mencionan este comportamiento: * http://ruby-doc.org/core/classes/Date.html#M000673 * http://ruby-doc.org/core/classes/Comparable.html (no se menciona "nil" en la página) –

+0

Vine aquí porque estoy trabajando en [Ruby Koans] (http: // rubykoans. com /), que incluyen la pregunta "¿Es mejor usar obj.nil? u obj == nil # ¿Por qué?" Espero que haya una respuesta mejor que "nil? Is better porque es menos problemático". –

4

La clase Date incluye el método Comparable#==, pero ese método invoca el método del receptor <=>. En este caso, es Date#<=>, que espera otro objeto Date. Cuando recibe nil, devuelve nil. Este comportamiento ciertamente parece inconsistente, y no sé las razones detrás de esto.

7

Si depende de esto para el código, ¿siempre puede usar el .nil? método al que responde cualquier objeto Ruby.

>> Date.new.nil? 
=> false 
+1

Personalmente, al leer esto, ¡nunca volveré a usar '== nil'! – Shadowfirebird

0

Sucede porque no puede comparar cosas que no están definidas. Es deseable porque si al menos uno de tus operandos no está definido, entonces no puedes sacar ninguna conclusión sobre el resultado, que es diferente de afirmar la verdad.

Muchos idiomas tratan igual y falso, lo que es sospechoso es puramente por conveniencia. Ciertamente no es matemáticamente correcto.

+1

Si este fuera el motivo, entonces esto solo se aplicaría al comparar con nil. Ese no es el caso. Obtienes nulo cuando comparas un objeto comparable con cualquier cosa que no sea del mismo tipo, así que por ejemplo Date.new == [1,2,3] también devuelve nil (excepto en 1.9 donde == nunca devuelve nada) . – sepp2k

+0

Buen punto. ¿Qué tal "sucede porque no se pueden comparar dos objetos de diferente tipo, un subconjunto de esto es una comparación con cero". :) El OP preguntó por qué no se puede comparar con cero, y creo que mi respuesta es correcta en ese contexto; sin embargo, no es la razón completa. – Duncan