Closures: um escopo local persistente

Cidadãos de primeira classe e escopo léxico

Igor G. Peternella
Editora Globo
4 min readJul 11, 2018

--

Photo by Chris Ried on Unsplash

Hoje vou falar sobre uma interessante técnica chamada closures! Vamos aprender o que são closures e como implementá-las! Para tal, utilizarei a linguagem Python que é uma linguagem de fácil leitura e será bastante útil para aprendermos este conceito que, a princípio, pode ser um pouco complicado! Ah! E já adianto: closures tem várias aplicações em Python! Já usou decoradores de funções? Se sim, já pode ter usado closures e nem sabia! Vamos aprender isso de uma vez?

Antes de aprendermos o conceito de closures, precisamos de um pouquinho de teoria para que tudo faça sentido! Precisamos de 2 conceitos definidos no design de linguagens de programação: cidadãos de primeira classe (calma, não tem nada haver com dinheiro!) e escopo léxico.

Cidadãos de primeira classe:

Em Python, funções são cidadãos de primeira classe. Vejamos a definição da Wikipedia sobre este conceito:

In programming language design, a first-class citizen (also type, object, entity, or value) in a given programming language is an entity which supports all the operations generally available to other entities. These operations typically include being passed as an argument, returned from a function, modified, and assigned to a variable.

Dessa forma, observamos que, basicamente, funções em Python se comportam como qualquer outro objeto na linguagem (algo esperado, afinal, tudo em Python é um objeto!). Assim, podemos declarar funções em diversos lugares do código como fazemos com variáveis e, inclusive, podemos declarar funções dentro de outras! Podemos, também, passá-las como argumentos para outras funções bem como retorná-las!

Assim, podemos pensar em funções quase como valores pois podemos passar como parâmetro, declarar, retornar…

Um detalhe importante: funções que:

  • recebem funções como argumentos; ou
  • retornam outras funções

São denominadas como funções de ordem superior. Funções que não suprem tais condições são denominadas funções de primeira ordem.

PS: Uma curiosidade: este é um dos grandes “pilares” de programação funcional!

Escopo Léxico

De forma simples, podemos definir escopo como a região no programa onde um determinado binding existe e pode ser referenciado. Quando criamos uma variável em Python:

Criamos um binding (ligação) entre o identificador x e a entidade 2 (objeto tipo int). Assim, nesta região do programa, podemos utilizar o identificador x para referenciar seu objeto (o inteiro 2). Contudo, em outras regiões do programa que definem seus próprios escopos, o mesmo identificador x pode referenciar outro objeto ou até mesmo não referenciar nada. Assim, escopo pode ser pensado como uma região onde um certo binding é visível.

Python, como muitas outras linguagens, é implementada para utilizar escopo léxico que também é conhecido como escopo estático. Nesta implementação, as regiões onde os bindings (como as variáveis) são visíveis são definidas pela sua localização no código-fonte! Isto mesmo, ao digitar código Python, já estamos definindo escopos!

Vejamos o exemplo a seguir. Em Python, funções definem seu próprio escopo. Como sabemos que estes são definidos logo no código-fonte, podemos identificá-los apenas com uma análise rápida do código:

Ao analisar o binding do identificador x na linha 3, vemos que no escopo global, x referencia o objeto inteiro 2. Contudo, dentro da função fn, um novo escopo é definido no qual o identificador x referencia o objeto inteiro 10.

Já dentro da função fn2, não há uma redefinição do identificador x. Contudo, como fn2 foi definida dentro de um contexto estático (definido no código) onde x = 10, fn2 pode acessar o binding x que referencia o objeto inteiro 10. De forma análoga, fn pode acessar o binding z já que também foi definida em um contexto onde o identificador z já existia. Portanto, ao invocar fn, printamos o valor -1 que é acessível pois fn foi definida em um contexto onde z = -1 e invocamos fn2 que printa o valor 10 pois definida num contexto onde x = 10.

Assim, o interpretador sempre busca o binding de um identificador no escopo local da função, e caso não o encontre, busca no próximo escopo definido no código-fonte até chegar no escopo global. Caso o binding não seja encontrado em nenhum escopo, uma exceção será gerada.

Vemos, portanto, que o escopo de uma variável depende de sua localização no código-fonte e, assim, dizemos que o escopo de uma variável depende de um contexto léxico (estático).

Este comportamento é o oposto de linguagens que usam um escopo dinâmico (como em Lisp) cuja resolução de nomes dependerá do estado do programa (contexto de execução) e não do contexto estático do programa. Mas este assunto fica para uma postagem futura!

Vou encerrar este post por aqui uma vez que cobrimos dois tópicos muito teóricos e um pouco extensos! No próximo post, veremos como podemos utilizar o escopo léxico para criar closures bem como ver sua definição!

Em caso de dúvidas, erros ou sugestões, por favor entrem em contato! Discussões são sempre bem vindas!

Até o próximo post!

--

--