| « Adicionando usuários ao trac via script | Ruby e Lisp » |
Closures
Com a popularidade da linguagem Ruby, conceitos como meta-programação, blocos e closures começam a se tornar parte do vocabulário de muitos programadores. Alguns, como por exemplo os usuários de Lua e JavaScript já estão acostumados com isso. Outros, como os usuários de Java tem adicionado a palavrinha closures na lista de coisas que desejam ter na linguagem. Mas o que vem a ser isso?
Resposta:
Precisamos voltar um pouco às bases para entender o conceito. Dentre as características que uma linguagem deve suportar para disponibilizar o uso de closures estão: escopo léxico, funções de primeiro nível e que um escopo tenha acesso a variáveis de fora dele. Mas vamos por partes!
Escopo Léxico
Em uma linguagem com escopo léxico (também chamado de escopo estático), o escopo de uma variável é definido como sendo o menor bloco (begin/end ou corpo da função) contendo a variável declarada. Isso significa que um identificador (variável) é acessível somente dentro do bloco e dos procedimentos que os armazenam. (Retirado de http://lua-users.org/wiki/LuaScopingDiscussion) .
O contrário do escopo léxico é chamado de escopo dinâmico. O escopo dinâmico cria variáveis que podem ser acessadas fora de um bloco.
Por exemplo, o Emacs Lisp tem escopo dinâmico, Common Lisp tem os dois tipos de escopo e Scheme tem escopo léxico. C e Pascal tem escopo léxico.
Objetos de Primeira Classe
Nas linguagens onde funções são objetos de primeira classe, funções podem ser armazenadas em variáveis, podem ser passadas como argumentos para outras funções ou recebidas como argumentos em uma função. Dizemos que em uma linguagem funções são funções de primeira classe quando elas são tratadas como objetos de primeira classe. Entre as linguagens que suportam isso: Lisp, Scheme, ML, Haskell, Io, Python, ECMAScript (JavaScript), Lua, Ruby e Scala, entre outras.
Mergulhando
Vamos examinar esses conceitos na prática. Para isso, vamos usar a linguagem Lua, que tem tanto escopo léxico como objetos de primeira classe.
Antes de tudo, uma frase para te deixar intrigado sobre Lua: em Lua, TODAS as funções são anônimas. Segundo o livro Programming in Lua: «Quando falamos sobre o nomes de uma função, digamos print, estamos falando na verdade sobre a variável que contém uma função». Vamos usar a função print como vítima, substituindo-a.
Lua 5.1.1 Copyright (C) 1994-2006 Lua.org, PUC-Rio > nome = 'walter' > print(nome) walter > old_print = print > print = function(var) >> old_print(type(var) .. " " .. tostring(var)) >> end > print(nome) string walter > numero = 1 > booleano = false > print(numero) number 1 > print(booleano) boolean false
Repare que substituímos a função print nativa pela nossa. Esse é um exemplo do uso de funções de primeira classe.
Finalmente, closures
Vamos dissecar um exemplo que usei no meu artigo Do PHP para Lua.
function newCounter ()
local i = 0
return function () -- função anônima
i = i + 1
return i
end
end
c1 = newCounter()
print(c1()) -- imprime 1
print(c1()) -- imprime 2
print(c1()) -- imprime 3
print(c1()) -- imprime 4
print(c1()) -- imprime 5
Repare que a cada nova chamada, o retorno da função é incrementado. Mas sabemos também que uma função não tem estado. Como a função newCounter
consegue manter o estado? A resposta: através de uma closure.
Logo após o cabeçalho da função newCounter, declaramos uma várivel, i, com valor 0. A função newCounter retorna uma função anônima.
A função anônima retornada por newCounter altera o valor de uma variável, chamada i. Mas de onde saiu essa variável? Ele veio do escopo anterior,
definido por newCounter. A variável i não é descartada - ele continua presa no escopo da função anônima. Essa é nossa closure em ação.
Minerando Ruby
Acostumado como estava com o jeitão de closures em Lua e Javascript, demorei um pouco para entender o
funcionamento de closures em Ruby. O que me fez entender como funcionavam closures em Ruby foi uma frase que eu li numa
PEP - a PEP 3104. A frase: «Funções em Ruby podem conter outras definições de funções, e podem conter também blocos. Blocos
tem acesso a variáveis externas [ao seu escopo] mas funções internas não». Ou seja, para compreendermos closures em Ruby, temos
antes de compreender blocos.
Blocos em Ruby
Blocos são trechos de código delimitados por {} ou por do e end. Utiliza-se {} quando o bloco é de uma linha apenas, e do/end
quando o bloco é maior. Exemplos:
{puts 'Oi mundo!'}
ou:
do
puts 'Oi mundo!'
puts 'Aqui tem mais espaço'
end
Blocos podem ser associados à uma variável usando Proc.new ou lambda
[taq@~]irb
irb(main):001:0> vezes3 = Proc.new {|n| n*3}
=> #<Proc:0xb7de21fc@(irb):1>
irb(main):002:0> vezes3.call(1)
=> 3
irb(main):003:0> vezes3.call(2)
=> 6
irb(main):004:0> vezes3.call(3)
=> 9
irb(main):005:0>
[taq@~]irb
irb(main):001:0> vezes3 = lambda {|n| n*3}
=> #<Proc:0xb7dd96c0@(irb):1>
irb(main):002:0> vezes3.call(1)
=> 3
irb(main):003:0> vezes3.call(2)
=> 6
irb(main):004:0> vezes3.call(3)
=> 9
irb(main):005:0>
Repare que um bloco é uma função anônima, e que agora o associamos a uma variável.
Uma função pode receber blocos como parâmetro. Nesse caso, as variáveis definidas no bloco são extraídas do escopo externo. Aí está
nossa closure! Um exemplo:
walter@walter:~/devel/waltercruz.com$ irb
>> nome = ['Walter','Rodrigo','de','Sá','Cruz']
=> ["Walter", "Rodrigo", "de", "S\341", "Cruz"]
>> nome.sort {|e1,e2| e1[0]<=> e2[0]}
=> ["Cruz", "Rodrigo", "S\341", "Walter", "de"]
>> nome.sort_by {|e| e.length}
=> ["de", "S\341", "Cruz", "Walter", "Rodrigo"]
Primeiro, criamos um array com as palavras que compõem o meu nome. Logo em seguida, executamos o método sort nesse array. Mas
passamos um bloco como parâmetro. No bloco, esperamos duas variáveis e fazemos uma comparação usando o método
<=> de strings. No caso, comparamos a primeira letra de cada string. Loco em seguida, fazemos um sort_by, passando também um outro bloco.
Nesse caso, o array é ordenado pelos tamanhos das palavras.
Por exemplo, podemos fazer facilmente uma ordenação alfabética pela última letra da palavra:
>> nome.sort {|e1,e2| e1[e1.length-1]<=>e2[e2.length-1]}
=> ["de", "Rodrigo", "Walter", "Cruz", "S\341"]
closures em Python
Antigamente, Python tinha apenas 2 escopos: o local e o global (para maiores informações, leia: http://defpython.blogspot.com/2007/02/namespaces-locals-e-globals.html )
Uma função não poderia acessar uma variável que estivesse em um escopo externo.
A partir da versão 2.2, Python tem nested scopes, mas é preciso conhecer alguns truques para poder usar essa funcionalidade (http://www.python.org/dev/peps/pep-0227/).
Exemplo de código:
def foo(start=0):
counter = [start] # counter is 1-element array
def bar():
counter[0] = counter[0] + 1
return counter[0]
return bar
count = foo(10)
print count()
Executando com o Python 1.5, que não tem nested scopes.
Traceback (innermost last):
File "/home/digo/devel/python/closurewikipedia.py", line 9, in ?
print count()
File "/home/digo/devel/python/closurewikipedia.py", line 4, in bar
counter[0] = counter[0] + 1
NameError: counter
Executando com o Python 2.4, retorna o valor esperado, 11.
Como em Python a inicialização e a atribuição de variáveis assumem a mesma forma (apenas o sinal de =), é necessário usar uma lista para enganar o interpretador.
Por exemplo, se a função acima fosse definida como:
def foo(start=0):
counter = start
def bar():
counter = counter + 1
return counter
return bar
count = foo(10)
print count()
Teríamos o erro:
UnboundLocalError: local variable 'counter' referenced before assignment
Já que o interpretador tenta criar a variável counter dentro do escopo de bar.
Python 3.0 nos traz uma feliz surpresa, definida na PEP-3104: Access to Names in Outer Scopes . O truque a a ser adicionado é uma nova palavra-chave:
nonlocal, a ser adicionada antes de uma variável, para indicar que ela deve ser procurada em um escopo externo.
Além das listas, também podemos usar um «objeto simulador de escopo», que é um objeto comum contendo as variáveis que queremos ter acesso de escrita:
def foo(start=0):
ns = type("Namespace", (object,), { "counter": start })()
def bar():
ns.counter += 1
return ns.counter
return bar
Python 2.5
Uma das vantagens das closures em Ruby é nos permitir um pouco de preguiça. Veja:
irb(main):001:0> File.open("teste.txt") do |handle|
irb(main):002:1* handle.each_line do |linha|
irb(main):003:2* puts linha
irb(main):004:2> end
irb(main):005:1> end
Pergunta: onde está o fechamento do arquivo? Resposta: Quando o método open recebe um bloco, o arquivo é automaticamente fechado quando o bloco termina.
Python 2.6 irá trazer uma novidade (já disponível no 2.5, pelo namespace __future__), que é a declaração with. Veja só:
with open('teste', 'r') as f:
for line in f:
sys.stdout.write(line)
E com isso o arquivo será fechado automaticamente, assim como no Ruby. O segredo aqui é que o objeto tenha um método _exit(), que será executado
no final do bloco. Mais em: http://docs.python.org/whatsnew/pep-343.html
Conclusão
closures são um pequeno grande truque. Se sua linguagem favorita suporta, use! Mas, se você é pythonista como eu, tem de se agarrar ao truque das
listas, do with, do objeto simulador de escopo, e aguardar um pouco até Python 3000. O que não torna Python uma linguagem menos poderosa que Ruby, já que isso é, no fim
das contas, uma questão de estilo. Meus agradecimentos ao Taq, de cujo tutorial copiei alguns exemplos.
Agradecimentos
Muito obrigado ao Rodrigo Cacilhas por revisar meu texto. A seção do objeto simulador de escopo e de autoria dele!
Bibliografia:
http://mail.python.org/pipermail/python-list/2004-July/270951.html
http://mail.python.org/pipermail/python-dev/2003-October/039318.html
http://www.rubycentral.com/book/ref_c_file.html
http://www.martinfowler.com/bliki/Closure.html
http://montegasppa.blogspot.com/2007/01/tampinhas.html
Endereço de trackback para este post
Trackback URL (clique direito e copie atalho/localização do link)
4 comentários
Está bem explicativo =)
falou ae ^_^
Esse assunto é um pouco complicado de entender, mas com a sua forma de explicar ficou bem mais fácil !
Até mais.
Eu já conhecia closures em Lua, como comecei a aprender Python, desejava saber como criar closures nesta linguagem.
De todas páginas que encontrei pesquisando no google, essa foi a única que esclareceu isso pra mim.
Abraços.
