Fiz uma pergunta sobre curry e os fechamentos foram mencionados. O que é um fechamento? Como isso se relaciona com o curry?
432
Fiz uma pergunta sobre curry e os fechamentos foram mencionados. O que é um fechamento? Como isso se relaciona com o curry?
Respostas:
Escopo variável
Quando você declara uma variável local, essa variável tem um escopo. Geralmente, as variáveis locais existem apenas dentro do bloco ou função em que você as declara.
Se eu tentar acessar uma variável local, a maioria dos idiomas procurará por ela no escopo atual, passando pelos escopos principais até atingirem o escopo raiz.
Quando um bloco ou função termina, suas variáveis locais não são mais necessárias e geralmente ficam sem memória.
É assim que normalmente esperamos que as coisas funcionem.
Um fechamento é um escopo variável local persistente
Um fechamento é um escopo persistente que se apega a variáveis locais, mesmo após a execução do código sair desse bloco. Os idiomas que suportam o fechamento (como JavaScript, Swift e Ruby) permitirão que você mantenha uma referência a um escopo (incluindo seus escopos pai), mesmo após a conclusão da execução do bloco em que essas variáveis foram declaradas, desde que você mantenha uma referência para esse bloco ou função em algum lugar.
O objeto de escopo e todas as suas variáveis locais estão vinculadas à função e persistirão enquanto essa função persistir.
Isso nos dá portabilidade de função. Podemos esperar que todas as variáveis que estavam no escopo quando a função foi definida pela primeira vez ainda estejam no escopo quando mais tarde chamarmos a função, mesmo se a chamarmos em um contexto completamente diferente.
Por exemplo
Aqui está um exemplo muito simples em JavaScript que ilustra o ponto:
Aqui eu defini uma função dentro de uma função. A função interna obtém acesso a todas as variáveis locais da função externa, inclusive
a
. A variávela
está no escopo da função interna.Normalmente, quando uma função sai, todas as suas variáveis locais são exageradas. No entanto, se retornarmos a função interna e a atribuirmos a uma variável
fnc
para que ela persista após aouter
saída, todas as variáveis que estavam no escopo quandoinner
definidas também persistem . A variávela
foi encerrada - está dentro de um fechamento.Observe que a variável
a
é totalmente privada parafnc
. Essa é uma maneira de criar variáveis privadas em uma linguagem de programação funcional como o JavaScript.Como você pode adivinhar, quando eu chamo
fnc()
, imprime o valor dea
, que é "1".Em um idioma sem fechamento, a variável
a
teria sido coletada e descartada como lixo quando a função fosseouter
encerrada. Chamar fnc geraria um erro porquea
não existe mais.Em JavaScript, a variável
a
persiste porque o escopo da variável é criado quando a função é declarada pela primeira vez e persiste enquanto a função continuar a existir.a
pertence ao escopo deouter
. O escopo deinner
possui um ponteiro pai para o escopo deouter
.fnc
é uma variável que aponta parainner
.a
persiste enquantofnc
persiste.a
está dentro do fechamento.fonte
Vou dar um exemplo (em JavaScript):
O que essa função, makeCounter, faz é retornar uma função, que chamamos de x, que será contada uma vez cada vez que for chamada. Como não fornecemos nenhum parâmetro para x, ele deve se lembrar da contagem. Ele sabe onde encontrá-lo com base no que é chamado de escopo lexical - ele deve procurar o local onde está definido para encontrar o valor. Esse valor "oculto" é chamado de encerramento.
Aqui está o meu exemplo de curry novamente:
O que você pode ver é que, quando você chama add com o parâmetro a (que é 3), esse valor está contido no fechamento da função retornada que estamos definindo como add3. Dessa forma, quando chamamos add3, ele sabe onde encontrar o valor a para realizar a adição.
fonte
A resposta de Kyle é muito boa. Eu acho que o único esclarecimento adicional é que o fechamento é basicamente um instantâneo da pilha no momento em que a função lambda é criada. Então, quando a função é reexecutada, a pilha é restaurada para esse estado antes de executar a função. Assim, como Kyle menciona, esse valor oculto (
count
) está disponível quando a função lambda é executada.fonte
Antes de tudo, ao contrário do que muitas pessoas aqui dizem, o fechamento não é uma função ! Então o que é isso?
É um conjunto de símbolos definidos no "contexto circundante" de uma função (conhecido como ambiente ) que a torna uma expressão FECHADA (ou seja, uma expressão na qual todo símbolo é definido e tem um valor, para que possa ser avaliado).
Por exemplo, quando você tem uma função JavaScript:
é uma expressão fechada porque todos os símbolos que nele ocorrem são definidos (seus significados são claros), para que você possa avaliá-lo. Em outras palavras, é independente .
Mas se você tem uma função como esta:
é uma expressão aberta porque há símbolos nela que não foram definidos nela. Ou seja
y
,. Ao olhar para essa função, não podemos dizer o quey
é e o que isso significa, não sabemos seu valor, portanto, não podemos avaliar essa expressão. Ou seja, não podemos chamar essa função até dizermos o quey
deveria significar nela. Issoy
é chamado de variável livre .Isso
y
implora por uma definição, mas essa definição não faz parte da função - ela é definida em outro lugar, em seu "contexto circundante" (também conhecido como ambiente ). Pelo menos é o que esperamos: PPor exemplo, ele pode ser definido globalmente:
Ou pode ser definido em uma função que a envolve:
A parte do ambiente que dá às variáveis livres de uma expressão seus significados é o fechamento . É chamado assim, porque transforma uma expressão aberta em uma fechada , fornecendo essas definições ausentes para todas as suas variáveis livres , para que possamos avaliá-la.
No exemplo acima, a função interna (que não demos um nome porque não precisamos dela) é uma expressão aberta porque a variável
y
é livre - sua definição está fora da função, na função que a envolve . O ambiente para essa função anônima é o conjunto de variáveis:Agora, o fechamento é a parte desse ambiente que fecha a função interna, fornecendo as definições para todas as suas variáveis livres . No nosso caso, a única variável livre na função interna era
y
, portanto, o fechamento dessa função é esse subconjunto de seu ambiente:Os outros dois símbolos definidos no ambiente não fazem parte do fechamento dessa função, porque não exige que eles sejam executados. Eles não são necessários para fechá- lo.
Mais sobre a teoria por trás disso aqui: https://stackoverflow.com/a/36878651/434562
Vale ressaltar que no exemplo acima, a função wrapper retorna sua função interna como um valor. O momento em que chamamos essa função pode ser remoto no tempo a partir do momento em que a função foi definida (ou criada). Em particular, sua função de quebra automática não está mais em execução e seus parâmetros que estão na pilha de chamadas não estão mais lá: P Isso causa um problema, porque a função interna precisa
y
estar lá quando é chamada! Em outras palavras, requer que as variáveis do seu fechamento sobrevivam de alguma forma à função do wrapper e estejam lá quando necessário. Portanto, a função interna precisa fazer um instantâneo dessas variáveis que tornam seu fechamento e armazená-las em algum lugar seguro para uso posterior. (Em algum lugar fora da pilha de chamadas.)E é por isso que as pessoas freqüentemente confundem o termo encerramento como aquele tipo especial de função que pode fazer instantâneos das variáveis externas que eles usam, ou da estrutura de dados usada para armazenar essas variáveis para mais tarde. Mas espero que você entenda agora que eles não são o fechamento propriamente dito - são apenas maneiras de implementar fechamentos em uma linguagem de programação ou mecanismos de linguagem que permitem que as variáveis do fechamento da função estejam lá quando necessário. Existem muitos conceitos errados sobre fechamentos que (desnecessariamente) tornam esse assunto muito mais confuso e complicado do que realmente é.
fonte
Um fechamento é uma função que pode referenciar o estado em outra função. Por exemplo, em Python, isso usa o fechamento "inner":
fonte
Para ajudar a facilitar o entendimento dos fechamentos, pode ser útil examinar como eles podem ser implementados em uma linguagem processual. Esta explicação seguirá uma implementação simplista de fechamentos no esquema.
Para começar, devo introduzir o conceito de um espaço para nome. Quando você insere um comando em um intérprete de esquema, ele deve avaliar os vários símbolos na expressão e obter seu valor. Exemplo:
As expressões define armazenam o valor 3 no local para x e o valor 4 no local para y. Então, quando chamamos (+ xy), o intérprete pesquisa os valores no espaço para nome e pode executar a operação e retornar 7.
No entanto, no esquema, existem expressões que permitem substituir temporariamente o valor de um símbolo. Aqui está um exemplo:
O que a palavra-chave let faz é introduzir um novo espaço para nome com x como o valor 5. Você perceberá que ainda é possível ver que y é 4, fazendo com que a soma retornada seja 9. Você também pode ver que quando a expressão termina x voltou a ser 3. Nesse sentido, x foi temporariamente mascarado pelo valor local.
Linguagens procedurais e orientadas a objetos têm um conceito semelhante. Sempre que você declara uma variável em uma função que tem o mesmo nome que uma variável global, obtém o mesmo efeito.
Como implementaríamos isso? Uma maneira simples é com uma lista vinculada - a cabeça contém o novo valor e a cauda contém o antigo espaço para nome. Quando você precisa procurar um símbolo, começa na cabeça e desce pela cauda.
Agora vamos pular para a implementação de funções de primeira classe no momento. Mais ou menos, uma função é um conjunto de instruções a serem executadas quando a função é chamada culminando no valor de retorno. Quando lemos uma função, podemos armazenar essas instruções nos bastidores e executá-las quando a função é chamada.
Definimos x como 3 e mais-x como seu parâmetro, y, mais o valor de x. Finalmente, chamamos plus-x em um ambiente em que x foi mascarado por um novo x, este valorizado 5. Se apenas armazenarmos a operação (+ xy), para a função plus-x, já que estamos no contexto de x sendo 5, o resultado retornado seria 9. Isso é chamado de escopo dinâmico.
No entanto, Scheme, Common Lisp e muitas outras linguagens têm o que se chama escopo lexical - além de armazenar a operação (+ xy), também armazenamos o espaço para nome nesse ponto específico. Dessa forma, quando procuramos os valores, podemos ver que x, nesse contexto, é realmente 3. Esse é um fechamento.
Em resumo, podemos usar uma lista vinculada para armazenar o estado do espaço para nome no momento da definição da função, permitindo acessar variáveis de escopos anexos, além de fornecer a capacidade de mascarar localmente uma variável sem afetar o restante do programa.
fonte
Why do we want to access variables that are out of scope? when we say let x = 5, we want x to be 5 and not 3. What is happening?
Aqui está um exemplo do mundo real do porquê Closures kick ass ... Isso é direto do meu código Javascript. Deixe-me ilustrar.
E aqui está como você o usaria:
Agora imagine que você deseja que a reprodução comece atrasada, como, por exemplo, 5 segundos depois que esse trecho de código for executado. Bem, isso é fácil
delay
e seu fechamento:Quando você chama
delay
com5000
ms, o primeiro trecho é executado e armazena os argumentos passados em seu fechamento. Então, 5 segundos depois, quando osetTimeout
retorno de chamada acontece, o fechamento ainda mantém essas variáveis, para que ele possa chamar a função original com os parâmetros originais.Este é um tipo de curry ou decoração funcional.
Sem encerramentos, você teria que, de alguma forma, manter o estado dessas variáveis fora da função, desarrumando o código fora da função com algo que pertence logicamente a ela. O uso de fechamentos pode melhorar muito a qualidade e a legibilidade do seu código.
fonte
Funções que não contêm variáveis livres são chamadas de funções puras.
Funções que contêm uma ou mais variáveis livres são chamadas de fechamentos.
src: https://leanpub.com/javascriptallongesix/read#leanpub-auto-if-functions-without-free-variables-are-pure-are-closures-impure
fonte
tl; dr
Um fechamento é uma função e seu escopo atribuído a (ou usado como) uma variável. Assim, o fechamento do nome: o escopo e a função são incluídos e usados como qualquer outra entidade.
Explicação detalhada do estilo Wikipedia
Segundo a Wikipedia, um fechamento é:
O que isso significa? Vamos analisar algumas definições.
Explicarei os fechamentos e outras definições relacionadas usando este exemplo:
Funções de primeira classe
Basicamente, isso significa que podemos usar funções como qualquer outra entidade . Podemos modificá-los, passá-los como argumentos, retorná-los de funções ou atribuí-los a variáveis. Tecnicamente falando, eles são cidadãos de primeira classe , daí o nome: funções de primeira classe.
No exemplo acima,
startAt
retorna uma função ( anônima ) à qual a função é atribuída aclosure1
eclosure2
. Então, como você vê, o JavaScript trata as funções como qualquer outra entidade (cidadãos de primeira classe).Ligação de nome
A associação de nomes é descobrir quais dados uma variável (identificador) faz referência . O escopo é realmente importante aqui, pois é isso que determinará como uma ligação é resolvida.
No exemplo acima:
y
é vinculado a3
.startAt
escopo do,x
está vinculado a1
ou5
(dependendo do fechamento).Dentro do escopo da função anônima,
x
não está vinculado a nenhum valor; portanto, ele precisa ser resolvido nostartAt
escopo superior ( s).Escopo lexical
Como a Wikipedia diz , o escopo:
Existem duas técnicas:
Para mais explicações, confira esta pergunta e dê uma olhada na Wikipedia .
No exemplo acima, podemos ver que o JavaScript tem um escopo lexical, porque quando
x
é resolvida, a ligação é pesquisada nostartAt
escopo superior ( s), com base no código-fonte (a função anônima que procura x é definida por dentrostartAt
) e não com base na pilha de chamadas, a maneira (o escopo em que) a função foi chamada.Empacotando (fechando)
Em nosso exemplo, quando chamamos
startAt
, ele retornará uma função (de primeira classe) que será atribuídaclosure1
e,closure2
assim, um fechamento será criado, porque as variáveis passadas1
e5
serão salvas nostartAt
escopo do que será incluído com o retorno função anônima. Quando chamamos essa função anônima viaclosure1
eclosure2
com o mesmo argumento (3
), o valor dey
será encontrado imediatamente (como esse é o parâmetro dessa função), masx
não está vinculado ao escopo da função anônima, portanto a resolução continua em o escopo da função superior (lexicamente) (que foi salvo no fechamento), ondex
está associado a um1
ou5
. Agora sabemos tudo sobre o somatório para que o resultado possa ser retornado e impresso.Agora você deve entender os fechamentos e como eles se comportam, o que é uma parte fundamental do JavaScript.
Escovando
Ah, e você também aprendeu do que se trata o curry : você usa funções (fechamentos) para passar cada argumento de uma operação em vez de usar uma função com vários parâmetros.
fonte
Closure é um recurso no JavaScript em que uma função tem acesso a suas próprias variáveis de escopo, acesso às variáveis de função externas e acesso às variáveis globais.
O fechamento tem acesso ao seu escopo de função externa, mesmo após o retorno da função externa. Isso significa que um fechamento pode lembrar e acessar variáveis e argumentos de sua função externa, mesmo após a conclusão da função.
A função interna pode acessar as variáveis definidas em seu próprio escopo, o escopo da função externa e o escopo global. E a função externa pode acessar a variável definida em seu próprio escopo e no escopo global.
Exemplo de fechamento :
A saída será 20, que soma da variável própria da função interna, variável da função externa e valor da variável global.
fonte
Em uma situação normal, as variáveis são vinculadas pela regra de escopo: Variáveis locais funcionam apenas dentro da função definida. O fechamento é uma maneira de quebrar esta regra temporariamente por conveniência.
no código acima,
lambda(|n| a_thing * n}
é o fechamento porquea_thing
é referido pelo lambda (um criador de função anônimo).Agora, se você colocar a função anônima resultante em uma variável de função.
foo quebrará a regra de escopo normal e começará a usar 4 internamente.
retorna 12.
fonte
Em resumo, o ponteiro de função é apenas um ponteiro para um local na base de código do programa (como o contador do programa). Considerando o encerramento = ponteiro de função + quadro de pilha .
.
fonte
• Um fechamento é um subprograma e o ambiente de referência em que foi definido
- O ambiente de referência é necessário se o subprograma puder ser chamado de qualquer local arbitrário do programa
- Uma linguagem com escopo estático que não permite subprogramas aninhados não precisa de fechamentos
- Os fechamentos são necessários apenas se um subprograma puder acessar variáveis em escopos de aninhamento e puder ser chamado de qualquer lugar
- Para oferecer suporte a fechamentos, uma implementação pode precisar fornecer extensão ilimitada para algumas variáveis (porque um subprograma pode acessar uma variável não local que normalmente não está mais ativa)
Exemplo
fonte
Aqui está outro exemplo da vida real, e usando uma linguagem de script popular em jogos - Lua. Eu precisava alterar levemente a maneira como uma função de biblioteca funcionava para evitar um problema com o stdin não estar disponível.
O valor de old_dofile desaparece quando esse bloco de código termina seu escopo (porque é local); no entanto, o valor foi encerrado em um fechamento; portanto, a nova função de dofile redefinida PODE acessá-lo, ou melhor, uma cópia armazenada junto com a função como um 'upvalue'.
fonte
Do Lua.org :
fonte
Se você é do mundo Java, pode comparar um fechamento com uma função de membro de uma classe. Veja este exemplo
A função
g
é um fechamento:g
fechaa
. Portanto,g
pode ser comparada com uma função de membro,a
pode ser comparada com um campo de classe e a funçãof
com uma classe.fonte
Encerramentos Sempre que tivermos uma função definida dentro de outra, a função interna terá acesso às variáveis declaradas na função externa. Os fechamentos são melhor explicados com exemplos. Na Listagem 2-18, você pode ver que a função interna tem acesso a uma variável (variableInOuterFunction) do escopo externo. As variáveis na função externa foram fechadas (ou ligadas) pela função interna. Daí o termo encerramento. O conceito em si é bastante simples e bastante intuitivo.
fonte: http://index-of.es/Varios/Basarat%20Ali%20Syed%20(auth.)-Beginning%20Node.js-Apress%20(2014).pdf
fonte
Dê uma olhada no código abaixo para entender o fechamento mais profundamente:
Aqui o que será produzido?
0,1,2,3,4
não que seja5,5,5,5,5
por causa do fechamentoEntão, como vai resolver? A resposta está abaixo:
Deixe-me explicar de maneira simples, quando uma função criada nada acontece até que ela chame o loop for no 1º código chamado 5 vezes, mas não seja chamada imediatamente quando chama, ou seja, após 1 segundo e também é assíncrona, portanto antes que o loop for concluído e o valor de armazenamento 5 na var ie finalmente execute a
setTimeout
função cinco vezes e imprima5,5,5,5,5
Aqui como resolver usando IIFE, isto é, expressão de função de chamada imediata
Para mais, entenda o contexto de execução para entender o fechamento.
Existe mais uma solução para resolver isso usando let (recurso ES6), mas sob o capô a função acima é trabalhada
=> Mais explicações:
Na memória, quando for loop executar imagem faça o seguinte:
Loop 1)
Loop 2)
Loop 3)
Loop 4)
Loop 5)
Aqui eu não sou executado e depois do loop completo, vari armazenou o valor 5 na memória, mas seu escopo é sempre visível na função filhos, então quando a função é executada de dentro
setTimeout
para fora cinco vezes5,5,5,5,5
para resolver esse uso, use IIFE como explicado acima.
fonte
Currying: permite avaliar parcialmente uma função passando apenas um subconjunto de seus argumentos. Considere isto:
Encerramento: um fechamento nada mais é do que acessar uma variável fora do escopo de uma função. É importante lembrar que uma função dentro de uma função ou uma função aninhada não é um fechamento. Os fechamentos são sempre usados quando é necessário acessar as variáveis fora do escopo da função.
fonte
O fechamento é muito fácil. Podemos considerar o seguinte: Encerramento = função + seu ambiente lexical
Considere a seguinte função:
Qual será o fechamento no caso acima? Função init () e variáveis em seu ambiente lexical, ou seja, nome. Encerramento = init () + nome
Considere outra função:
Quais serão os fechamentos aqui? A função interna pode acessar variáveis da função externa. displayName () pode acessar o nome da variável declarada na função pai, init (). No entanto, as mesmas variáveis locais em displayName () serão usadas se existirem.
Encerramento 1: função init + (variável de nome + função displayName ()) -> escopo lexical
Fechamento 2: função displayName + (variável de nome) -> escopo lexical
fonte
Os fechamentos fornecem estado ao JavaScript.
Estado na programação significa simplesmente lembrar as coisas.
Exemplo
No caso acima, o estado é armazenado na variável "a". Em seguida, adicionamos 1 a "a" várias vezes. Só podemos fazer isso porque somos capazes de "lembrar" o valor. O detentor do estado, "a", mantém esse valor na memória.
Freqüentemente, nas linguagens de programação, você deseja acompanhar as coisas, lembrar as informações e acessá-las posteriormente.
Este, em outros idiomas , é comumente realizada através do uso de classes. Uma classe, assim como as variáveis, controla seu estado. E instâncias dessa classe, por sua vez, também têm estado dentro delas. Estado simplesmente significa informações que você pode armazenar e recuperar posteriormente.
Exemplo
Como podemos acessar o "peso" de dentro do método "render"? Bem, graças ao estado. Cada instância da classe Bread pode render seu próprio peso lendo-o no "state", um local na memória onde podemos armazenar essas informações.
Agora, o JavaScript é uma linguagem muito única que, historicamente, não possui classes (mas agora existem apenas funções e variáveis), portanto o Closures fornece uma maneira do JavaScript lembrar as coisas e acessá-las posteriormente.
Exemplo
O exemplo acima alcançou o objetivo de "manter o estado" com uma variável. Isso é ótimo! No entanto, isso tem a desvantagem de que a variável (o detentor do "estado") está agora exposta. Nós podemos fazer melhor. Podemos usar Closures.
Exemplo
Isto é fantástico.
Agora nossa função "count" pode contar. Só é capaz de fazê-lo porque pode "manter" o estado. O estado neste caso é a variável "n". Esta variável está agora fechada. Fechado no tempo e no espaço. Com o tempo, porque você nunca poderá recuperá-lo, alterá-lo, atribuir um valor ou interagir diretamente com ele. No espaço, porque está aninhado geograficamente na função "countGenerator".
Por que isso é fantástico? Porque, sem envolver nenhuma outra ferramenta sofisticada e complicada (por exemplo, classes, métodos, instâncias, etc.), somos capazes de 1. ocultar 2. o controle à distância
Nós escondemos o estado, a variável "n", o que a torna uma variável privada! Também criamos uma API que pode controlar essa variável de uma maneira predefinida. Em particular, podemos chamar a API como "count ()" e isso adiciona 1 ao "n" de uma "distância". De forma alguma, a forma ou a forma que alguém poderá acessar "n", exceto por meio da API.
O JavaScript é realmente incrível em sua simplicidade.
Os fechamentos são uma grande parte do motivo disso.
fonte
Um exemplo simples no Groovy para sua referência:
fonte