miércoles, 3 de junio de 2009

Qué es una closure

Bueno casi parecía obligada una entrada que explicara el título del blog ya que closure podria traducirse como cierre clausura y con un poco (o un mucho) mas de licencia al traducir "cerradura".

Cuando estudiaba la carrera hubo un ejercicio en java en el que me vi obligado a iterar varias veces sobre una estructura de datos para realizar operaciones sobre ellas. Entonces me vino la idea de porque no se podría pasar un método como parametro para no tener que repetir una y otra vez la estructura de iteración.

En mi mente lo que queria era algo así:


public void doForAll (Collection coll,Method m) {
for (item in coll) "ejecutar codigo de m
sobre cada elemento de la coleccion";
}


Le pregunte a mi profesor y su rapida respuesta fue NO.
Y no tenia razon claro porque en el mismo java se puede hacer algo así con clases anónimas. Usando el Commons Collection de Apache:


CollectionUtils.doForAll (coll, new Closure () {
public void execute(Object arg0) {
"ejecutar codigo sobre cada
elemento arg0 de la coleccion"
}});


Este "hack" se basa en las clases anonimas, implementaciones ad hoc normalmente de una interfaz. Gracias a ellas podemos implementar codigo justo en el sitio que lo necesitamos (si solo lo vamos a necesitar en un sitio y no va a hacer falta reutilizarlo)

Sin embargo aunque la interfaz se llama "closure" solo se parece y es una opción muy limitada si la comparamos con las closures reales de los lenguajes funcionales.

En javascript, python, ruby, groovy y la familiar de los lisp entre otros el codigo organizado en bloques es un "objeto" de primera clase. O sea puedes asignar a una variable un bloque de codigo, pasarlo como parametro y devolverlo en una funcion y ejecutarlo en el momento y lugar necesario.

En el caso de java con las clases anónimas puedes encapsular el código en una clase (con lo que se añade un elemento totalmente prescindible que hace de intermediario), crear una instancia y pasarla como parámetro y devolverla.

¿Donde esta la limitación? en la capacidad de las closures (por definición) de "capturar" las variables del contexto donde son definidas. Para que las clases anónimas puedan usar las variables externas a su bloque estas deben ser marcadas finales, es decir no modificables. Sin embargo con las closures reales el código mantiene una referencia a la variable en el momento de la definición del bloque de código y esta puede ser modificada antes y despues por el codigo y por la propia Closure.

Veamos un ejemplo en javascript (tomado de esta interesante página en Inglés):


function say667() {
// Local variable that ends up within closure
var num = 666;
var sayAlert = function() { alert(num); }
num++;
return sayAlert;
}

var f=say667();
f();


¿Que nos alerta la función? como es previsible por el nombre de la función "madre" que la ha creado nos pone "667" (Podeis comprobarlo en la pagina)

Bien todo esto de capturar las variables es muy bonito pero así de primeras no se le ve una gran utilidad. Sin embargo sí la tiene ya que en java esto es imposible de hacer:


(defn make-adder [x] #(+ x %1))
(def add5 (make-adder 5))
(add5 8)
; Nos pintaria 13


Un ejemplo ya clásico de Paul Graham en su magnifica introducción a Lisp: On Lisp implementada en el dialecto de lisp Clojure.

Para los que sienten extrañeza, temor o repelus a los parentesis (aunque son bien parecidos a las llaves) habrá que explicar que make-adder es una función que genera funciones de suma. Que add5 es una variable que apunta a una función creada con la anterior que suma 5 a un numero dado y que al ser llamada con un parámetro de 8 nos devolvería 13.

...precioso verdad.

Para los que todaváa no esten convencidos veamos las closures otra vez en acción:


(filter #(> %1 5) '(1 2 3 4 5 6 7 8 9))


Esta funcion filtra los datos de una lista usando una funcion ("#(> %1 5)") que compara cada uno de los elementos de la lista y los mete en otra solo si cumplen la condicion.
En este caso la devolucion seria (6 7 8 9).
Claro como el agua, breve como un parpadeo y picante como una guindilla si me permitis calificar asi un codigo.

Os dejo a vuestra imaginación las posibilidades que se abren con estos cierres.

PD: Expongo el ejemplo en haskell, otro de los lenguajes estrellas del paradigma declarativo funcional (mas lejano y puro todavía que clojure)

>let makeAdder :: Int -> (Int -> Int);makeAdder x=(x +)
> let add5=makeAdder 5
> add5 6
11

3 comentarios:

  1. Creo que la traducción mas correcta seria "clausura" como p.ej la clausura de Kleene en matemáticas.
    Mas alla de eso, mucha suerte! Yo también estoy intentando aprender Clojure y tus links me vienen bien...

    ResponderEliminar
  2. jum no se me habia ocurrido esa posibilidad, gracias por la correccion y me alegro que te haya servido de ayuda

    ResponderEliminar