Clausuras, monjas y objetos

Aclaraciones sobre las clausuras (closures, en inglés) en varios lenguajes de programación, excluyendo Java.

"Closures"

A veces es difícil traducir términos de informática, porque el lenguaje español suele ir con algo de retraso en términos científicos y técnicos. Las cosas avanzan muy rápido y no siempre hay tiempo para aumentar el Diccionario de la RAE al ritmo adecuado.

En inglés les llaman "closures" (o Lexical Closures), y la traducción de wordreference es "cierre" o "clausura". Lo de "clausura" tiene gracia para mí, por aquello de las monjas de clausura, y creo que me quedo con esa traducción.

El concepto en sí no es muy complicado, quizá lo complicado sea explicarlo. Lo intento de forma rápida y con monjas de clausura. Vamos allá.

Funciones de primera clase

Las "clausuras" aparecen en los lenguajes de programación en los que las funciones son "objetos de primera clase", es decir, "cosas" que uno puede "guardar" en variables y "llevar" de un lado para otro.

Por ejemplo, en Scheme uno puede construirse una función y guardarla en la variable "F" tal que así:

(define F (lambda (x) (+ x 3)))

De este modo hemos guardado en la variable F una función que recibe un valor x y devuelve ese valor más tres.

En Lua la función (y la variable F) se escribirían así:

local F = function (x) return x + 3 end

Y en Javascript la variable F y la función se escribirían así:

var F = function (x) { return x + 3; };

Una vez nuestra variable F tiene una función guardada podemos llamar a esa función y enviarle el valor numérico 1 tal que así:

(F 1) ;  4  (en Scheme)
F(1)  -- 4  (en Lua)
F(1); // 4  (en Javascript)

Y obtendríamos el valor numérico 4 (o sea, 3+1) resultante.

Funciones que devuelven funciones

Las funciones pueden, desde luego, devolver valores numéricos. O valores de cualquier otro tipo que el lenguaje soporte. Por ejemplo, en Scheme las funciones pueden devolver booleanos, o listas de valores, o símbolos, o cadenas de caracteres, etc.

Lo curioso es que en estos lenguajes de programación que soportan funciones "de primera clase" resulta que las funciones pueden devolver también funciones.

Por ejemplo, podemos escribir una función en Scheme que devuelva otra función tal que así:

(define F (lambda (x) (lambda (y) (+ x y))))

Y en Lua podemos escribir esa función así:

local F = function (x)
    return function(y) return x + y end
end

Y en Javascript la cosa quedaría así:

var F = function (x) {
  return function(y) {
    return x + y;
  };
};

Una vez nuestra variable F tiene una función "guardada" podemos llamar a esa función y enviarle el valor numérico "1" (este "1" es importante después, como veremos), pero ahora en vez de obtener un número como antes obtendríamos una función (la función "generada" que mencionaremos después):

(F 1) ;  (lambda (y) (+ 1 y))           (en Scheme)
F(1)  -- function (y) return 1 + y end  (en Lua)
F(1); // function (y) { return 1 + y;}  (en Javascript)

Obviamente también podemos llamar a esta función resultante, y enviarle el número "5", por ejemplo, así:

((F 1) 5) ;  6 (en Scheme)
F(1)(5)   -- 6 (en Lua)
F(1)(5);  // 6 (en Javascript)

Y obtendríamos el resultado de sumar "5" y "1", o sea, "6".

El 1 y las monjas de clausura

Cuando construimos funciones que devuelven funciones, estas funciones resultantes "capturan" variables (o valores) y las "encierran" dentro. Estos valores (o variables) encerradas son como monjas de clausura: quedan encerradas dentro y no pueden salir de la función.

En el ejemplo anterior el 1 que enviábamos a F queda "capturado" o "encerrado" dentro de la variable x en la función "encerrada", y ahí se queda.

Por esta razón la función "generada" se llama "clausura" (o "closure" en inglés): porque "encierra" un valor o una variable en su interior (la variable x en los ejemplos anteriores), y esta no puede salir de ahí.

La función "generada" (la clausura) utiliza este valor (o esa variable). Por eso cuando invocamos a la clausura y le enviamos un "5" utiliza el valor "clausurado" (el 1 de antes, capturado en la variable x) para devolvernos el valor "6".

Java no tiene "closures"

El lenguaje Java no soporta funciones de primera clase, por eso no soporta tampoco "clausuras" ni "closures". Si oye por ahí "Java soporta clausuras" no se crea nada, porque no es verdad.

En las versiones más modernas de Java se incluyen las llamadas "lambda expressions", que son un símil a las funciones de primera clase, pero sin serlo. En consecuencia son también un símil a las clausuras, pero sin serlo.

Las "expresiones lambda" de Java no pueden "capturar" variables como en los lenguajes de programación anteriores (Lua, Scheme, Lisp, Javascript) y sólo pueden acceder a estas variables si tienen valores constantes (o "final"). En caso contrario el compilador genera un error diciendo:

local variables referenced from a lambda expression must be final or effectively final

Que es el reconocimiento explícito de que Java no soporta clausuras.

Las "clausuras" y los objetos

Que las "clausuras" puedan guardar dentro uno o más valores es muy interesante, porque uno puede usar "clausuras" para guardar información de estado.

Por ejemplo, uno puede construir una función que reciba un parámetro X y que devuelva diferentes funciones (o valores) dependiendo del valor de X. En Lua:

local F = function (x)
  if x == 'add1' then
    return function(y) return y + 1 end
  else if x == 'add3' then
    return function(y) return y + 3 end
  else
    return 3
  end
end

Y esto vendría a funcionar, básicamente como un objeto. De hecho hay lenguajes de programación que no son orientados a objetos pero que sí pueden "emular" objetos utilizando clausuras.

Al hilo de esto me permito traducir este mensaje de Anton van Straaten a Guy Steele (el padre de Scheme):

El venerable maestro Qc Na estaba paseando con su estudiante, Anton. Deseando entrar en discusión con su maestro Anton dijo "Maestro, he oído que los objetos son una cosa muy buena, ¿es esto cierto?". Qc Na miró con pena a su discípulo y dijo "Alumno tonto: los objetos no son más que las clausuras de los pobres".

Escarmentado, Anton dejó a su maestro y volvió a su celda, intentando estudiar clausuras. Leyó atentamente todos los artículos de la serie "Lambda: the Ultimate…​" y los relacionados, e implementó un pequeño intérprete de Scheme con un sistema de objetos basado en clausuras. Aprendió mucho, y deseó volver junto a su maestro para demostrarle su progreso.

En su siguiente paseo con Qc Na, Anton intentó impresionar a su maestro diciendo "Maestro, he estudiado el asunto diligentemente, y ahora comprendo que, efectivamente, los objetos son como las clausuras de los pobres". Qc Na respondió golpeando a Anton con su bastón y diciendo, "¿Cuándo aprenderás? Las clausuras son los objetos de los pobres."

Y en ese instante Anton vió la luz.

— Anton van Straaten
4 de junio de 2003

Que la luz les acompañe en el maravilloso mundo de las clausuras, o de los objetos.

y ¡Feliz Año Nuevo!,

Antonio