O que é um fechamento?

De vez em quando, vejo “encerramentos” sendo mencionados e tentei pesquisar, mas o Wiki não dá uma explicação que eu compreenda. Alguém poderia me ajuda aqui?

Comentários

  • Se você conhece Java / C #, espero que este link ajude- http://www.developerfusion.com/article/8251/the-beauty-of-closures/
  • Os fechamentos são difíceis de entender. Você deve tentar clicar em todos os links na primeira frase desse artigo da Wikipedia e entendê-los artigos primeiro.
  • stackoverflow.com/questions/36636/what-is-a-closure
  • O quê ‘ é a diferença fundamental entre um encerramento e uma classe? Ok, uma classe com apenas um método público.
  • @biziclop: Você poderia emular um encerramento com uma classe (que ‘ é o que os desenvolvedores Java devem fazer). Mas eles ‘ geralmente são um pouco menos prolixos para criar e você não não precisa gerenciar manualmente o que você ‘ está cuidando. (Os ouvintes radicais fazem uma pergunta semelhante, mas provavelmente chegam a outra conclusão – que o suporte OO em nível de linguagem é desnecessário quando você tem encerramentos).

Resposta

(Aviso: esta é uma explicação básica; no que diz respeito à definição, estou simplificando um pouco)

A maneira mais simples de pensar em um encerramento é uma função que pode ser armazenada como uma variável (referida como “primeiro -class function “), que tem uma habilidade especial para acessar outras variáveis locais para o escopo em que foi criado.

Exemplo (JavaScript):

 var setKeyPress = function(callback) { document.onkeypress = callback; }; var initialize = function() { var black = false; document.onclick = function() { black = !black; document.body.style.backgroundColor = black ? "#000000" : "transparent"; } var displayValOfBlack = function() { alert(black); } setKeyPress(displayValOfBlack); }; initialize();  

As funções 1 atribuídas a document.onclick e displayValOfBlack são encerramentos. Você pode ver que ambos fazem referência à variável booleana black, mas essa variável é atribuída fora da função. Porque black é local para o escopo onde a função foi definida , o ponteiro para esta variável é preservado.

Se você colocar isso em uma página HTML:

  1. Clique para mudar para preto
  2. Pressione [enter] para ver “verdadeiro”
  3. Clique novamente, muda para branco novamente
  4. Pressione [enter] para ver “false”

Isso demonstra que ambos têm acesso ao mesmo black, e pode ser usado para armazenar o estado sem qualquer objeto wrapper.

A chamada para setKeyPress é para demonstrar como uma função pode ser passada como qualquer variável. O escopo preservado no fechamento ainda é aquele em que a função foi definida.

Fechamentos são comumente usados como manipuladores de eventos, especialmente em JavaScript e ActionScript. O bom uso de encerramentos o ajudará a vincular variáveis implicitamente a manipuladores de eventos sem ter que criar um wrapper de objeto. No entanto, o uso descuidado levará a vazamentos de memória (como quando um manipulador de eventos não utilizado, mas preservado, é a única coisa a reter grandes objetos na memória, especialmente objetos DOM, evitando a coleta de lixo).


1: Na verdade, todas as funções em JavaScript são encerradas.

Comentários

  • Enquanto lia sua resposta, eu senti uma lâmpada acender em minha mente. Muito apreciado! 🙂
  • Visto que black é declarado dentro de uma função, não ‘ isso seria destruído conforme a pilha fosse desenrolada. ..?
  • @gablin, isso é o que há de único nas linguagens que têm fechamentos. Todas as linguagens com coleta de lixo funcionam da mesma maneira – quando não há mais referências a um objeto, ele pode ser destruído. Sempre que uma função é criada em JS, o escopo local é vinculado a essa função até que a função seja destruída.
  • @gablin, ‘ é uma boa pergunta. Não ‘ acho que eles podem ‘ t & mdash; mas eu só mencionei a coleta de lixo porque o JS usa e ‘ é o que você parecia estar se referindo quando disse ” Desde black é declarado dentro de uma função, não ‘ que seria destruído “. Lembre-se também de que se você declarar um objeto em uma função e, em seguida, atribuí-lo a uma variável que vive em outro lugar, esse objeto é preservado porque há outras referências a ele.
  • Objective-C (e C sob clang) suporta blocos, que são essencialmente encerramentos, sem coleta de lixo. Requer suporte de tempo de execução e alguma intervenção manual em torno do gerenciamento de memória.

Resposta

Um encerramento é basicamente apenas uma maneira diferente de olhar para um objeto. Um objeto são dados que possuem uma ou mais funções vinculadas a ele. Um encerramento é uma função que possui uma ou mais variáveis vinculadas a ela. Os dois são basicamente idênticos, pelo menos no nível de implementação. A diferença real está em de onde eles vêm.

Na programação orientada a objetos, você declara uma classe de objeto definindo suas variáveis de membro e seus métodos (funções de membro) antecipadamente e, em seguida, cria instâncias de essa classe. Cada instância vem com uma cópia dos dados do membro, inicializada pelo construtor. Você então tem uma variável de um tipo de objeto e a passa como um dado, porque o foco está em sua natureza como dados.

Em um encerramento, por outro lado, o objeto não é definido antecipadamente como uma classe de objeto ou instanciado por meio de uma chamada de construtor em seu código. Em vez disso, você escreve o encerramento como uma função dentro de outra função. O encerramento pode se referir a qualquer uma das variáveis locais da função externa, e o compilador detecta isso e move essas variáveis do espaço de pilha da função externa para a declaração de objeto oculto do encerramento. Você então tem uma variável de um tipo de encerramento , e mesmo que seja basicamente um objeto subjacente, você o passa como uma referência de função, porque o foco está em sua natureza como uma função.

Comentários

  • +1: Boa resposta. Você pode ver um fechamento como um objeto com apenas um método e um objeto arbitrário como uma coleção de fechamentos sobre alguns dados subjacentes comuns (as variáveis de membro ‘ do objeto). Acho que essas duas visões são bastante simétricas.
  • Muito boa resposta. Na verdade, explica a percepção do fechamento.
  • @Mason Wheeler: Onde os dados de fechamento são armazenados? Na pilha como uma função? Ou na pilha como um objeto?
  • @RoboAlex: Na pilha, porque ‘ é um objeto que se parece com uma função .
  • @RoboAlex: Onde um fechamento e seus dados capturados são armazenados depende da implementação. Em C ++, pode ser armazenado na pilha ou na pilha.

Resposta

O termo closure vem do fato de que um trecho de código (bloco, função) pode ter variáveis livres que são fechado (ou seja, vinculado a um valor) pelo ambiente no qual o bloco de código é definido.

Pegue por exemplo a definição da função Scala :

def addConstant(v: Int): Int = v + k 

No corpo da função existem dois nomes (variáveis) v e k indicando dois valores inteiros. O nome v é vinculado porque é declarado como um argumento da função addConstant (olhando para a declaração da função, sabemos que v receberá um valor quando a função for chamada). O nome k é livre para a função addConstant porque a função não contém nenhuma pista sobre qual valor k está vinculado a (e como).

Para avaliar uma chamada como:

val n = addConstant(10) 

temos que atribuir k um valor, que só pode acontecer se o nome k for definido no contexto em que addConstant está definido. Por exemplo:

def increaseAll(values: List[Int]): List[Int] = { val k = 2 def addConstant(v: Int): Int = v + k values.map(addConstant) } 

Agora que definimos addConstant em um contexto onde k é definido, addConstant tornou-se um encerramento porque todos suas variáveis livres agora são fechadas (vinculadas a um valor): addConstant podem ser invocado e transmitido como se fosse uma função. Observe que a variável livre k é vinculada a um valor quando o fechamento é definido , enquanto a variável de argumento v é limitada quando o fechamento é invocado .

Portanto, um encerramento é basicamente uma função ou bloco de código que pode acessar valores não locais por meio de suas variáveis livres após estes terem sido limitados pelo contexto.

Em muitas línguas, se use um encerramento apenas quando puder torná-lo anônimo , por exemplo,

def increaseAll(values: List[Int]): List[Int] = { val k = 2 values.map(v => v + k) } 

Observe que uma função sem variáveis livres é um caso especial de encerramento (com um conjunto vazio de variáveis livres). Analogamente, uma função anônima é um caso especial de fechamento anônimo , ou seja, uma função anônima é um encerramento anônimo sem variáveis livres.

Comentários

  • Isso combina bem com fórmulas fechadas e abertas em lógica. Obrigado pela sua resposta.
  • @RainDoctor: Variáveis livres são definidas em fórmulas lógicas e em expressões de cálculo lambda de maneira semelhante: o lambda em uma expressão lambda funciona como um quantificador em fórmulas lógicas sobre variáveis livres / limitadas .

Resposta

Uma explicação simples em JavaScript:

var closure_example = function() { var closure = 0; // after first iteration the value will not be erased from the memory // because it is bound with the returned alertValue function. return { alertValue : function() { closure++; alert(closure); } }; }; closure_example(); 

alert(closure) usará o valor criado anteriormente de closure. O namespace da função alertValue retornado será conectado ao namespace no qual a variável closure reside. Quando você exclui a função inteira, o valor da variável closure será excluído, mas até então, a função alertValue sempre será capaz de ler / gravar o valor da variável closure.

Se você executar este código, a primeira iteração atribuirá um valor 0 à variável closure e reescrever a função para:

var closure_example = function(){ alertValue : function(){ closure++; alert(closure); } } 

E porque alertValue precisa da variável local closure para executar a função, ela se associa ao valor da variável local previamente atribuída closure.

E agora, toda vez que você chamar o closure_example função, ele escreverá o valor incrementado da variável closure porque alert(closure) é vinculado.

closure_example.alertValue()//alerts value 1 closure_example.alertValue()//alerts value 2 closure_example.alertValue()//alerts value 3 //etc. 

Comentários

  • obrigado, não ‘ t teste o código =) tudo parece bem agora.

Resposta

Um “encerramento” é , em essência, algum estado local e algum código, combinados em um pacote. Normalmente, o estado local vem de um escopo circundante (lexical) e o código é (essencialmente) uma função interna que é então retornada para o exterior. O encerramento é então uma combinação das variáveis capturadas que a função interna vê e o código da função interna.

É uma daquelas coisas que, infelizmente, é um pouco difícil de explicar, devido a ser desconhecido.

Uma analogia que usei com sucesso no passado foi “imagine que temos algo que chamamos de” o livro “, no fechamento da sala,” o livro “é aquela cópia ali, no canto , do TAOCP, mas no fechamento da tabela, é aquela cópia de um livro dos Arquivos de Dresden. Portanto, dependendo do fechamento em que você está, o código “dê-me o livro” resulta em coisas diferentes acontecendo. “

Comentários

  • Você esqueceu isto: en.wikipedia.org/wiki/Closure_(computer_programming) em sua resposta.
  • Não, decidi conscientemente não fechar essa página.
  • ” Estado e função. “: Uma função C com uma static variável local pode ser considerada um encerramento? Os encerramentos em Haskell envolvem estado?
  • @Giorgio Closures em Haskell fecham (eu acredito) sobre os argumentos no escopo léxico que ‘ definem em, então, eu ‘ d diga ” sim ” (embora eu não esteja familiarizado com Haskell). A função AC com uma variável estática é, na melhor das hipóteses, um encerramento muito limitado (você realmente deseja poder criar vários encerramentos a partir de uma única função, com uma static variável local, você tem exatamente um).
  • Eu fiz esta pergunta propositalmente porque acho que uma função C com uma variável estática não é um encerramento: a variável estática é definida localmente e só é conhecida dentro do encerramento, ela não acessa o ambiente. Além disso, não tenho 100% de certeza, mas formularia sua declaração ao contrário: você usa o mecanismo de encerramento para criar funções diferentes (uma função é uma definição de encerramento + uma ligação para suas variáveis livres).

Resposta

É difícil definir o que é fechamento sem definir o conceito de “estado”.

Basicamente , em uma linguagem com escopo léxico completo que trata as funções como valores de primeira classe, algo especial acontece. Se eu fizesse algo como:

function foo(x) return x end x = foo 

A variável x não apenas faz referência function foo() mas também faz referência ao estado foo foi deixado na última vez que retornou. A verdadeira mágica acontece quando foo tem outras funções definidas em seu escopo; é como seu próprio mini-ambiente (assim como “normalmente” definimos funções em um ambiente global).

Funcionalmente, pode resolver muitos dos mesmos problemas que o C ++ (C?) palavra-chave “s” estática “, que retém o estado de uma variável local durante várias chamadas de função; no entanto, é mais como aplicar o mesmo princípio (variável estática) a uma função, já que as funções são valores de primeira classe; o fechamento adiciona suporte para que o estado da função inteira seja salvo (nada a ver com as funções estáticas do C ++).

Tratar funções como valores de primeira classe e adicionar suporte para encerramentos também significa que você pode ter mais de uma instância da mesma função na memória (semelhante às classes). Isso significa que você pode reutilizar o mesmo código sem ter que redefinir o estado da função, como é necessário ao lidar com variáveis estáticas C ++ dentro de uma função (pode estar errado sobre isso?).

Aqui estão alguns testes do suporte de encerramento de Lua .

--Closure testing --By Trae Barlow -- function myclosure() print(pvalue)--nil local pvalue = pvalue or 10 return function() pvalue = pvalue + 10 --20, 31, 42, 53(53 never printed) print(pvalue) pvalue = pvalue + 1 --21, 32, 43(pvalue state saved through multiple calls) return pvalue end end x = myclosure() --x now references anonymous function inside myclosure() x()--nil, 20 x() --21, 31 x() --32, 42 --43, 53 -- if we iterated x() again 

resultados:

nil 20 31 42 

Pode ser complicado e provavelmente varia de linguagem para linguagem, mas parece em Lua que sempre que uma função é executada, seu estado é zerado. Digo isso porque os resultados do código acima seriam diferentes se estivéssemos acessando o função / estado diretamente (em vez de por meio da função anônima que ele retorna), pois pvalue seria redefinido para 10; mas se acessarmos o estado de myclosure por meio de x (a função anônima), você verá que pvalue está vivo e bem em algum lugar da memória. Suspeito que haja um pouco mais, talvez alguém pode explicar melhor a natureza da implementação.

PS: Eu não sei uma pitada de C ++ 11 (além do que está nas versões anteriores), então observe que isso não é uma comparação entre fechamentos em C ++ 11 e Lua. Além disso, todas as “linhas desenhadas” de Lua a C ++ são semelhanças, pois as variáveis estáticas e os fechamentos não são 100% iguais; mesmo que às vezes sejam usados para resolver problemas semelhantes.

O que não tenho certeza é, no exemplo de código acima, se a função anônima ou a função de ordem superior é considerada o encerramento?

Resposta

Um closure é uma função que tem estado associado:

Em perl você cria closures assim:

#!/usr/bin/perl # This function creates a closure. sub getHelloPrint { # Bind state for the function we are returning. my ($first) = @_;a # The function returned will have access to the variable $first return sub { my ($second) = @_; print "$first $second\n"; }; } my $hw = getHelloPrint("Hello"); my $gw = getHelloPrint("Goodby"); &$hw("World"); // Print Hello World &$gw("World"); // PRint Goodby World 

Se olharmos para a nova funcionalidade fornecida com C ++.
Ela também permite que você vincule o estado atual ao objeto:

#include <string> #include <iostream> #include <functional> std::function<void(std::string const&)> getLambda(std::string const& first) { // Here we bind `first` to the function // The second parameter will be passed when we call the function return [first](std::string const& second) -> void { std::cout << first << " " << second << "\n"; }; } int main(int argc, char* argv[]) { auto hw = getLambda("Hello"); auto gw = getLambda("GoodBye"); hw("World"); gw("World"); } 

Resposta

Vamos considerar uma função simples:

function f1(x) { // ... something } 

Esta função é chamada de função de nível superior porque não está aninhada em nenhuma outra função. A cada A função JavaScript associa a si mesma uma lista de objetos chamada de ” Cadeia de escopo “. Essa cadeia de escopo é uma lista ordenada de objetos. E Cada um desses objetos define algumas variáveis.

Nas funções de nível superior, a cadeia de escopo consiste em um único objeto, o objeto global. Por exemplo, a função f1 acima tem uma cadeia de escopo que possui um único objeto que define todas as variáveis globais. (observe que o termo “objeto” aqui não significa objeto JavaScript, é apenas um objeto definido pela implementação que atua como um contêiner de variável, no qual o JavaScript pode “procurar” variáveis.)

Quando isso função é invocada, JavaScript cria algo chamado de “Objeto de ativação” e o coloca no topo da cadeia de escopo. objeto contém todas as variáveis locais (por exemplo x aqui). Portanto, agora temos dois objetos na cadeia de escopo: o primeiro é o objeto de ativação e abaixo dele está o objeto global.

Observe com muito cuidado que os dois objetos são colocados na cadeia de escopo em momentos DIFERENTES. O objeto global é colocado quando a função é definida (ou seja, quando JavaScript analisou a função e criou o objeto de função), e o o objeto de ativação entra quando a função é chamada.

Então, agora sabemos isso:

  • Cada função tem uma cadeia de escopo associada a ela
  • Quandoa função é definida (quando o objeto de função é criado), JavaScript salva uma cadeia de escopo com essa função
  • Para funções de nível superior, a cadeia de escopo contém apenas o objeto global no momento da definição da função e adiciona um objeto de ativação no topo no momento da invocação

A situação fica interessante quando lidamos com funções aninhadas. Então, vamos criar um:

function f1(x) { function f2(y) { // ... something } } 

Quando f1 é definido, obtemos uma cadeia de escopo contendo apenas o objeto global.

Agora, quando f1 é chamado, a cadeia de escopo de f1 obtém o objeto de ativação. Este objeto de ativação contém a variável x e a variável f2 que é uma função. E, observe que f2 está sendo definido.Portanto, neste ponto, o JavaScript também salva uma nova cadeia de escopo para f2. A cadeia de escopo salva para esta função interna é a cadeia de escopo atual em vigor. O escopo atual a cadeia em vigor é a de f1 “s. Portanto, f2” a cadeia de escopo é f1 “s corrente cadeia de escopo – que contém o objeto de ativação de f1 e o objeto global.

Quando f2 é chamado, “s obtém” seu próprio objeto de ativação contendo y, adicionado à sua cadeia de escopo que já contém o objeto de ativação de f1 e o objeto global.

Se houvesse outra função aninhada definida em f2, sua cadeia de escopo conteria três objetos no tempo de definição (2 objetos de ativação de duas funções externas e o objeto global) e 4 no tempo de invocação.

Então, agora nós entendemos e como a cadeia de escopo funciona, mas ainda não falamos sobre encerramentos.

A combinação de um objeto de função e um escopo (um conjunto de ligações variáveis) em que as variáveis da função são resolvidas é chamado de encerramento na literatura da ciência da computação – JavaScript o guia definitivo de David Flanagan

A maioria das funções são chamadas usando a mesma cadeia de escopo que estava em vigor quando a função foi definida e realmente não importa se há um encerramento envolvido. Os fechamentos se tornam interessantes quando são chamados em uma cadeia de escopo diferente daquela que estava em vigor quando foram definidos. Isso acontece mais comumente quando um objeto de função aninhado é retornado da função dentro da qual foi definido.

Quando a função retorna, aquele objeto de ativação é removido da cadeia de escopo. Se não houver funções aninhadas, não haverá mais referências ao objeto de ativação e ele será coletado pelo lixo. Se houver funções aninhadas definidas, então cada uma dessas funções tem uma referência à cadeia de escopo, e essa cadeia de escopo se refere ao objeto de ativação.

Se esses objetos de funções aninhadas permaneceram dentro de sua função externa, no entanto, então eles próprios serão coletados como lixo, junto com o objeto de ativação ao qual se referiram. Mas se a função definir uma função aninhada e retorná-la ou armazená-la em uma propriedade em algum lugar, haverá uma referência externa à função aninhada. Não será coletado como lixo, e o objeto de ativação a que se refere também não será coletado como lixo.

Em nosso exemplo acima, não retornamos f2 de f1, portanto, quando uma chamada para f1 retornar, seu objeto de ativação será removido de sua cadeia de escopo e o lixo coletado. Mas se tivéssemos algo assim:

function f1(x) { function f2(y) { // ... something } return f2; } 

Aqui, o f2 de retorno terá uma cadeia de escopo que conter o objeto de ativação de f1 e, portanto, não será coletado como lixo. Nesse ponto, se chamarmos f2, ele poderá acessar a f1 “variável x embora estejamos “fora de f1.

Portanto, podemos ver que uma função mantém sua cadeia de escopo com ela e com a cadeia de escopo vêm todos os objetos de ativação de funções externas. Essa é a essência do encerramento. Dizemos que as funções em JavaScript têm “escopo léxico” , o que significa que eles salvam o escopo que estava ativo quando foram definidos, em oposição ao escopo que estava ativo quando foram chamados.

Existem várias técnicas de programação poderosas que envolvem encerramentos, como aproximar variáveis privadas , programação orientada a eventos, aplicativo parcial , etc.

Observe também que tudo isso se aplica a todas as linguagens que suportam encerramentos. Por exemplo PHP (5.3+), Python, Ruby, etc.

Resposta

Um encerramento é uma otimização do compilador (também conhecida como açúcar sintático?) Algumas pessoas se referiram a isso como o Objeto do homem pobre também.

Veja a resposta de Eric Lippert : (trecho abaixo)

O compilador irá gerar código como este:

 private class Locals { public int count; public void Anonymous() { this.count++; } } public Action Counter() { Locals locals = new Locals(); locals.count = 0; Action counter = new Action(locals.Anonymous); return counter; }  

Faz sentido?
Além disso, você pediu comparações. VB e JScript criam fechamentos praticamente da mesma maneira.

Comentários

  • Esta resposta é CW porque eu não ‘ t mereço pontos para Eric ‘
  • -1: sua explicação está muito enraizada em C #. Fechamento é usado em muitas linguagens e é muito mais do que açúcar sintático nessas linguagens e abrange a função e o estado.
  • Não, um fechamento não é apenas um ” otimização do compilador ” nem açúcar sintático. -1

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *