De vez em quando eu vejo "fechamentos" sendo mencionados, e tentei procurar, mas o Wiki não dá uma explicação que eu entenda. Alguém poderia me ajudar aqui?
De vez em quando eu vejo "fechamentos" sendo mencionados, e tentei procurar, mas o Wiki não dá uma explicação que eu entenda. Alguém poderia me ajudar aqui?
Respostas:
(Isenção de responsabilidade: esta é uma explicação básica; no que diz respeito à definição, estou simplificando um pouco)
A maneira mais simples de pensar em um fechamento é uma função que pode ser armazenada como uma variável (denominada "função de primeira classe"), que possui uma capacidade especial de acessar outras variáveis locais do 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 document.onclick
e displayValOfBlack
são encerramentos. Você pode ver que os dois referenciam a variável booleana black
, mas essa variável é atribuída fora da função. Como black
é local no escopo em que a função foi definida , o ponteiro para essa variável é preservado.
Se você colocar isso em uma página HTML:
Isso demonstra que ambos têm acesso ao mesmo black
e podem ser usados para armazenar o estado sem nenhum objeto de wrapper.
A chamada para setKeyPress
é 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.
Os fechamentos são comumente usados como manipuladores de eventos, especialmente em JavaScript e ActionScript. O bom uso de encerramentos ajudará você a vincular implicitamente variáveis a manipuladores de eventos sem precisar 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 manter objetos grandes na memória, especialmente objetos DOM, impedindo a coleta de lixo).
1: Na verdade, todas as funções em JavaScript são encerramentos.
black
é declarado dentro de uma função, isso não seria destruído à medida que a pilha se desenrola ...?
black
é declarado dentro de uma função, isso não seria destruído". Lembre-se também de que, se você declarar um objeto em uma função e depois atribuí-lo a uma variável que mora em outro lugar, esse objeto será preservado porque há outras referências a ele.
Um fechamento é 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 fechamento é uma função que possui uma ou mais variáveis ligadas a ele. Os dois são basicamente idênticos, pelo menos em um nível de implementação. A verdadeira diferença está em 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 dessa 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 transmite como um dado, porque o foco é sua natureza como dados.
Em um fechamento, 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 fechamento como uma função dentro de outra função. O fechamento pode se referir a qualquer variável local 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 fechamento. Você então tem uma variável de um tipo de fechamento e, embora seja basicamente um objeto sob o capô, transmite-o como uma referência de função, porque o foco é sua natureza como uma função.
O termo encerramento vem do fato de que um trecho de código (bloco, função) pode ter variáveis livres que são fechadas (ou seja, vinculadas a um valor) pelo ambiente em que o bloco de código está definido.
Tomemos, 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
indicam dois valores inteiros. O nome v
é vinculado porque é declarado como um argumento da função addConstant
(observando a declaração da função, sabemos que v
será atribuído 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 a qual valor k
está vinculado (e como).
Para avaliar uma chamada como:
val n = addConstant(10)
temos que atribuir k
um valor, o que só pode acontecer se o nome k
estiver 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 em que k
é definido, addConstant
tornou-se um fechamento porque todas as suas variáveis livres agora estão fechadas (vinculadas a um valor): addConstant
podem ser invocadas e passadas 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
é vinculada quando o fechamento é invocado .
Portanto, um fechamento é basicamente uma função ou bloco de código que pode acessar valores não locais por meio de suas variáveis livres depois que elas são vinculadas pelo contexto.
Em muitos idiomas, se você usar um fechamento apenas uma vez, poderá 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 fechamento (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 fechamento anônimo sem variáveis livres.
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 alertValue
espaço de nome da função retornada será conectado ao espaço de nome no qual a closure
variável reside. Quando você exclui toda a função, o valor da closure
variável será excluído, mas até então, a alertValue
função sempre poderá ler / gravar o valor da variável closure
.
Se você executar esse código, a primeira iteração atribuirá um valor 0 à closure
variável 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 liga ao valor da variável local atribuída anteriormente closure
.
E agora, toda vez que você chama a closure_example
função, ela grava o valor incrementado da closure
variável porque alert(closure)
está vinculada.
closure_example.alertValue()//alerts value 1
closure_example.alertValue()//alerts value 2
closure_example.alertValue()//alerts value 3
//etc.
Um "fechamento" é, 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 é retornada para fora. O fechamento é então uma combinação das variáveis capturadas que a função interna vê e o código da função interna.
Infelizmente, é uma daquelas coisas que é um pouco difícil de explicar, por não ser familiar.
Uma analogia que usei com sucesso no passado foi "imagine que temos algo que chamamos de 'livro', no fechamento da sala, 'o livro' é aquela cópia ali, no canto, do TAOCP, mas no fechamento da mesa , é a cópia de um livro do Dresden Files. Portanto, dependendo de qual encerramento você está, o código 'me dê o livro' resulta em diferentes coisas acontecendo ".
static
variável local pode ser considerada um fechamento? Os fechamentos em Haskell envolvem estado?
static
variável local, você tem exatamente um).
É difícil definir o que é o fechamento sem definir o conceito de "estado".
Basicamente, em uma linguagem com escopo lexical 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 que foo
foi deixado na última vez que retornou. A mágica real acontece quando foo
há outras funções definidas mais dentro de seu escopo; é como seu próprio mini-ambiente (assim como 'normalmente' definimos funções em um ambiente global).
Funcionalmente, ele pode resolver muitos dos mesmos problemas que a palavra-chave 'estática' do C ++ (C?), Que retém o estado de uma variável local através de várias chamadas de função; no entanto, é mais como aplicar o mesmo princípio (variável estática) a uma função, pois as funções são valores de primeira classe; O encerramento adiciona suporte para que todo o estado da função seja salvo (nada a ver com as funções estáticas do C ++).
Tratar funções como valores de primeira classe e adicionar suporte a fechamentos também significa que você pode ter mais de uma instância da mesma função na memória (semelhante às classes). O que isso significa é que você pode reutilizar o mesmo código sem precisar redefinir o estado da função, conforme é necessário ao lidar com variáveis estáticas do C ++ dentro de uma função (pode estar errado sobre isso?).
Aqui estão alguns testes do suporte ao fechamento 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 idioma para idioma, mas parece em Lua que sempre que uma função é executada, seu estado é redefinido. Digo isso porque os resultados do código acima seriam diferentes se estivéssemos acessando a myclosure
função / estado diretamente (em vez de retornar pela função anônima), pois pvalue
seria redefinido para 10; mas se acessarmos o estado do myclosure por meio de x (a função anônima), você poderá ver que ele pvalue
está vivo e bom em algum lugar da memória. Suspeito que exista um pouco mais, talvez alguém possa explicar melhor a natureza da implementação.
PS: Eu não conheço uma lambida de C ++ 11 (além do que está nas versões anteriores), portanto, observe que essa não é uma comparação entre fechamentos em C ++ 11 e Lua. Além disso, todas as 'linhas desenhadas' de Lua para C ++ são semelhanças, pois variáveis estáticas e fechamentos não são 100% iguais; mesmo que sejam usados algumas vezes para resolver problemas semelhantes.
O que eu 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 fechamento?
Um fechamento é uma função que tem estado associado:
No perl, você cria fechamentos como este:
#!/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 o C ++.
Também permite vincular 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");
}
Vamos considerar uma função simples:
function f1(x) {
// ... something
}
Essa função é chamada de função de nível superior porque não está aninhada em nenhuma outra função. Toda função JavaScript associa consigo uma lista de objetos chamada "Cadeia de escopo" . Essa cadeia de escopo é uma lista ordenada de objetos. 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 possui 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 para implementação que atua como um contêiner de variável, no qual o JavaScript pode "procurar" variáveis.)
Quando essa função é chamada, o JavaScript cria algo chamado "objeto de ativação" e o coloca no topo da cadeia de escopo. Este 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, 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 o JavaScript analisa a função e cria o objeto da função) e o objeto de ativação entra quando a função é chamada.
Então, agora sabemos isso:
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 f1
recebe 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 f2
. A cadeia de escopo salva para esta função interna é a atual cadeia de escopo em vigor. A cadeia de escopo atual em vigor é a de f1
's. Portanto f2
, a cadeia de escopo é f1
a cadeia de escopo atual - que contém o objeto de ativação f1
e o objeto global.
Quando f2
é chamado, ele obtém seu próprio objeto de ativação y
, adicionado à sua cadeia de escopo, que já contém o objeto de ativação f1
e o objeto global.
Se houvesse outra função aninhada definida dentro f2
, sua cadeia de escopo conteria três objetos no momento da definição (2 objetos de ativação de duas funções externas e o objeto global) e 4 no momento da chamada.
Então, agora entendemos como a cadeia de escopo funciona, mas ainda não falamos sobre fechamentos.
A combinação de um objeto de função e um escopo (um conjunto de ligações de variáveis) no qual as variáveis da função são resolvidas é chamada de fechamento na literatura de ciência da computação - JavaScript, o guia definitivo de David Flanagan
A maioria das funções é chamada usando a mesma cadeia de escopo que estava em vigor quando a função foi definida, e não importa realmente se há um fechamento envolvido. Os fechamentos tornam-se interessantes quando são chamados sob uma cadeia de escopo diferente daquela que estava em vigor quando foram definidos. Isso acontece mais comumente quando um objeto de função aninhada é retornado da função na qual ele foi definido.
Quando a função retorna, esse 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 como lixo. Se houvesse funções aninhadas definidas, cada uma dessas funções terá 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 permanecerem dentro de sua função externa, eles mesmos serão coletados como lixo, juntamente com o objeto de ativação a que se referiram. Mas se a função define uma função aninhada e a retorna ou a armazena em uma propriedade em algum lugar, haverá uma referência externa à função aninhada. Ele não será coletado como lixo e o objeto de ativação a que se refere também não será coletado.
No nosso exemplo, acima, não voltar f2
a partir de f1
, por isso, quando uma chamada de f1
retorno, o seu objecto de activação será removido a partir do seu âmbito e cadeia de lixo recolhido. Mas se tivéssemos algo assim:
function f1(x) {
function f2(y) {
// ... something
}
return f2;
}
Aqui, o retorno f2
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 f1
a variável de x
mesmo que estejamos sem 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 fechamento. Dizemos que as funções no JavaScript têm "escopo lexical " , o que significa que elas salvam o escopo que estava ativo quando elas foram definidas, em oposição ao escopo que estava ativo quando foram chamadas.
Existem várias técnicas de programação poderosas que envolvem fechamentos, como aproximar variáveis privadas, programação orientada a eventos, aplicação parcial etc.
Observe também que tudo isso se aplica a todos os idiomas que suportam fechamentos. Por exemplo, PHP (5.3+), Python, Ruby, etc.
Um fechamento é uma otimização de compilador (também conhecido como açúcar sintático?). Algumas pessoas também se referiram a isso como o objeto do pobre homem .
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. O VB e o JScript criam fechamentos praticamente da mesma maneira.