Depois de ler muitas postagens explicando os fechamentos aqui, ainda estou perdendo um conceito-chave: Por que escrever um fechamento? Que tarefa específica um programador executaria que poderia ser melhor atendida por um fechamento?
Exemplos de fechamentos no Swift são os acessos de um NSUrl e o uso do geocoder reverso. Aqui está um exemplo. Infelizmente, esses cursos apenas apresentam o fechamento; eles não explicam por que a solução de código é escrita como um fechamento.
Um exemplo de um problema de programação do mundo real que poderia provocar meu cérebro a dizer "ah, eu deveria escrever um fechamento para isso" seria mais informativo do que uma discussão teórica. Não faltam discussões teóricas disponíveis neste site.
Respostas:
Primeiro de tudo, não há nada impossível sem o uso de fechamentos. Você sempre pode substituir um fechamento por um objeto implementando uma interface específica. É apenas uma questão de brevidade e acoplamento reduzido.
Segundo, lembre-se de que os fechamentos costumam ser usados de forma inadequada, onde uma referência simples de função ou outra construção seria mais clara. Você não deve tomar todos os exemplos que vê como uma prática recomendada.
Onde os fechamentos realmente brilham sobre outras construções é ao usar funções de ordem superior, quando você realmente precisa comunicar o estado e pode torná-lo uma linha, como neste exemplo JavaScript da página da wikipedia sobre fechamentos :
Aqui,
threshold
é comunicado de maneira muito sucinta e natural de onde é definido para onde é usado. Seu escopo é precisamente limitado o menor possível.filter
não precisa ser gravado para permitir a possibilidade de transmitir dados definidos pelo cliente como um limite. Não precisamos definir nenhuma estrutura intermediária com o único objetivo de comunicar o limiar nessa pequena função. É totalmente independente.Você pode escrever isso sem um fechamento, mas isso exigirá muito mais código e será mais difícil de seguir. Além disso, o JavaScript possui uma sintaxe lambda bastante detalhada. Em Scala, por exemplo, todo o corpo da função seria:
No entanto, se você pode usar o ECMAScript 6 , graças às funções de seta gorda, até o código JavaScript se torna muito mais simples e pode ser colocado em uma única linha.
No seu próprio código, procure lugares onde você gera muitos clichês apenas para comunicar valores temporários de um lugar para outro. Essas são excelentes oportunidades para considerar a substituição por um fechamento.
fonte
bestSellingBooks
código e aofilter
código, como uma interface específica ou argumento de dados do usuário, para poder comunicar osthreshold
dados. Isso une as duas funções de maneiras muito menos reutilizáveis.A título de explicação, vou emprestar algum código deste excelente post sobre fechamentos . É JavaScript, mas é a linguagem que a maioria das postagens do blog fala sobre fechamentos, porque fechamentos são muito importantes em JavaScript.
Digamos que você queira renderizar uma matriz como uma tabela HTML. Você poderia fazer assim:
Mas você está à mercê do JavaScript sobre como cada elemento da matriz será renderizado. Se você deseja controlar a renderização, você pode fazer o seguinte:
E agora você pode simplesmente passar uma função que retorna a renderização desejada.
E se você quisesse exibir um total em execução em cada linha da tabela? Você precisaria de uma variável para rastrear esse total, não precisaria? Um fechamento permite gravar uma função de renderizador que fecha sobre a variável total em execução e permite gravar um renderizador que pode acompanhar o total em execução:
A mágica que está acontecendo aqui é que
renderInt
retém o acesso àtotal
variável, mesmo querenderInt
seja repetidamente chamada e saia.Em uma linguagem mais tradicionalmente orientada a objetos que o JavaScript, você pode escrever uma classe que contenha essa variável total e repassá-la ao invés de criar um fechamento. Mas um fechamento é uma maneira muito mais poderosa, limpa e elegante de fazê-lo.
Leitura adicional
fonte
O objetivo de
closures
é simplesmente preservar o estado; daí o nomeclosure
- ele fecha sobre o estado. Para facilitar a explicação, usarei Javascript.Normalmente você tem uma função
onde o escopo da (s) variável (s) está vinculado a essa função. Então, após a execução, a variável
txt
sai do escopo. Não há como acessá-lo ou usá-lo após a conclusão da execução da função.Os fechamentos são construtos de linguagem, que permitem - como dito anteriormente - preservar o estado das variáveis e prolongar o escopo.
Isso pode ser útil em diferentes casos. Um caso de uso é a construção de funções de ordem superior .
Um exemplo simples, mas admitidamente não muito útil, é:
Você define uma função
makedadder
, que recebe um parâmetro como entrada e retorna uma função . Existe uma função externafunction(a){}
e uma internafunction(b){}{}
. Além disso, você define (implicitamente) outra funçãoadd5
como resultado de chamar a função de ordem superiormakeadder
.makeadder(5)
retorna uma função anônima ( interna ), que, por sua vez, recebe 1 parâmetro e retorna a soma do parâmetro da função externa e o parâmetro da função interna .O truque é que, ao retornar a função interna , que faz a adição real, o escopo do parâmetro da função externa (
a
) é preservado.add5
lembra , que o parâmetroa
foi5
.Ou para mostrar um exemplo pelo menos de alguma forma útil:
Outro caso de uso comum é a chamada expressão de função IIFE = chamada imediatamente. É muito comum em javascript falsificar variáveis de membros particulares. Isso é feito por meio de uma função, que cria um escopo privado =
closure
, porque é imediatamente após a definição da invocação. A estrutura éfunction(){}()
. Observe os colchetes()
após a definição. Isso torna possível usá-lo para criação de objetos com padrão de módulo revelador . O truque é criar um escopo e retornar um objeto, que tenha acesso a esse escopo após a execução do IIFE.O exemplo de Addi é assim:
O objeto retornado tem referências a funções (por exemplo
publicSetName
), que por sua vez têm acesso a variáveis "particulares"privateVar
.Mas esses são casos de uso mais especiais para Javascript.
Há várias razões para isso. Pode-se dizer que isso é natural para ele, pois ele segue um paradigma funcional . Ou em Javascript: é mera necessidade contar com fechamentos para contornar algumas peculiaridades da linguagem.
fonte
Existem dois casos de uso principais para fechamentos:
Assincronia. Digamos que você queira executar uma tarefa que demore um pouco e faça alguma coisa quando terminar. Você pode fazer com que seu código aguarde a execução, o que bloqueia a execução adicional e pode deixar o programa sem resposta ou chamar sua tarefa de forma assíncrona e dizer "inicie esta longa tarefa em segundo plano e, quando terminar, execute esse fechamento", onde o fechamento contém o código a ser executado quando terminar.
Retornos de chamada. Eles também são conhecidos como "delegados" ou "manipuladores de eventos", dependendo do idioma e da plataforma. A idéia é que você tenha um objeto personalizável que, em certos pontos bem definidos, executará um evento , que executa um fechamento transmitido pelo código que o configura. Por exemplo, na interface do usuário do seu programa, você pode ter um botão e oferece um fechamento que mantém o código a ser executado quando o usuário clica no botão.
Existem vários outros usos para fechamentos, mas esses são os dois principais.
fonte
Alguns outros exemplos:
Classificação
A maioria das funções de classificação opera comparando pares de objetos. É necessária alguma técnica de comparação. Restringir a comparação a um operador específico significa uma classificação bastante inflexível. Uma abordagem muito melhor é receber uma função de comparação como argumento para a função de classificação. Às vezes, uma função de comparação sem estado funciona bem (por exemplo, classificando uma lista de números ou nomes), mas e se a comparação precisar de estado?
Por exemplo, considere classificar uma lista de cidades por distância para um local específico. Uma solução feia é armazenar as coordenadas desse local em uma variável global. Isso faz com que a comparação funcione sem estado, mas ao custo de uma variável global.
Essa abordagem impede que vários threads classifiquem simultaneamente a mesma lista de cidades pela distância em dois locais diferentes. Um fechamento que encerra o local resolve esse problema e elimina a necessidade de uma variável global.
Números aleatórios
O original
rand()
não teve argumentos. Geradores de números pseudo-aleatórios precisam de estado. Alguns (por exemplo, Mersenne Twister) precisam de muito estado. Até o simples, mas terrívelrand()
estado necessário. Leia um jornal de matemática em um novo gerador de números aleatórios e inevitavelmente verá variáveis globais. Isso é bom para os desenvolvedores da técnica, não é tão bom para os chamadores. Encapsular esse estado em uma estrutura e passar a estrutura para o gerador de números aleatórios é uma maneira de contornar o problema de dados globais. Essa é a abordagem usada em muitos idiomas não OO para tornar o gerador de números aleatórios reentrante. Um fechamento oculta esse estado do chamador. Um fechamento oferece a sequência de chamada simplesrand()
e a reentrada do estado encapsulado.Os números aleatórios são mais do que apenas um PRNG. A maioria das pessoas que querem aleatoriedade deseja distribuí-lo de uma certa maneira. Começarei com números sorteados aleatoriamente entre 0 e 1, ou U (0,1) para abreviar. Qualquer PRNG que gere números inteiros entre 0 e o máximo será suficiente; simplesmente divida (como ponto flutuante) o número inteiro aleatório pelo máximo. Uma maneira conveniente e genérica de implementar isso é criar um fechamento que aceite um fechamento (o PRNG) e o máximo como entradas. Agora temos um gerador aleatório genérico e fácil de usar para U (0,1).
Existem várias outras distribuições além de U (0,1). Por exemplo, uma distribuição normal com uma certa média e desvio padrão. Todo algoritmo de gerador de distribuição normal que eu deparei usa um gerador de U (0,1). Uma maneira conveniente e genérica de criar um gerador normal é criar um fechamento que encapsule o gerador U (0,1), a média e o desvio padrão como estado. Este é, pelo menos conceitualmente, um fechamento que aceita um fechamento que aceita um fechamento como argumento.
fonte
Closures são equivalentes a objetos que implementam um método run () e, inversamente, objetos podem ser emulados com closures.
A vantagem dos fechamentos é que eles podem ser usados facilmente em qualquer lugar em que você espera uma função: funções de ordem superior, retornos de chamada simples (ou Padrão de estratégia). Você não precisa definir uma interface / classe para criar fechamentos ad-hoc.
A vantagem dos objetos é a possibilidade de ter interações mais complexas: vários métodos e / ou interfaces diferentes.
Portanto, usar fechamento ou objetos é principalmente uma questão de estilo. Aqui está um exemplo de coisas que o fechamento facilita, mas é inconveniente para implementar com objetos:
Basicamente, você encapsula um estado oculto que é acessado apenas por meio de fechamentos globais: você não precisa se referir a nenhum objeto, apenas usa o protocolo definido pelas três funções.
Confio no primeiro comentário de supercat no fato de que, em algumas linguagens, é possível controlar com precisão o tempo de vida dos objetos, enquanto o mesmo não ocorre nos fechamentos. No entanto, no caso de linguagens coletadas de lixo, o tempo de vida dos objetos é geralmente ilimitado e, portanto, é possível criar um fechamento que poderia ser chamado em um contexto dinâmico em que não deveria ser chamado (leitura de um fechamento após um fluxo) está fechado, por exemplo).
No entanto, é bastante simples evitar esse uso indevido capturando uma variável de controle que protegerá a execução de um fechamento. Mais precisamente, aqui está o que eu tenho em mente (no Common Lisp):
Aqui, pegamos um designador de função
function
e retornamos dois fechamentos, ambos capturando uma variável local denominadaactive
:function
, somente quandoactive
é verdadeaction
anil
, akafalse
.Em vez de
(when active ...)
, é claro que é possível ter uma(assert active)
expressão, o que poderia gerar uma exceção caso o fechamento fosse chamado quando não deveria ser. Além disso, lembre-se de que o código não seguro já pode gerar uma exceção quando usado incorretamente, portanto, você raramente precisa desse invólucro.Aqui está como você o usaria:
Observe que os fechamentos desativadores também podem ser concedidos a outras funções; aqui, as
active
variáveis locais não são compartilhadas entref
eg
; Além disso, além deactive
,f
apenas se refereobj1
eg
apenas se refere aobj2
.O outro ponto mencionado por supercat é que o fechamento pode levar a vazamentos de memória, mas infelizmente é o caso de quase tudo em ambientes de coleta de lixo. Se estiverem disponíveis, isso pode ser resolvido por indicadores fracos (o próprio fechamento pode ser mantido na memória, mas não impede a coleta de lixo de outros recursos).
fonte
List<T>
em uma (classe hipotética)TemporaryMutableListWrapper<T>
e expô-la ao código externo, pode-se garantir que, se invalidar o wrapper, o código externo não terá mais como manipular oList<T>
. Pode-se projetar fechamentos para permitir a invalidação, uma vez que eles atendam ao seu objetivo esperado, mas não é conveniente. Existem fechamentos para tornar certos padrões convenientes, e o esforço necessário para protegê-los negaria isso.Nada que ainda não tenha sido dito, mas talvez um exemplo mais simples.
Aqui está um exemplo de JavaScript usando tempos limite:
O que acontece aqui é que, quando
delayedLog()
é chamado, ele retorna imediatamente após definir o tempo limite, e o tempo limite continua passando em segundo plano.Mas quando o tempo limite se esgotar e chamar a
fire()
função, o console exibirá omessage
que foi passado originalmente paradelayedLog()
, porque ainda está disponível para ofire()
fechamento. Você pode ligardelayedLog()
quantas vezes quiser, com uma mensagem diferente e adiar cada vez, e isso fará a coisa certa.Mas vamos imaginar que o JavaScript não tem fechamentos.
Uma maneira seria fazer o
setTimeout()
bloqueio - mais como uma função "sleep" - para quedelayedLog()
o escopo não desapareça até que o tempo limite se esgote. Mas bloquear tudo não é muito bom.Outra maneira seria colocar a
message
variável em algum outro escopo que estará acessível apósdelayedLog()
o escopo desaparecer.Você poderia usar variáveis globais - ou pelo menos "de escopo mais amplo" -, mas teria que descobrir como acompanhar qual mensagem acompanha o tempo limite. Mas não pode ser apenas uma fila FIFO sequencial, porque você pode definir qualquer atraso que desejar. Portanto, pode ser "primeiro a entrar, terceiro a sair" ou algo assim. Portanto, você precisaria de outros meios para vincular uma função temporizada às variáveis de que ela precisa.
Você pode instanciar um objeto de tempo limite que "agrupe" o timer com a mensagem. O contexto de um objeto é mais ou menos um escopo que permanece. Depois, o cronômetro será executado no contexto do objeto, para que ele tenha acesso à mensagem correta. Mas você teria que armazenar esse objeto porque, sem nenhuma referência, o lixo seria coletado (sem fechamentos, também não haveria referências implícitas). E você teria que remover o objeto assim que o tempo limite for disparado, caso contrário, ele permanecerá. Então, você precisa de algum tipo de lista de objetos de tempo limite e verifica periodicamente se há objetos "gastos" para remover - ou os objetos se adicionariam e se removeriam da lista e ...
Então ... sim, isso está ficando chato.
Felizmente, você não precisa usar um escopo mais amplo ou agrupar objetos apenas para manter certas variáveis por perto. Como o JavaScript possui encerramentos, você já tem exatamente o escopo de que precisa. Um escopo que fornece acesso à
message
variável quando você precisar. E por causa disso, você pode se safar escrevendodelayedLog()
como acima.fonte
message
está incluído no escopo da funçãofire
e, portanto, é referido em outras chamadas; mas faz isso acidentalmente, por assim dizer. Tecnicamente, é um fechamento. +1 de qualquer maneira;)makeadder
exemplo acima, que, aos meus olhos, parece quase o mesmo. Você retorna uma função "curry" que recebe um único argumento em vez de dois; usando os mesmos meios, eu crio uma função que recebe zero argumentos. Eu simplesmente não o devolvo, mas o passo para elesetTimeout
.message
emfire
gera o fechamento. E quando é chamadosetTimeout
, faz uso do estado preservado.O PHP pode ser usado para ajudar a mostrar um exemplo real em um idioma diferente.
Então, basicamente, estou registrando uma função que será executada para o URI / api / users . Esta é realmente uma função de middleware que acaba sendo armazenada em uma pilha. Outras funções serão envolvidas. Praticamente como o Node.js / Express.js .
O contêiner de injeção de dependência está disponível (através da cláusula use) dentro da função quando é chamado. É possível criar algum tipo de classe de ação de rota, mas esse código acaba sendo mais simples, rápido e fácil de manter.
fonte
Um fechamento é um pedaço de código arbitrário, incluindo variáveis, que pode ser tratado como dados de primeira classe.
Um exemplo trivial é o bom e antigo qsort: é uma função para classificar dados. Você precisa apontar para uma função que compara dois objetos. Então você tem que escrever uma função. Essa função pode precisar ser parametrizada, o que significa que você fornece variáveis estáticas. O que significa que não é seguro para threads. Você está no DS. Então, você escreve uma alternativa que faz um fechamento em vez de um ponteiro de função. Você resolve instantaneamente o problema da parametrização porque os parâmetros se tornam parte do fechamento. Você torna seu código mais legível porque escreve como os objetos são comparados diretamente com o código que chama a função de classificação.
Existem várias situações em que você deseja executar alguma ação que requer uma grande quantidade de código da placa da caldeira, além de um pequeno, mas essencial, código que precisa ser adaptado. Você evita o código clichê escrevendo uma função uma vez que recebe um parâmetro de fechamento e faz todo o código clichê ao seu redor e, em seguida, você pode chamar essa função e passar o código para ser adaptado como um fechamento. Uma maneira muito compacta e legível de escrever código.
Você tem uma função na qual algum código não trivial precisa ser executado em muitas situações diferentes. Isso costumava produzir duplicação de código ou código contorcido, para que o código não trivial estivesse presente apenas uma vez. Trivial: você atribui um fechamento a uma variável e a chama da maneira mais óbvia, sempre que necessário.
Multithreading: iOS / MacOS X tem funções para executar ações como "executar este fechamento em um thread em segundo plano", "... no thread principal", "... no thread principal, daqui a 10 segundos". Torna multithreading trivial .
Chamadas assíncronas: foi o que o OP viu. Qualquer ligação que acesse a Internet ou qualquer outra coisa que possa levar tempo (como a leitura das coordenadas GPS) é algo em que você não pode esperar pelo resultado. Portanto, você tem funções que fazem as coisas em segundo plano e depois passa um encerramento para dizer a elas o que fazer quando elas terminarem.
Isso é um pequeno começo. Cinco situações em que os fechamentos são revolucionários em termos de produção de código compacto, legível, confiável e eficiente.
fonte
Um fechamento é uma maneira abreviada de escrever um método no qual ele deve ser usado. Isso poupa o esforço de declarar e escrever um método separado. É útil quando o método será usado apenas uma vez e a definição do método for curta. Os benefícios são de digitação reduzida, pois não há necessidade de especificar o nome da função, seu tipo de retorno ou seu modificador de acesso. Além disso, ao ler o código, você não precisa procurar em outro lugar a definição do método.
O texto acima é um resumo de Compreender as expressões lambda de Dan Avidar.
Isso esclareceu o uso de encerramentos para mim, porque esclarece as alternativas (fechamento versus método) e os benefícios de cada um.
O código a seguir é usado uma vez e apenas uma vez durante a instalação. Escrevê-lo no lugar em viewDidLoad evita o problema de procurá-lo em outro lugar e reduz o tamanho do código.
Além disso, prevê a conclusão de um processo assíncrono sem bloquear outras partes do programa e um fechamento reterá um valor para reutilização nas chamadas de função subseqüentes.
Outro fechamento; este captura um valor ...
fonte