2011/01/26

O tal do "closure"

NOTA: Artigo atualizado em 26/1/2011. Na época em que escrevi a primeira versão (12/2008), estava "namorando" Ruby, mas ela perdeu a graça quando a psiquiatra mudou o remédio :) A versão atual puxa mais para Javascript.

Com a recente ressurgência das linguagens interpretadas, o tal do "closure" está sendo mencionado o tempo todo. Mas afinal, que bicho é esse?

É um conceito difícil de pegar para quem vem de linguagens estáticas/compiladas, e também foi difícil para mim, muito embora já existissem closures no Clipper 5.

Em Python é particularmente fácil criar closures no modo interativo:

>>> a = lambda a: a*2
>>> a(3)
6

>>> def dobro(x):
... return x*2
...
>>> a = dobro
>>> a(3)
6

Na primeira forma, usamos o operador "lambda" e criamos o que muita gente chama de função anônima, ou sem nome, que fica contida em uma variável.

Na segunda forma, atribuímos uma função convencional a uma variável, com o mesmo resultado.

Estas mesmas coisas poderiam ser feitas facilmente em Javascript, pois também nele as funções e métodos são "objetos de primeira classe" e podem ser atribuídos e passados como se fossem números ou strings.

Em C ou C++, existem ponteiros para funções, que suprem alguns casos de uso dos closures. Isto leva muita gente a concluir que um closure é simplesmente uma função anônima ou um ponteiro para função ou método.

Mas closures são na verdade bem mais poderosos. Considere o código Javascript a seguir:

function multiplicador(x)
{
return function (y) { return x * y; };
}

x2 = multiplicador(2);
x10 = multiplicador(10);

alert(x2(5)); // deve mostrar 10
alert(x10(8)); // deve mostrar 80

A função "multiplicador" fabrica e retorna outra função, esta sim capaz de executar a operação matemática.

Agora, o mais importante: cada função fabricada lembra o parâmetro "x" originalmente recebido. Portanto, "x2" sempre vai multiplicar números por 2, pois 2 foi o valor recebido por "x" quando o closure x2 foi criado.

Podemos criar tantos closures quanto quisermos, e cada um terá a sua versão congelada de "x", sem que um interfira no outro. O mesmo truque básico também funciona em Python, com sintaxe um pouco diferente:

>>> def multiplicador(x):
... return lambda y: x*y
...
>>> x2 = multiplicador(2)
>>> x2(5)
10

Em Python, podemos também definir uma função dentro de outra em vez de lambda, com o mesmo resultado:

>>> def multiplicador(x):
... def m(y):
... return y * x;
... return m
...
>>> x3 = multiplicador(3)
>>> x3(7)
21

Os closures têm um quê de templates do C++, embora sejam infinitamente mais poderosos por serem criados em tempo de execução.

Se o closure "lembra" dos parâmetros, é porque estão armazenados em algum lugar, que é o stack frame da função. O stack frame é a estrutura onde estão contidos os parâmetros e as variáveis locais de uma chamada de função. A cada chamada de função, um novo stack frame é criado, e normalmente é destruído quando a função retorna.

Mas, como multiplicador(x) retorna uma outra função, que faz referência a "x", o stack frame que contém "x" tem de ficar na memória -- pelo menos enquanto o closure não for apagado.

Em linguagens interpretadas "decentes", o stack frame é um objeto como outro qualquer (embora oculto), que só é removido da memória quando nenhum outro objeto faz referência a ele. Assim, a decisão de destruir ou reter o stack frame é tomada automaticamente pelo coletor de lixo; não é preciso grandes mexidas no interpretador para suportar closures.

Compare isto com os ponteiros para funções do C/C++. É possível imitar alguns truques de closure usando "functors", embora com muito menos naturalidade:

#include <stdio.h>

class Multiplica {
public:
int xx;
Multiplica(int x) { xx = x; }
int operator() (int y) { return xx*y; }

};

int main()
{
Multiplica x2 = Multiplica(2);
Multiplica x10 = Multiplica(10);
printf("%d %d\n", x2(3), x10(3));
}

/Users/epx $ gcc -o closure closure.cpp -lstdc++
/Users/epx $ ./closure
6 30

Um uso mais esotérico de closures, que não envolve "fotografia" de variáveis nem fazer papel de ponteiro de função, é a criação de novos comandos numa linguagem. Isto faz mais sentido onde a sintaxe do code block integra-se bem com o resto da linguagem, como no caso do Ruby:

def executar_as_vezes(&codeblock)
if rand < 0.3
codeblock call
end
end

executar_as_vezes {
...
}

# Também podemos fazer o bloco assim:
executar_as_vezes do
....
end

Tal uso de closure seria difícil em Python pois uma função lambda não ficaria "natural". Em Javascript, o resultado ficaria mais natural que Python (podemos passar um bloco de código entre chaves) porém menos natural que Ruby pois o bloco tem de ser precedido por function().

Este uso de closures é mais importante em linguagens como LISP e Smalltalk -- se bem entendi, até estruturas de controle como if, while etc. são na verdade funções implementadas na própria linguagem, da mesma forma que implementamos executar_as_vezes.

Javascript

Os closures "salvam" a linguagem Javascript, tornando-a respeitável. Sem eles. seria medíocre.

Por exemplo, o sistema de objetos usa o famigerado this, uma variável global, para referenciar o objeto corrente. Aí...

classe.prototype.metodo = function ()
{
this.outro_metodo(); // funciona
setTimeout(function () { this.outro_metodo(); }, 100); // não funciona
}

O problema é que setTimeout() é método do objeto window, e this será igual a window, e obviamente o método window.outro_metodo() não existe.

É um bom argumento a favor da passagem explícita de "self" no Python :) Em homenagem a isso, vamos adicionar uma variável ao closure:

minha_classe.prototype.metodo = function ()
{
var self = this;
setTimeout(function () { self.outro_metodo(); }, 100); // funciona
}

Este exemplo funciona porque self não é uma variável global, e ela é preenchida numa situação onde this é (quase) garantidamente uma instância de minha_classe. A função passada a setTimeout() referencia self, portanto o stack frame de metodo() será preservado até a chamada retardada se completar.

Em Python, um código semelhante não daria problema, por dois motivos: 1) porque self, que faz o papel análogo a this, é passado explicitamente e portanto é parte do closure, não é uma variável global; 2) Se tentarmos passar um ponteiro para um método, usando a sintaxe objeto.metodo (sem parênteses), a linguagem cria automaticamente um closure para que o método seja chamado sobre o objeto pretendido.

Aliás, a única forma "limpa" de passar um método atrelado a um objeto em Javascript é construindo explicitamente um closure:

obj_metodo = function () {
obj.metodo();
};
obj_metodo2 = obj.metodo;

// funciona
obj_metodo()

// não funciona porque tenta fazer this.obj_metodo2()
// e provavelmente this não é igual a obj
obj_metodo2()

// mas assim funciona (embora seja feio)
obj.call(obj_metodo2);

// funciona mas é inócuo porque o closure
// não usa this para resolver o objeto
obj.call(obj_metodo)


Em Javascript, se quisermos usar o sistema de "classes" baseado em protótipos, precisamos criar um objeto explicitamente com new. Mas é possível criar objetos sem exigir new:

function minha_classe() { return {}; }

obj = minha_classe();

A função minha_classe() é mais uma fábrica que um construtor, pois o objeto retornado "{}" não tem classe alguma. Podemos retornar um objeto mais substancial, com atributos (variáveis-membro) e métodos:

function minha_classe() {
return {
membro1: 1,
membro2: "bla",
metodo: function (x) {
this.membro1 = x;
}
};
}

As duas grandes desvantagens desta forma frente ao mecanismo 'clássico' do Javascript baseado em protótipos, são uma maior carga na construção (pois todo o código de minha_classe() tem de ser executado novamente a cada construção, enquanto protótipos são construídos apenas uma vez), e o fato de cada objeto possuir uma cópia de todos os métodos (enquanto protótipos são compartilhados entre todos os objetos da mesma classe).

Normalmente, as vantagens da forma acima compensam as desvantagens, e códigos avançados como toolkits tendem a usá-la no código todo.

Pode-se até criar membros e métodos privados usando esta forma, coisa que parece impossível no Javascript:

function minha_classe() {
var membro_privado = 3;
var metodo_privado = function (x) {
return membro_privado * x };
};

return {
metodo: function (x) {
metodo_privado();
}
};
}

Note que os métodos privados ficam "fora" do objeto. Mas, como este objeto referencia variáveis do stack frame da função minha_classe(), ele é preservado. O stack frame torna-se um "objeto-fantasma" que acompanha o objeto público enquanto este último existir.

O mais interessante desta construção é que os clientes simplesmente não podem ter acesso às variáveis privadas. E, no exemplo, a única ligação entre o objeto e o stack frame é metodo(), que referencia metodo_privado(). Se um cliente tentar substituir o método, fazendo algo como

objeto.metodo = function (x) { alert(membro_privado) };

não vai conseguir ler a variável privada (porque o escopo é diferente) e ainda por cima vai perder para sempre o stack frame (porque a única referência a ele foi removida).

Closures resolvem um outro problema do Javascript, que é o uso excessivo do escopo global. A construção a seguir é comumente encontrada em toolkits:

// início do arquivo
function () {
.... milhares de linhas de código ...
}();
// fim do arquivo

Uma função anônima é criada e imediatamente executada. E ela contém todo o código do arquivo Javascript. Qual o objetivo disto?

O objetivo criar e executar tudo num escopo não-global. O escopo será o stack frame desta grande função anônima.

Python faz algo parecido -- cada módulo é um objeto e os elementos do módulo são propriedades dele -- mas é um mecanismo embutido na linguagem, bem mais "limpo".
blog comments powered by Disqus