2007-01-24

Tampinhas


Passei quase o mês todo conversando com o Walter sobre closures, porque o ele está escrevendo um artigo muito bom sobre o assunto e eu estou revisando.

Como fiquei empolgado, resolvi escrever meu próprio artigo sobre o assunto, apenas como uma prévia para o artigo do Walter – muito melhor.

O conceito de closures (literamente «tampas», ou «clausuras») não é tão complicado assim, mas é preciso entender bem outros conceitos antes de chegar lá.

Variável


Em programação, variável é um repositório (na memória principal) para armazenar uma informação. O conteúdo deste repositório pode mudar ao longo do programa.

Há em programação basicamente três tipos de variáveis:
  1. alocação direta: a variável é um apelido para uma posição na memória;
  2. ponteiro: a variável contém um endereço de memória e este sim contém a informação;
  3. referência: uma variação muito popular de ponteiro, usada nas linguagens mais modernas.


Primeira classe


Dado ou objeto de alto nível ou de primeira classe é qualquer informação que pode ser armazenada por uma variável.

Basicamente qualquer objeto de primeira classe pode ser passado como parâmetro ou recebido como retorno de uma função.

Para podermos trabalhar com closures, função precisa ser um objeto de primeira classe na linguagem.

Escopo


Escopo (em inglês namespace) é a «região» onde uma variável «existe», ou seja, terminando o escopo, a variável deixa de existir.

Em linguagens com boa coleta de lixo (garbage collection), quando não há mais variáveis referenciando um dado na memória, este também deixa de existir (a memória é liberada).

Você pode ter um escopo dentro do outro. As variáveis pertencentes ao escopo superior (escopo pai) existem no escopo interno, porque o escopo destas ainda não terminou.

Em C, Java, C++ e Perl, o escopo se inicia com abre-chaves ({) e termina com fecha-chaves (}), ou seja, todo e qualquer bloco de código é um escopo em si.

Em Python o escopo se inicia com a indentação de uma função ou classe e termina com a dedentação equivalente.

Em Lua, assim como em C, todo e qualquer bloco é um escopo em si.

Em Ruby, eu não faço a mínima... =P
Tem tempo que não mexo com essa linguagem.

Closures


Tendo esclarecido os conceitos anteriores, podemos falar de closures agora. =)

Imaginem que temos uma função (que tem seu escopo), dentro dela há outra função. Então o escopo da segunda função está dentro do escopo da primeira e, portanto, as variáveis da primeira existem no escopo da segunda.

Agora imaginem que na linguagem usada, função é um dado de alto nível e a primeira função retorna a segunda. Então, ao terminar sua execução, o escopo da primeira se encerra, mas o escopo mais interno não!

O que acontece?

O escopo da primeira função continua existindo «preso» ao escopo da segunda.

Isso é muito legal, porque podemos criar reiteradores mesmo se a linguagem não tiver este recurso diretamente disponível.

Por exemplo, criar um reiterador que devolva a sequência de Fibonacci (já repararam como gosto desta sequência?).

Em Perl temos:
sub mkfib {
my $a, $b = 0, 1;
return sub {
$a, $b = $b, $a+$b;
return $a;
};
};

my $fib = mkfib();


A cada chamada de $fib->() será retornado o próximo elemento da sequência. Em Perl, a palavra reservada my indica que aquela(s) variável(is) pertence(m) ao escopo atual (local). Se não for usado my, a variável será procurada no escopo pai e, se não existir, no anterior, até chegar ao global. Se a variável não existir no escopo global, será criada (no escopo global).

Vamos ver o equivalente em Python:
def mkfib():
c = [0, 1]
def f():
c[0], c[1] = c[1], sum(c)
return c[0]
return f

fib = mkfib()


Em Python, toda atribuição cria uma variável no escopo local (se ela não existir neste), portanto tivemos de usar uma lista para «enganar» a linguagem e poder alterar valores de variáveis do escopo pai.

O equivalente em Lua:
fib = (function ()
local a, b = 0, 1
return function ()
a, b = b, a+b
return a
end
end)()


Lua funciona como Perl (local equivale a my de Perl).

Contador


Só para arrematar, vou passar como exemplo um contador...

Em Perl:
sub mkcounter {
my $c = 0;
return sub {
return ++$c;
};
};

$count = mkcounter();


Em Python:
def mkcounter():
c = [0]
def f():
c[0] += 1
return c[0]
return f

count = mkcounter()


Em Lua:
count = (function ()
local c = 0
return function ()
c = c + 1
return c
end
end)()


[]'s