Uno de los mecanismos más potentes provistos por el lenguaje Ruby es la posibilidad de manipular bloques de código que pueden ser pasados como argumentos en la invocación a funciones.
Este mecanismo se denomina “clausura” (“closure“, en inglés), y aunque no es exclusivo de Ruby (es un viejo conocido en Smalltalk y Lisp, encontrándose aún en Perl y Python), la simplicidad de su uso lo transforma en una herramienta muy poderosa y flexible.
Quienes hemos programado en lenguajes funcionales (como ML y Haskell), siempre hemos extrañado en los lenguajes imperativos la posibilidad de pasar funciones como parámetros de forma “natural” (no a la manera en que puede hacerse en Java o Pascal, sino incluyendo las ligaduras al scope actual).
Un ejemplo simple
Supongamos que deseamos escribir una función que tome como parámetro un arreglo de valores enteros y retorne otro arreglo, producto de multiplicar por 2 cada uno de los elementos del parámetro.
En Java podríamos escribir algo así:
public static ArrayList double(ArrayList x)
{
ArrayList y = new ArrayList();
for (Iterator i = x.iterator(); i.hasNext(); )
y.add(i.next() * 2);
return y;
}
(Obviamente, olvidándonos de algunos casts para simplificar la lectura.)
O, en PHP:
function double($x) {
$y = array();
foreach ($x as $e)
array_push($y, $e*2);
return $y;
}
En tanto que en un lenguaje funcional, como Haskell podríamos escribir lo siguiente:
double x = map (*2) x
O, simplemente:
double = map (*2)
“map” toma como parámetro una función (en este caso, la función parcial “*2“) y un arreglo, devolviendo el resultado de aplicar dicha función a cada elemento del arreglo.
Veamos un ejemplo de la evaluación de double:
double [1, 2, 3]
[1*2, 2*2, 3*2]
[2, 4, 6]
Como podemos ver, la principal ventaja de los lenguajes funcionales es su expresividad, en el sentido de que el código del programa expresa de forma mucho más clara qué es lo que este hace (multiplicar por 2 cada elemento del arreglo).
En Ruby, aunque no es un lenguaje funcional, la posibilidad de pasar funciones (o, en general, bloques de código) como parámetro se presenta de forma igualmente sencilla:
def double(a)
a.map {|e| e*2}
end
“map” es un método de la clase “Array” que toma un bloque de código y lo ejecuta para cada elemento del arreglo, devolviendo la colección de los resultados producidos. El fragmento “|e|” establece la ligadura entre el identificador “e” y el elemento actual.
Explotando la potencia de las clausuras
Supongamos ahora que tenemos una clase “Person“, cuyo método “age” calcula la edad de la persona. Si tenemos una colección de personas en el arreglo “people” y deseamos obtener sólo aquellas que son mayores de 18 años, en Ruby podríamos escribir lo siguiente:
people.select{|p| p.age > 18}
(El método “select” aplica una función lógica (booleana) a cada elemento y devuelve sólo aquellos para los cuales resultó verdadera.)
En tanto que si, además, quisiéramos mostrar el nombre de cada una de ellas, podríamos escribir:
people.select{|p| p.age > 18}.each{|p| puts p.name}
(El método “each” aplica el bloque a cada elemento del arreglo.)
O bien, de forma más compacta (sin generar un segundo arreglo):
people.each{|p| puts p.name if p.age > 18}
Comparación con otros lenguajes
Problema 1
Convertir las cadenas de un arreglo a mayúsculas.
En PHP:
for ($i = 0; $i < count($a); $i++)
$a[$i] = strtoupper($a[$i]);
En Ruby:
a.map!{|e| e.upcase}
(El método “map!“, a diferencia de “map“, modifica el arreglo, en vez de retornar uno nuevo.)
Problema 2
Dado un arreglo de la forma [["nombre", "apellido"]], obtener un arreglo de la forma ["apellido, nombre"]. Por ejemplo, dado:
[["Javier","Smaldone"],["Santiago","Lobos"],["Bon","Scott"]]
obtener:
["Smaldone, Javier", "Lobos, Santiago", "Scott, Bon"]
En PHP:
$b = array();
foreach ($a as $e)
array_push($b, $e[1] . ', ' . $e[0]);
En Ruby:
b = a.map{|e| e.reverse.join(', ')}
Problema 3
Calcular la edad promedio de las personas mayores de edad (según el ejemplo planteado anteriormente).
En PHP:
$sum = 0;
$count = 0;
foreach ($people as $p) {
if ($p->age > 18) {
$sum += $p->age;
$count++;
}
}
if ($count) print $sum/$count;
Una solución posible en Ruby sería:
adults = people.select{|p| p.age > 18}
sum = 0
adults.each {|p| sum += p.age }
puts sum.to_f/adults.size if adults.size > 0
Pero usando todo el poder de las clausuras y las funciones “map” e “inject“, podríamos escribir:
adults = people.select{|p| p.age > 18}
sum = adults.map{|p| p.age}.inject{|ac,e| ac+e}
puts sum.to_f/adults.size if sum
( “inject” ejecuta el bloque, asignando la segunda variable a cada elemento del arreglo y asignando el resultado de la ejecución a la primera, manteniendo su valor a través de cada iteración.)
Problema 4
Dado un arreglo de cadenas, obtener la de mayor longitud.
En PHP:
$max = '';
foreach ($a as $e)
if (strlen($e) > strlen($max))
$max = $e;
En Ruby:
max = a.inject {|m, e| e.length > m.length ? e : m }
(Adicionalmente, hemos usado el operador de asignación condicional “cond ? val1 : val2“, que retorna “val1” si “cond” es verdadero y “val2” en caso contrario.)
Conclusión
La posibilidad de utilizar clausuras nos acerca a la simplicidad y la expresividad de la programación funcional. Este es uno de los puntos fuertes del lenguaje Ruby (y la envidia de otros lenguajes, como Java, cuya comunidad está debatiendo sobre la posibilidad de incorporarlo en la versión 7).
(Y sí, se lo que pensará mi amigo Ricardo cuando lea este artículo. Es cierto, los lenguajes tienden a Lisp.)
Algunas referencias:


Gracias por el post, después de tanto tiempo posteas algo realmente interesante, sobretodo para los desarrolladores de software, lenguajes como Ruby, Python y perl facilitan mucho la tarea del desarrollador, yo personal mente aprendí Python y estoy comenzando con una pequeña aplicación (proyecto final de la Uni) y me ha facilitado mucho el trabajo, ahora es cosa de comenzar con Ruby que segun puedo ver en tus ejemplos es muy muy util.
gracias por el tiempo.