martes, 19 de febrero de 2013

Compare, compare (2 de 2)

Probando la clase Nocase

¡Eso! Ya la puedes probar con irb:

irb(main):001:0> require './nocase.rb'
=> true
irb(main):002:0> a = Nocase.new('abcde')
=> "abcde"
irb(main):003:0> 'abcde' <= 'ABCDE' # En String, las minúsculas tienen valor superior a las minúsculas
=> false
irb(main):004:0>
irb(main):005:0* a <= 'ABCDE'  # En Nocase, no
=> true
irb(main):006:0> a < 'AC'
=> true
irb(main):007:0> a == 'aBcDe'
=> true
irb(main):008:0>


Cosa curiosa


Cuando Ruby compara una String con un Nocase... ¿cómo decide si utiliza el método de la clase String o el de Nocase?

La regla a seguir es: se usa el método de la clase a la que pertenezca el primero de los operandos. Eso hace que a veces se produzcan situaciones extrañas. Sigamos probando cosas:

irb(main):009:0>  b = Nocase.new('a')
=> "a"
irb(main):010:0> 'A' == b
=> false
irb(main):011:0> b == 'A'
=> true


Una comparación puede ser cierta o falsa dependiendo de cómo se haga. 'A' no es igual que el valor de a y, sin embargo, el valor de a es igual que 'A'...

La explicación: en el primer caso ('A' == b), como 'A' es una String, se usa la comparación de esta clase, que distingue entre mayúsculas y minúsculas. Por su parte, en el segundo (b == 'A'), como la variable “a” referencia un objeto Nocase, se usa la comparación de Nocase, que no entiende de tamaños.

A mi, tal y como suelo hacer las comparaciones, esto no me supone un problema. Pero quizá a tí sí...

¿Tiene solución?

Sí, sí que la tiene.

El problema se produce cuando el primer operando es una String y, por tanto, se usa el método de esta clase. Por lo tanto, únicamente hace falta redefinir los métodos de la clase String.

¿Únicamente? ¿Puedo redefinir los métodos existentes de clases existentes?+

Sí. Sin ningún problema. Basta con volver a abrir la clase String y añadirle el código que queramos. Y es que en Ruby siempre vamos a poder modificar las cosas de las clases que tenemos.

En la misma carpeta donde pusiste “nocase.rb”, crea un fichero llamado “prueba.rb” con el siguiente contenido:

require './nocase.rb'
class String
private
 # Garantizando que se podrán referenciar los métodos originales

 # dentro de los nuevos
 alias_method :tmp_method_for_equal, :==


public 
 # Redefinición de ==
 # Si x es de clase Nocase, se realiza la comparación utilizando el

 # método de Nocase.
 # En otro caso, se usa el método original de la clase String
 def ==(x)
  if x.class.name.tmp_method_for_equal('Nocase')
   return Nocase.new(self) == x
  else
   return tmp_method_for_equal(x)
  end
 end
end


En “prueba.rb” se redefine el método “==” de la clase String de forma que, si el valor con el que se compara la cadena es de la clase Nocase, se utilice el método de Nocase mientras que para el resto de casos se use el método original de la clase String.

Pero ahora nos surge una nueva duda: Al redefinir  “==” perderemos el comportamiento original de este método y no podremos utilizarlo.

Una solución consistiría en hacerle un alias antes de la redefinición y utilizar posteriormente este alias. Lo cual resuelve el problema pero a costa de añadir un nuevo método, el alias, a la clase String.

Si este método no es necesario para el uso de la clase... cuanto menos se puede decir que esto queda feo. Menos mal que Ruby tiene formas de que las cosas no se vean. Observa que al principio de nuestra redefinición de String aparece la palabra “private”. Eso quiere decir que los métodos que se especifican a continuación no van a estar accesibles para los programas ni para otros objetos. Son  como cosas “secretas” que cada objeto de nuestra clase guarda para sí. Ni siquiera los pone a disposición de otros objetos de la misma clase.

Rincones bajo las alfombras donde se supone que nadie mira.

Más adelante, tras la definición del alias, nos encontramos con la palabra “public”. Lo que le sigue ya sí es otra vez “público”. O sea, visible para el resto de objetos y programas. De modo que éste es el sitio donde declarar los métodos que queramos que se puedan invocar desde fuera de nuestra clase.

Y lo que econtramos ahí es la redefinición de “==”. Algo que hay que hacer con cuidado de evitar comparar cadenas utilizando “==” o crearíamos una cadena de llamadas recursivas que acabaría con el espacio disponible para la pila y, de camino, con el funcionamiento normal del programa. En lugar de “==”, se utiliza el alias creado.

Y ya podemos probar
irb(main):001:0> require './prueba.rb'
=> true
irb(main):002:0> a = Nocase.new('abcde')
=> "abcde"
irb(main):003:0> 'abcde' == 'ABCDE' # Con dos cadenas,distingue
=> false
irb(main):004:0> a == 'ABCDE'       # Con un Nocase y una cadena no
=> true
irb(main):005:0> 'ABCDE' == a       # Con una cadena y un  Nocase no
=> true


Ya sabes cómo se hace. Ahora si quieres redefinir los otros métodos... ¡es tu turno!

No hay comentarios:

Publicar un comentario