« Adicionando usuários ao trac via scriptRuby e Lisp »

Closures

22/02/2007

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


Email por Walter Cruz em Python, Programação, Lua, JavaScript, Ruby

Endereço de trackback para este post

Trackback URL (clique direito e copie atalho/localização do link)

4 comentários

Comentário de: Proteu [Visitante] Email
Valeu pelo texto, Walter =D
Está bem explicativo =)
falou ae ^_^
22/02/2007 @ 23:41
Comentário de: Luciano Pacheco [Visitante] Email · http://lucmult.wordpress.com
Walter ficou muito boa a sua explicação!

Esse assunto é um pouco complicado de entender, mas com a sua forma de explicar ficou bem mais fácil !

Até mais.
23/02/2007 @ 11:51
Comentário de: Danilo [Visitante]
*****
Obrigado, cara!

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.
20/04/2008 @ 13:38
Comentário de: Walter Cruz [Membro] Email · http://waltercruz.com
Bacana cara! Continue visitando!
20/04/2008 @ 13:40

Deixe seu comentário


Seu endereço de e-mail não será revelado nesse site.

Sua URL será exibida.
PobreExcelente
=> :!: :?: :idea: :) :D :p B) ;) :> :roll: :oops: :| :-/ :( >:( :'( |-| :>> ;D :P :)) 88| :. :no: XX( :lalala: :crazy: >:XX
(Quebras de linha se tornam <br />)
(Nome, e-mail & website)
(Permitir que usuários o contatem através de um formulário eletrônico (seu e-mail não será exibido.))

You can just use your OpenID to provide your name, e-mail and url.

Busca

Recomendações

UML: Guia do Usuário

Estatísticas

Esse blog tem 165 posts e 536 comentários, com posts publicados no período de 15/08/2005 a 21/07/2008.

powered by b2evolution free blog software