Por que um programa usaria um fechamento?

58

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.

Bendrix
fonte
7
"Reclame aqui recentemente revisado de fechamentos" - está faltando um link?
precisa
2
Você deve colocar esse esclarecimento sobre a tarefa assíncrona em um comentário, abaixo da resposta relevante. Isso não faz parte da sua pergunta original e o responsável pela resposta nunca será notificado sobre sua edição.
Robert Harvey
5
Apenas um petisco: o ponto crucial do fechamento é o escopo lexical (em oposição ao escopo dinâmico), os fechamentos sem o escopo lexical são meio inúteis. Os fechamentos às vezes são chamados de fechamentos lexicais.
Hoffmann
11
Os fechamentos permitem instanciar funções .
user253751
11
Leia qualquer bom livro sobre programação funcional. Talvez comece com SICP
Basile Starynkevitch

Respostas:

33

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 :

// Return a list of all books with at least 'threshold' copies sold.
function bestSellingBooks(threshold) {
  return bookList.filter(
      function (book) { return book.sales >= threshold; }
    );
}

Aqui, thresholdé comunicado de maneira muito sucinta e natural de onde é definido para onde é usado. Seu escopo é precisamente limitado o menor possível. filternã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:

bookList filter (_.sales >= threshold)

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.

const bestSellingBooks = (threshold) => bookList.filter(book => book.sales >= threshold);

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.

Karl Bielefeldt
fonte
Como exatamente os fechamentos reduzem o acoplamento?
precisa saber é o seguinte
Sem encerramentos, você deve impor restrições ao bestSellingBookscódigo e ao filtercódigo, como uma interface específica ou argumento de dados do usuário, para poder comunicar os thresholddados. Isso une as duas funções de maneiras muito menos reutilizáveis.
Karl Bielefeldt
51

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:

function renderArrayAsHtmlTable (array) {
  var table = "<table>";
  for (var idx in array) {
    var object = array[idx];
    table += "<tr><td>" + object + "</td></tr>";
  }
  table += "</table>";
  return table;
}

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:

function renderArrayAsHtmlTable (array, renderer) {
  var table = "<table>";
  for (var idx in array) {
    var object = array[idx];
    table += "<tr><td>" + renderer(object) + "</td></tr>";
  }
  table += "</table>";
  return table;
}

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:

function intTableWithTotals (intArray) {
  var total = 0;
  var renderInt = function (i) {
    total += i;
    return "Int: " + i + ", running total: " + total;
  };
  return renderObjectsInTable(intArray, renderInt);
}

A mágica que está acontecendo aqui é que renderInt retém o acesso à totalvariável, mesmo que renderIntseja 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

Robert Harvey
fonte
10
Em geral, você pode dizer que "função de primeira classe == objeto com apenas um método", "Fechamento == objeto com apenas um método e estado", "objeto == pacote de fechamentos".
Jörg W Mittag
Parece bom. Outra coisa, e isso pode ser pedantismo, é que o Javascript é orientado a objetos, e você pode criar um objeto que contenha a variável total e repassá-la. Os fechamentos permanecem muito mais poderosos, limpos e elegantes e também contribuem para um Javascript muito mais idiomático, mas a maneira como está escrito pode implicar que o Javascript não pode fazer isso de maneira orientada a objetos.
KRyan
2
Na verdade, para ser realmente pedante: o fechamento é o que torna o JavaScript orientado a objetos em primeiro lugar! OO é sobre abstração de dados, e fechamentos são a maneira de executar a abstração de dados em JavaScript.
Jörg W Mittag
@ JörgWMittag: Como você pode ver cloures como objetos com apenas um método, você pode ver objetos como uma coleção de fechamentos que se fecham sobre as mesmas variáveis: as variáveis ​​membros de um objeto são apenas as variáveis ​​locais do construtor do objeto e os métodos do objeto são fechamentos definidos dentro do escopo do construtor e disponibilizados para serem chamados posteriormente. A função construtora retorna uma função de ordem superior (o objeto) que pode ser despachada em cada fechamento, de acordo com o nome do método usado para a chamada.
Giorgio
@Giorgio: De fato, é assim que os objetos são tipicamente implementados no Scheme, por exemplo, e também é como os objetos são implementados no JavaScript (não surpreende, considerando sua estreita relação com o Scheme, afinal Brendan Eich foi contratado originalmente para projetar um O Scheme dialeta e implementa um intérprete de Scheme incorporado no Netscape Navigator, e somente mais tarde foi ordenado a criar uma linguagem "com objetos que se parecessem com C ++", após o que ele fez a quantidade mínima absoluta de alterações para atender a esses requisitos de marketing).
Jörg W Mittag
23

O objetivo de closuresé simplesmente preservar o estado; daí o nome closure- ele fecha sobre o estado. Para facilitar a explicação, usarei Javascript.

Normalmente você tem uma função

function sayHello(){
    var txt="Hello";
    return txt;
}

onde o escopo da (s) variável (s) está vinculado a essa função. Então, após a execução, a variável txtsai 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 .

Em matemática e ciências da computação, uma função de ordem superior (também forma funcional, funcional ou functor) é uma função que executa pelo menos um dos seguintes procedimentos: 1

  • assume uma ou mais funções como entrada
  • gera uma função

Um exemplo simples, mas admitidamente não muito útil, é:

 makeadder=function(a){
     return function(b){
         return a+b;
     }
 }

 add5=makeadder(5);
 console.log(add5(10)); 

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 interna function(b){}{} . Além disso, você define (implicitamente) outra função add5como resultado de chamar a função de ordem superior makeadder. 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âmetro afoi 5.

Ou para mostrar um exemplo pelo menos de alguma forma útil:

  makeTag=function(openTag, closeTag){
     return function(content){
         return openTag +content +closeTag;
     }
 }

 table=makeTag("<table>","</table>")
 tr=makeTag("<tr>", "</tr>");
 td=makeTag("<td>","</td>");
 console.log(table(tr(td("I am a Row"))));

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:

 var myRevealingModule = (function () {

         var privateVar = "Ben Cherry",
             publicVar = "Hey there!";

         function privateFunction() {
             console.log( "Name:" + privateVar );
         }

         function publicSetName( strName ) {
             privateVar = strName;
         }

         function publicGetName() {
             privateFunction();
         }


         // Reveal public pointers to
         // private functions and properties

         return {
             setName: publicSetName,
             greeting: publicVar,
             getName: publicGetName
         };

     })();

 myRevealingModule.setName( "Paul Kinlan" );

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.

Que tarefa específica um programador executaria que poderia ser melhor atendida por um fechamento?

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.

Thomas Junk
fonte
"O objetivo dos fechamentos é simplesmente preservar o estado; daí o nome do fechamento - ele fecha sobre o estado.": Depende da linguagem, na verdade. Os fechamentos fecham sobre nomes / variáveis ​​externos. Eles podem indicar estado (locais de memória que podem ser alterados), mas também podem indicar valores (imutáveis). Os fechamentos em Haskell não preservam o estado: eles preservam as informações conhecidas no momento e no contexto em que o fechamento foi criado.
Giorgio
11
»Preservam as informações conhecidas no momento e no contexto em que o fechamento foi criado« independentemente de poder ser alterado ou não, é estado - talvez estado imutável .
Thomas Junk
16

Existem dois casos de uso principais para fechamentos:

  1. 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.

  2. 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.

Mason Wheeler
fonte
23
Então, basicamente, retornos de chamada, pois o primeiro exemplo também é um retorno de chamada.
Robert Harvey
2
@RobertHarvey: Tecnicamente verdade, mas são modelos mentais diferentes. Por exemplo, (de modo geral), você espera que o manipulador de eventos seja chamado várias vezes, mas sua continuação assíncrona seja chamada apenas uma vez. Mas sim, tecnicamente tudo o que você faria com um fechamento é um retorno de chamada. (A menos que você está convertendo-a em uma árvore de expressão, mas isso é uma questão completamente diferente.);)
Mason Wheeler
4
@RobertHarvey: visualizar fechamentos como retornos de chamada coloca você em um estado de espírito que realmente o impede de usá-los de maneira eficaz. Fechamentos são retornos de chamada com esteróides.
gnasher729
13
@MasonWheeler Coloque dessa maneira, parece errado. Os retornos de chamada não têm nada a ver com fechamentos; é claro que você poderia retornar uma função dentro de um fechamento ou chamar um fechamento como retorno de chamada; mas um retorno de chamada não é necessário um fechamento. Um retorno de chamada é apenas uma chamada de função simples. Um manipulador de eventos não é, por si só, um fechamento. Um delegado não é necessário um fechamento: é principalmente o modo C # de ponteiros de função. Claro que você pode usá-lo para construir um fechamento. Sua explicação é imprecisa. O ponto está se aproximando do estado e fazendo uso dele.
Thomas Junk
5
Talvez haja conotações específicas de JS acontecendo aqui que estão me fazendo entender mal, mas essa resposta parece absolutamente errada para mim. A assincronia ou os retornos de chamada podem usar qualquer coisa que possa ser chamada. Não é necessário um fechamento para nenhum deles - uma função regular funcionará bem. Enquanto isso, um fechamento, como definido na programação funcional, ou usado no Python, é útil, como Thomas diz, porque "fecha" algum estado, ou seja, uma variável, e fornece uma função em um escopo interno acesso consistente aos parâmetros dessa variável. mesmo que a função seja chamada e saia várias vezes.
Jonathan Hartley
13

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ível rand()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 simples rand()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.

David Hammen
fonte
7

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:

 (let ((seen))
    (defun register-name (name)
       (pushnew name seen :test #'string=))

    (defun all-names ()
       (copy-seq seen))

    (defun reset-name-registry ()
       (setf seen nil)))

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):

(defun guarded (function)
  (let ((active t))
    (values (lambda (&rest args)
              (when active
                (apply function args)))
            (lambda ()
              (setf active nil)))))

Aqui, pegamos um designador de função functione retornamos dois fechamentos, ambos capturando uma variável local denominada active:

  • o primeiro delega function, somente quando activeé verdade
  • a segunda define actiona nil, aka false.

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:

(use-package :metabang-bind) ;; for bind

(defun example (obj1 obj2)
  (bind (((:values f f-deactivator)(guarded (lambda () (do-stuff obj1))))
         ((:values g g-deactivator)(guarded (lambda () (do-thing obj2)))))

    ;; ensure the closure are inactive when we exit
    (unwind-protect
         ;; pass closures to other functions
         (progn
           (do-work f)
           (do-work g))

      ;; cleanup code: deactivate closures
      (funcall f-deactivator)
      (funcall g-deactivator))))

Observe que os fechamentos desativadores também podem ser concedidos a outras funções; aqui, as activevariáveis locais não são compartilhadas entre fe g; Além disso, além de active, fapenas se refere obj1e gapenas se refere a obj2.

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).

coredump
fonte
11
Uma desvantagem dos fechamentos normalmente implementados é que, uma vez que um fechamento que usa uma das variáveis ​​locais de um método é exposto ao mundo externo, o método que define o fechamento pode perder o controle sobre o momento em que essa variável é lida e gravada. Não há maneira conveniente, na maioria dos idiomas, de definir um fechamento efêmero, passá-lo para um método e garantir que ele deixará de existir quando o método que receber o retorno for devolvido, nem nas linguagens multithread garanta que um fechamento não seja executado em um contexto de segmentação inesperado.
Supercat
@ supercat: Dê uma olhada no Objective-C e Swift.
precisa saber é o seguinte
11
@ supercat Não existiria a mesma desvantagem em objetos com estado?
Andres F.
@AndresF .: É possível encapsular um objeto com estado em um wrapper que suporte invalidação; se o código encapsular uma propriedade privada 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 o List<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.
supercat
11
@coredump: Em C #, se dois fechamentos têm alguma variável em comum, o mesmo objeto gerado pelo compilador servirá a ambos, pois as alterações feitas em uma variável compartilhada por um fechamento devem ser vistas pelo outro. Evitar esse compartilhamento exigiria que cada fechamento fosse seu próprio objeto que contenha suas próprias variáveis ​​não compartilhadas, bem como uma referência a um objeto compartilhado que contenha as variáveis ​​compartilhadas. Não é impossível, mas teria adicionado um nível extra de desreferenciamento à maioria dos acessos variáveis, tornando tudo mais lento.
supercat
6

Nada que ainda não tenha sido dito, mas talvez um exemplo mais simples.

Aqui está um exemplo de JavaScript usando tempos limite:

// Example function that logs something to the browser's console after a given delay
function delayedLog(message, delay) {
  // this function will be called when the timer runs out
  var fire = function () {
    console.log(message); // closure magic!
  };

  // set a timeout that'll call fire() after a delay
  setTimeout(fire, delay);
}

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á o messageque foi passado originalmente para delayedLog(), porque ainda está disponível para o fire()fechamento. Você pode ligar delayedLog()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 que delayedLog()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 messagevariável em algum outro escopo que estará acessível após delayedLog()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 à messagevariável quando você precisar. E por causa disso, você pode se safar escrevendo delayedLog()como acima.

Flambino
fonte
Eu tenho um problema chamando isso de fechamento . Talvez eu cunhasse o fechamento acidental . messageestá incluído no escopo da função firee, portanto, é referido em outras chamadas; mas faz isso acidentalmente, por assim dizer. Tecnicamente, é um fechamento. +1 de qualquer maneira;)
Thomas Junk
@ThomasJunk Não tenho certeza se sigo bem. Como seria um " fechamento não acidental"? Eu vi o seu makeadderexemplo 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 ele setTimeout.
Flambino 6/06/2015
Talvez eu não deva voltar, talvez esse seja o ponto, o que faz a diferença para mim. Não consigo expressar bem a minha "preocupação";) Em termos técnicos, você está 100% certo. Referenciar messageem firegera o fechamento. E quando é chamado setTimeout, faz uso do estado preservado.
Thomas Junk
11
@ThomasJunk Eu vejo como isso pode cheirar um pouco diferente. Porém, talvez eu não compartilhe da sua preocupação :) Ou, de qualquer forma, eu não chamaria isso de "acidental" - com certeza eu o codifiquei dessa maneira de propósito;)
Flambino
3

O PHP pode ser usado para ajudar a mostrar um exemplo real em um idioma diferente.

protected function registerRoutes($dic)
{
  $router = $dic['router'];

  $router->map(['GET','OPTIONS'],'/api/users',function($request,$response) use ($dic)
  {
    $controller = $dic['user_api_controller'];
    return $controller->findAllAction($request,$response);
  })->setName('api_users');
}

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.

Cerad
fonte
-1

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.

gnasher729
fonte
-4

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.

myPhoton!.getVariable("Temp", completion: { (result:AnyObject!, error:NSError!) -> Void in
  if let e = error {
    self.getTempLabel.text = "Failed reading temp"
  } else {
    if let res = result as? Float {
    self.getTempLabel.text = "Temperature is \(res) degrees"
    }
  }
})

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 ...

let animals = ["fish", "cat", "chicken", "dog"]
let sortedStrings = animals.sorted({ (one: String, two: String) -> Bool in return one > two
}) println(sortedStrings)
Bendrix
fonte
4
Infelizmente, isso confunde fechamentos e lambdas. Os lambdas costumam usar fechamentos, porque geralmente são muito mais úteis se definidos a) muito estritamente eb) dentro e dependendo de um determinado contexto de método, incluindo variáveis. No entanto, o fechamento real não tem nada a ver com a idéia de um lambda, que é basicamente a capacidade de definir uma função de primeira classe e distribuí-la para uso posterior.
Nathan Tuggy
Enquanto você está votando nesta resposta, leia isto ... Quando devo votar? Use seus votos negativos sempre que encontrar uma postagem flagrantemente desleixada e sem esforço ou uma resposta que seja clara e talvez perigosamente incorreta. Minha resposta pode não ter algumas das razões para usar um fechamento, mas não é flagrantemente desleixado nem perigosamente incorreto. Existem muitos documentos e discussões de fechamentos que se concentram na programação funcional e na notação lambda. Tudo que minha resposta diz é que essa explicação da programação funcional me ajudou a entender os fechamentos. Isso e o valor persistente.
precisa
11
A resposta talvez não seja perigosa , mas certamente é "claramente [...] incorreta". Ele usa os termos errados para conceitos altamente complementares e, portanto, frequentemente usados ​​juntos - exatamente onde a clareza da distinção é essencial. É provável que isso cause problemas de design para programadores que leem isso se não forem abordados. (E uma vez que, tanto quanto eu posso dizer, o exemplo de código simplesmente não contém quaisquer fechos, apenas uma função lambda, não ajuda a explicar por fechamentos seria útil.)
Nathan Tuggy
Nathan, esse exemplo de código é um fechamento no Swift. Você pode perguntar a outra pessoa se duvidar de mim. Então, se for um fechamento, você votaria?
precisa
11
A Apple descreve claramente claramente o que eles querem dizer com fechamento em sua documentação . "Os fechamentos são blocos independentes de funcionalidade que podem ser passados ​​e usados ​​em seu código. Os fechamentos no Swift são semelhantes aos blocos em C e Objective-C e aos lambdas em outras linguagens de programação." Quando você chega à implementação e terminologia, pode ser confuso .