Peculiaridades de Ruby para desarrolladores Java (parte 2)
Continuando con la entrada anterior sigo explorando los aspectos de Ruby mas peculiares para un programador que viene del mundo Java.
Closures
En los artículos de autores relacionados con el mundo de los métodos ágiles es habitual oir hablar de lenguajes que soportan closures. Pues bien, tengo que reconocer que aunque creía haber entendido bien el concepto lo cierto es que al menos no lo había asimilado. Usandolo con Rails por fin lo he conseguido, así que ahí va mi intento de explicar lo que son:
Un closure puede verse como un bloque de código que se sitúa en paralelo al del método y al que éste puede pasarle el control cuando crea conveniente usando el método yield()
Un ejemplo típico de uso podría ser un método que itera por un árbol y realiza una acción con cada nodo. En Ruby es posible pasar al método (al que llamaremos iterateTree) un fragmento de código que será el que realice la operación sobre el nodo. Por ejemplo, la invocación del método para imprimir cada nodo podría ser:
tree.iterateTree do |node|
puts "Iterating node: " + node
end
El fragmento de código entre do y end recibe el nombre de closure. El método iterateTree invocará el closure usando el método especial yield:
def iterateTree
...
yield node
...
end
En Java puede lograrse la misma funcionalidad con una interfaz (p.e. NodeCommand) y una clase anónima:
class NodeCommand {
public void run(Node node);
}
public class MyClass {
...
tree.iterateTree(new NodeCommand() {
public void run(Node node) {
System.out.println("Iterating node: " + node);
... }
}
}
No parece que haya mucha diferencia ¿verdad? Pues en la práctica la diferencia se ha reflejado de forma muy significativa en las APIs que acompañan a Java y Ruby. Mientras que en el primero los métodos que siguen una estructura similar a la anterior son muy excasas en Ruby son muchas. Quizá haya aportado a esta enorme diferencia en la práctica el que las clases anónimas son unas desconocidas para muchos programadores Java y su sintaxis algo difícil de leer y en Ruby es una de las cosas que primero se aprenden y es fácil acostumbrarse a leerlas. Una vez más no hay que subestimar el efecto que puede tener una pequeña diferencia inicial en los resultados finales.
Operador ||= y otros similares
Este operador me traía de cabeza al principio así que solía ignorar las líneas en que se usaba. Sin embargo su significado es bien sencillo una vez se conocen dos aspectos de Ruby:
En primer lugar es necesario recordar que Ruby tiene sobrecarga de operadores y eso en la práctica significa que (casi) cualquier operador puede aplicarse a cualquier objeto. En Java el operador || sólo puede aplicarse a booleanos, pero en Ruby también se puede aplicar a objetos. Por ejemplo:
x = a||b
El código equivalente en Java sería:
x = a;
if (x == null) {
x = b;
}
El uso habitual de esta construcción es como una forma concisa de asignar un valor por defecto a una variable en caso de que el valor dinámico (la variable a) en el ejemplo venga vacía;
El segundo aspecto que hay que conocer es que al igual que Java permite asociar el operador de asignación (=) con los operadores de multiplicación, suma, etc, Ruby permite hacerlo con muchos más operadores. Así que igual que en Java escribimos:
a *= 2; //equivalente a: a = a * 2; b += 4; //equivalente a: b = b + 4;
En Ruby puede escribirse:
x ||= b #equivalente a: x = x || b
Que en Java se expresaría como:
if (x == null) {
x = b;
}
Expresiones regulares
Ruby soporta el uso de expresiones regulares en el código usando el operador /regexp/, así que es posible comprobar si una cadena de texto empieza por hola con:
if cadena_texto =~ /^hola/
do_it
end
Listas de cadenas de texto
numbers = %w(one two three four)
Que es equivalente a:
numbers = ['one', 'two', 'three', 'four']
Referencia al fichero actual
En cualquier punto del código es posible acceder a la variable especial __FILE__ que representa el fichero actual.
Un uso de esta variable que me he encontrado con asiduidad es:
require File.dirname(__FILE__) + '/../fichero_Ruby'
Esta parece ser una forma de saltarse el orden de load path (similar al CLASSPATH de Java). La línea anterior construye una ruta absoluta al fichero que se desea incluir a partir de la ruta absoluta del fichero actual.
Uso de exclamaciones o interrogaciones en el nombre de métodos
En Ruby está permitido el uso de los caracteres ! y ? en los nombres de los métodos. De hecho se suelen usar según la siguiente convención:
- Los métodos que acaban con el símbolo ! hacen cosas peligrosas
- Los métodos que acaban con el símbolo ? devuelven un boolean
En particular el uso del símbolo interrogación extraña al programador Java acostumbrado a su uso como operador. En Ruby no es más que parte del nombre del método.
Ruby por todos lados
Cuando se desarolla en Java es habitual extraer las partes del programa que cambian con más frecuencia a ficheros de configuración, normalmente ficheros de propiedades o XML. Cuando se usa J2EE y frameworks sobre el mismo (Struts, hibernate, Spring, etc) el número de ficheros de configuración se multiplica. También es habitual encontrarse con ciertas construcciones específicas (adicionales al lenguaje original) para determinadas tareas, como puedan ser las taglibs.
En Ruby la filosofía es diferente y Rails la lleva al extremo. Por un lado sustituye configuración por convenciones en la mayoría de los casos (aunque luego se pueden cambiar muchas de las convenciones usando Ruby) y por otro hay una clara tendencia a hacer absolutamente todo en Ruby. El ejemplo de las tablibs es muy claro: en lugar de inventar una construcción nueva como hace J2EE simplemente se usan llamadas a métodos.
Al principio me llamó la atención y me pareció feo y no me gustaba ver código en los ficheros HTML. Pero la verdad es que la gran facilidad y rapidez con la que se crean estos métodos es superior al de las taglibs y sobretodo la curva de aprendizaje es mucho menos, dado que todo el mundo sabe cómo funcionan los métodos.
Conclusiones: diferencias con Java
Un aspecto fundamental del lenguage Java es la consistencia de su sintaxis. Este es un aspecto que los programadores aprendemos a valorar ya sea consciente o inconscientemente (lo más común es que sea inconsciente). De esa forma es fácil saber qué és lo que está ocurriendo: se está invocando a un método con 3 parámetros, de los cuales uno es un array, etc.
Debido a ello al empezar a programar en Ruby uno de los aspectos que más me fastidiaba es que a veces no se usaban paréntesis o llaves y se hacía muy difícil saber qué es lo que está ocurriendo en el sentido anterior. Pero poco a poco voy entendiendo (y apreciando) que Ruby pretende quitar la atención en la sintaxis para enfocarse en la semántica. Por ejemplo cuando se invoca:
redirect_to :action => 'show', :id => product.id
¿Realmente importa si es un método o no o cuantos parámetros hay? Lo cierto es que se entiende lo que hace ¿verdad? Esta característica de Ruby es la que le convierte en un lenguaje muy apropiado para construir lenguajes específicos de dominio, que és uno de los aspectos que más me atrae de él.
Por lo demás, destacaría la forma en que Ruby consigue que el número de líneas para hacer casi cualquier cosa sea siempre muy reducido y a la vez no se pierde la legibilidad.
No quiero decir con esto que Ruby me parezca mejor que Java pero mi impresión hasta ahora es que es un buen lenguaje y merece la consideración que se le está dando.


Mas cosas sobre los closures
Un comentario más acerca de los closures (inner classes en Java), y es que buena parte de su potencia radica en que es accesible dentro del bloque de código el contexto en el que son creados (de ahí el término
closure). Utilizando el ejemplo Java que has puesto, sería:public class MyClass {
private String a;
...
public method(final String b) {
final String c;
tree.iterateTree(new NodeCommand() {
public void run(Node node) {
System.out.println("a: " + a +", b" + b + ", c" + c);
... }
}
}
}
Perdón por la falta de indentación. Java obliga a declarar las variables dentro del método que van a ser accedidas desde el bloque como
final, una restrición más en la sintaxis que penaliza su usoClosures y Lisp
Por lo que llevo leído de Ruby, parece que se parece bastante a Lisp, no en su sintaxis, por supuesto, pero sí en el tratamiento del lenguaje, la memoria y los símbolos.
Ya en el comentario anterior, veíamos que todo son símbolos, al igual que en Lisp. En Lisp pueden obtener la variable asociada a un símbolo cualquiera. Por ejemplo el símbolo "hola". Lo mejor de todo es que podrías obtener la variable asociada a una cadena de caracteres introducida por el usuario. Esto es porque Lisp es altamente dinámico, en contraposición a los lenguajes estilo C, C++, Java.
En cuanto a los Closures, adivino que otra de las potencias de Ruby es que puedes crear funciones "sobre la marcha", lo que hace que la programación orientada a "closures" o cierres sea lo habitual en Lisp, y así parece en Ruby, pero algo artificiosa en lenguajes estáticos como Java.
Sobre la inclusión de código de propósito general en las pág
He de agradecer al autor el tratamiento comparativo que está realizando entre Ruby y Java, aunque discrepo sobre la opinión de que embeber código de propósito general en las páginas de presentación sea una buena idea, por ventajoso que parezca el no tener que pasar por el proceso de aprendizaje de los taglibs
A mí entender es un paso atrás en la evolución de las tecnologías web. Sería equivalente a volver a los servlets, muy cercanos a los CGIs, y además de su uso se siguen almenos los siguientes problemas: