Introducción a Groovy (II)

En el segundo artículo del tutorial de introducción a Groovy vamos a ver como trabajar con cadenas de texto, así como el concepto de Closure. En el primer artículo vimos las características más básicas del lenguaje, así como las reglas de sintaxis. Esta entrega es una continuación de la anterior, pues muestra características del lenguaje que son comúnmente utilizadas.

2.1 Tipos de cadenas de texto

Groovy soporta de forma nativa tres tipos de cadenas de texto: Strings, GStrings y Heredocs. Cual usar en cada momento es una decisión que surgirá de las necesidades de nuestro código, aunque en última instancia todas ellas son intercambiales por cualquier otra.

2.2 String's

Un String en Groovy es similar a un String en Java, con la particularidad de que puede ser construido usando tanto comillas simples como dobles:

def cadena1 = "Esto es válido en Java y Groovy"
def cadena2 = 'Esto es válido solo en Groovy'

println cadena1.class
println cadena2.class
	

Si observamos la salida de ambas sentencias println veremos que las dos variables definidas previamente, cadena1 y cadena2, son de tipo java.lang.String. Un tipo de comillado puede contener al otro en su interior, sin necesidad de (aunque está permitido) escapar el comillado interior:

def cadena3 = "Hola 'Usuario'"
def cadena4 = 'Hola "Usuario"'
	

2.3 GString's

GStrings (abreviación de Groovy Strings) es una cadena de texto que contiene expresiones embebidas. Un GString solo puede ser construido con comillas dobles o triples (las cuales veremos en el apartado 2.4). Veamos un ejemplo:

def saldo = 1821.14
println "El saldo es de ${saldo} euros"
	

En el código anterior, vemos que dentro de la cadena de texto pasada a println hemos insertado la variable saldo como si fuera parte de la propia cadena de texto. Esta inserción se hace dentro del operador ${} y es evaluada en tiempo de ejecución. Como se indicaba en el párrafo anterior, podemos insertar cualquier expresión dentro de un GString:

def saldo = 1821.14
def mensaje = "El saldo a fecha ${new Date()} es de ${saldo} euros"
println mensaje
	

Es evidente que el código anterior es mucho mas natural, tanto al escribirlo como al leerlo, que su homologo en Java (el cual necesitaría unir cada parte de texto con cada expresión mediante el operador de concatenación +). Es importante tener siempre presente que un GString no es un String, y que usarlos en combinación puede producir resultados inesperados:

def str = "abc" 
def gstr = "${str}" 

println str.equals(gstr) 
println str.equals(gstr.toString()) 
	

La primera sentencia println devuelve false, ya que ambas clases implementan sus métodos equals() y hashCode() de forma diferente. El mismo problema puede surgir (y surgirá) si mezclamos String's y GString's en sitios tales como las claves de un Map. Una forma de solventar este problema es invocar el método toString() en el objeto GString, como puede verse en la segunda sentencia println del código anterior (la cual devuelve true pues ahora ambos objetos a comparar sí son del mismo tipo).

2.4 Heredocs

El último tipo de cadena de texto soportado en Groovy es el Heredoc, el cual se forma con tres comillas simples o dobles:

println """Esto es un Heredoc""" 
	

Los Heredoc's nos permiten tanto almacenar cadenas de texto multilínea en una única variable como mezclar comillas simples y dobles en su interior sin necesidad de escaparlas:

def multilinea = """
Primera linea
Segunda linea
Tercera linea con "comillas dobles" y 'comillas simples'
"""
println multilinea
	

Por las dos características arriba mencionadas, los Heredocs son tremendamente útiles para almacenar XML y HTML en una variable. Los Heredocs formados con comillas dobles permiten insertar cualquier expresión, resultando en un GString multilínea. Realmente los Heredocs no son un tipo (de ahí que no aparece su nombre en otra fuente, como ocurre por ejemplo con los String's y GString's), sino más bien una característica del lenguaje para escribir cadenas multilínea. Esto podemos comprobarlo de la siguiente manera:

println '''heredoc con comillas simples'''.class 
println """heredoc con comillas dobles""".class 
println """heredoc con comillas dobles y expresión embebida: ${new Date()}""".class 
	

Las dos primeras sentencias println del código anterior devuelven java.lang.String como tipo del heredoc, mientras que la tercera expresión devuelve org.codehaus.groovy.runtime.GStringImpl, ya que detecta la expresión embebida y automáticamente trata la cadena como si fuera un GString.

2.5 Closures

Los closures son una de las características mas potentes del Groovy, y para aquellos que solo han trabajado con Java tal vez la más difícil de comprender y utilizar correctamente (en otros lenguajes hay construcciones comparables a los closures de Groovy). Empecemos con su definición: un closure es un bloque de código autónomo (fuera de cualquier clase) que puede ser definido y usado en puntos distintos. Veamos el ejemplo más simple posible:

def saludar = { println '¡Hola Mundo de los Closures!' }
	

En el código anterior se define un closure, siempre entre llaves, y se asigna a una variable llamada saludar. Para ejecutar este closure podemos invocarlo como si fuera un método (a efectos prácticos lo es):

saludar()
	

Un closure puede aceptar parametros:

def saludar2 = { println "¡Hola ${it}!" }
saludar2 "David"
	

En el ejemplo anterior, utilizamos dentro del closure la variable it, la cual es implícita para todos los closures que no definen ningún parámetro. Para cerrar el círculo y entender que significa esto, declaremos un closure con parámetros explícitos:

def saludar3 = { nombre -> 
    println "¡Hola ${nombre}!" 
} 

saludar3 "David" 
	

En este último ejemplo, al contrario del anterior, hemos definido un parámetro explícito llamado nombre que sustituye a it y hace el código más legible y consonante con su función final (saludar usando un nombre). Este parámetro debe declararse antes del código del closure y separarse de este con los caracteres ->. Por supuesto, podemos declarar más de un parámetro para un closure:

def saludar4 = { nombre, apellido -> 
    println "¡Hola ${nombre} ${apellido}!" 
} 
saludar4 "David", "Marco" 
	

Recuerda que, como se explicó en el punto 1.9 del primer artículo del tutorial, Groovy permite omitir los paréntesis al llamar a una función a la que le pasamos argumentos (y tal como hemos dicho unos párrafos mas arriba los closures son a efectos prácticos funciones) de ahí que la llamada a los closures saludar2, saludar3 y saludar4 puedan parecer un poco extrañas.

2.6 Currying

Currying es una técnica que nos permite pre-cargar valores en los parámetros de un closure. Veamos un ejemplo:

def saludar5 = { nombre -> println "¡Hola ${nombre}!" }
def anonimo = saludar5.curry("Anonimo")
anonimo()
	

En el ejemplo anterior hemos pre-cargado el valor "Anonimo" para el único parámetro del closure saludar5, y el closure resultante lo hemos almacenado en la variable anonimo. A continuación hemos llamado a dicho closure, resultando en una llamada equivalente a:

saludar5 "Anonimo"
	

Este ejemplo, tan simple como estúpido (espero que estés de acuerdo conmigo) no muestra la verdadera potencia del currying; su única misión es mostrarnos el concepto de la manera más simple posible. Ahora es el momento de ver algo más práctico:

def multiplicar = { valor1, valor2 -> 
    valor1 * valor2 
} 
def doble = multiplicar.curry(2) 
def triple = multiplicar.curry(3) 

println doble(7) 
println triple(7) 
	

El currying muestra toda su potencia cuando trabajamos con múltiples parámetros, ya que nos permite pre-cargar valores que serán siempre los mismos para una determinada función y pasar el resto en tiempo de ejecución. En el ejemplo anterior, hemos creado dos closures, doble y triple que hacen currying sobre el closure multiplicar, pre-cargando los valores 2 y 3 respectivamente en su parámetro más a la izquierda, valor1. Así, al llamar a doble(7) y triple(7) lo que hacemos es asignar el valor 7 al primer parámetro sin definir de multiplicar, en nuestro caso valor2 (ya que, vuelvo a insistir, valor1 fue asignado con un valor por defecto al hacer currying).

2.7 Closures como parámetros

Una de las características más potentes de los closures es que pueden ser utilizadas como argumentos de una función:

def repetirClosure(int numRepeticiones, Closure closure) { 
    for(int i = 0; i < numRepeticiones; i++) { 
        closure.call(i) 
    } 
} 

def closure = { println it } 
repetirClosure(5, closure) 
	

En el ejemplo anterior, hemos definido un método (repetirClosure) que acepta como parámetro un closure. Dentro de dicho método, invocamos el closure dentro de un bucle mediante el método call(), al cual podemos pasarle cualquier parámetro que sera enviado directamente al closure (esto es equivalente a ejecutar el closure directamente, como hemos hecho hasta ahora, pero necesario cuando trabajamos con un objeto Closure en lugar de con la propia definición del closure). A continuación, y ya fuera de la definición del método, definimos un closure y se lo pasamos al método recién definido. Por otro lado, cuando el parámetro que acepta el closure es el situado más a la derecha en la definición del método (como ocurre en nuestro caso) podemos pasar el closure de la siguiente manera:

repetirClosure(5) { println it }
	

Esta última sintaxis la veremos de forma intensiva en el capitulo 3 cuando trabajemos con Rangos y Colecciones.

Los closures permiten hacer cosas muchísimo más complejas (y por supuesto útiles) que las aquí mostradas, pero todo lo que necesitamos saber para continuar con el tutorial se encuentra en este capítulo. Es importante que escribas tus propios closures para ganar confianza con esta potentísima herramienta del lenguaje, y que busques información adicional a la aquí descrita si quieres explorar/explotar todas sus características.

Resumen

En este artículo del tutorial de introducción a Groovy hemos visto características del lenguaje, como los GStrings, Heredocs y los Closures, que hacen de Groovy un lenguaje muy potente y dinámico. En el próximo artículo veremos que son los Rangos, y como Groovy ha simplificado el uso de Colecciones respecto a Java. Hasta entonces, ¡feliz currying!.