En Ruby, ¿por qué una igualdad con nil("Date.new== nil") devuelve nil?
equality (4)
La clase Fecha 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 él.
Al escribir un poco de rspec hoy, encontré un comportamiento inesperado al comparar las instancias de Fecha (y Hora) con nada. Aquí hay una muestra usando ruby en bruto (sin Rails u otras bibliotecas):
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
¿Hasta aquí todo bien, no?
>> Date.new == nil
=> nil
>> Time.new == nil
=> nil
La fecha implementa su propio ===, que funciona bien:
>> Date.new === nil
=> false
¿Hay alguna explicación de por qué sucede esto o por qué se trata de un comportamiento deseado? == 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?
¡Actualizar! 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
Revisé la fuente y esto es lo que descubrí:
Los operadores de comparación definidos por Comparable utilizan la función rb_cmpint
junto con <=>
. rb_cmpint
genera una excepción cuando uno de los operandos es nulo.
Por lo tanto, los operadores de Comparable generan una excepción si el valor de rs no es comparable al de lhs. Es decir, 5 < 2
es falso, pero 5 < "la"
genera una excepción. Hacen esto para diferenciar entre los casos donde <
no es cierto porque el rhs es más pequeño y los casos donde no es cierto porque el rhs no es comparable. O en otras palabras: cuando x < y
es falso, eso implica que x >= y
es verdadero. Así que en los casos en que no sería el caso, se produce una excepción.
==
generar una excepción sería malo, porque ==
generalmente no (y no debería) requerir que sus operandos sean comparables. Sin embargo, ==
utiliza el mismo método que los otros operandos, lo que genera una excepción. Así que toda la función está simplemente 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).
Si depende de esto para el código, siempre puede usar .nil? Método al que responde cualquier objeto Ruby.
>> Date.new.nil?
=> false
Sucede porque no puedes comparar cosas que no están definidas. Es deseable porque si al menos uno de sus operandos no está definido, entonces no puede sacar ninguna conclusión sobre el resultado, que es diferente de afirmar la verdad.
Muchos lenguajes tratan lo nulo y lo falso de la misma manera, lo que se sospecha es puramente por conveniencia. Ciertamente no es matemáticamente correcto.