Site menu O tal do closure
e-mail icon
Site menu

O tal do closure

e-mail icon

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.

Em tempo: este "congelamento" de um parâmetro de função para posterior reuso repetido é denominado currying.

Se o closure "lembra" dos parâmetros, é porque estão armazenados em algum lugar. Este lugar é 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 um closure, que faz referência a "x", o stack frame que contém "x" tem de permanecer na memória, pelo menos enquanto o closure existir.

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
    // não funciona:
    setTimeout(function () { this.outro_metodo(); }, 100);
}

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 isto, 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 como parâmetro 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
// já sabia que objeto devia usar, e não usa
// this para nada
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 também é possível criar objetos sem usar 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. Fica por nossa conta implementar todo o seu comportamento.

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:

Normalmente, as vantagens de abandonar o new compensam as desvantagens, e códigos avançados como toolkits seguem esta forma.

Ela permite até criar membros e métodos privados, 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 subverter 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 que havia 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".

e-mail icon