Qual é a diferença entre um ‘encerramento’ e um ‘lambda’?

Alguém poderia explicar? Eu entendo os conceitos básicos por trás deles, mas muitas vezes os vejo usados ​​de forma intercambiável e fico confuso.

E agora que estamos aqui, como eles diferem de uma function normal?

Um lambda é apenas uma function anônima – uma function definida sem nome. Em alguns idiomas, como o Scheme, eles são equivalentes a funções nomeadas. De fato, a definição da function é reescrita como vinculando um lambda a uma variável internamente. Em outras linguagens, como Python, existem algumas (e desnecessárias) distinções entre elas, mas elas se comportam da mesma maneira.

Um encerramento é qualquer function que se fecha sobre o ambiente no qual ele foi definido. Isso significa que ele pode acessar variables ​​que não estão em sua lista de parâmetros. Exemplos:

def func(): return h def anotherfunc(h): return func() 

Isso causará um erro, porque a func não fecha sobre o ambiente em anotherfunc lugar, anotherfunc é indefinido. func só fecha sobre o ambiente global. Isso vai funcionar:

 def anotherfunc(h): def func(): return h return func() 

Porque aqui, func é definido em anotherfunc , e no python 2.3 e superior (ou algum número como este) quando eles quase tem closures corretos (a mutação ainda não funciona), isso significa que ela fecha sobre o ambiente do anotherfunc e pode acessar variables ​​dentro dela. No Python 3.1+, a mutação também funciona ao usar a palavra-chave nonlocal .

Outro ponto importante – func continuará a se fechar no ambiente do anotherfunc , mesmo quando não estiver mais sendo avaliado em anotherfunc . Este código também funcionará:

 def anotherfunc(h): def func(): return h return func print anotherfunc(10)() 

Isto irá imprimir 10.

Isso, como você percebe, não tem nada a ver com lambda s – eles são dois conceitos diferentes (embora relacionados).

Quando a maioria das pessoas pensa em funções , elas pensam em funções nomeadas :

 function foo() { return "This string is returned from the 'foo' function"; } 

Estes são chamados pelo nome, é claro:

 foo(); //returns the string above 

Com expressões lambda , você pode ter funções anônimas :

  @foo = lambda() {return "This is returned from a function without a name";} 

Com o exemplo acima, você pode chamar o lambda através da variável que foi atribuída a:

 foo(); 

Mais útil do que atribuir funções anônimas às variables, no entanto, é transferi-las para ou a partir de funções de ordem mais alta, isto é, funções que aceitam / retornam outras funções. Em muitos desses casos, nomear uma function é desnecessário:

 function filter(list, predicate) { @filteredList = []; for-each (@x in list) if (predicate(x)) filteredList.add(x); return filteredList; } //filter for even numbers filter([0,1,2,3,4,5,6], lambda(x) {return (x mod 2 == 0)}); 

Um fechamento pode ser uma function nomeada ou anônima, mas é conhecido como tal quando “fecha” variables ​​no escopo onde a function está definida, ou seja, o fechamento ainda se referirá ao ambiente com quaisquer variables ​​externas que são usadas no fechamento em si. Aqui está um fechamento nomeado:

 @x = 0; function incrementX() { x = x + 1;} incrementX(); // x now equals 1 

Isso não parece muito, mas e se tudo isso estivesse em outra function e você passasse o incrementX para uma function externa?

 function foo() { @x = 0; function incrementX() { x = x + 1; return x; } return incrementX; } @y = foo(); // y = closure of incrementX over foo.x y(); //returns 1 (yx == 0 + 1) y(); //returns 2 (yx == 1 + 1) 

É assim que você obtém objects com estado na functional programming. Como nomear “incrementX” não é necessário, você pode usar um lambda neste caso:

 function foo() { @x = 0; return lambda() { x = x + 1; return x; }; } 

Há muita confusão em torno de lambdas e closures, mesmo nas respostas a esta questão do StackOverflow aqui. Em vez de perguntar a programadores randoms que aprenderam sobre encerramentos da prática com certas linguagens de programação ou outros programadores sem noção, faça uma viagem até a fonte (onde tudo começou). E como os lambdas e os closures vêm do Lambda Calculus inventado pela Alonzo Church nos anos 30 antes mesmo de existirem os primeiros computadores eletrônicos, essa é a fonte da qual estou falando.

O Lambda Calculus é a linguagem de programação mais simples do mundo. As únicas coisas que você pode fazer: ►

  • APLICAÇÃO: Aplicando uma expressão a outra, denotada por fx .
    (Pense nisso como uma chamada de function , onde f é a function e x é seu único parâmetro)
  • ABSTRACTION: Associa um símbolo que ocorre em uma expressão para marcar que este símbolo é apenas um “slot”, uma checkbox em branco esperando para ser preenchida com valor, uma “variável” como se fosse. Isso é feito prefixando uma letra grega λ (lambda), depois o nome simbólico (por exemplo, x ) e, em seguida, um ponto . antes da expressão. Isso então converte a expressão em uma function esperando um parâmetro .
    Por exemplo: λx.x+2 pega a expressão x+2 e diz que o símbolo x nesta expressão é uma variável vinculada – ela pode ser substituída por um valor fornecido como um parâmetro.
    Note que a function definida desta forma é anônima – ela não tem um nome, então você não pode se referir a ela ainda, mas você pode chamá- la imediatamente (lembre-se de aplicação?) Fornecendo o parâmetro que ela está esperando, como isso: (λx.x+2) 7 . Então a expressão (neste caso, um valor literal) 7 é substituída como x na subexpressão x+2 do lambda aplicado, então você obtém 7+2 , que então reduz para 9 por regras aritméticas comuns.

Então resolvemos um dos mistérios:
lambda é a function anônima do exemplo acima, λx.x+2 .


Em diferentes linguagens de programação, a syntax para abstração funcional (lambda) pode diferir. Por exemplo, em JavaScript, é assim:

 function(x) { return x+2; } 

e você pode aplicá-lo imediatamente a algum parâmetro como este:

 (function(x) { return x+2; })(7) 

ou você pode armazenar essa function anônima (lambda) em alguma variável:

 var f = function(x) { return x+2; } 

que efetivamente lhe dá um nome f , permitindo que você se refira a ele e o chame várias vezes mais tarde, por exemplo:

 alert( f(7) + f(10) ); // should print 21 in the message box 

Mas você não precisa nomear. Você poderia ligar imediatamente:

 alert( function(x) { return x+2; } (7) ); // should print 9 in the message box 

No LISP, os lambdas são feitos assim:

 (lambda (x) (+ x 2)) 

e você pode chamar tal lambda aplicando-o imediatamente a um parâmetro:

 ( (lambda (x) (+ x 2)) 7 ) 


OK, agora é hora de resolver o outro mistério: o que é um fechamento . Para fazer isso, vamos falar sobre símbolos ( variables ) em expressões lambda.

Como eu disse, o que a abstração lambda faz é ligar um símbolo em sua subexpressão, de modo que ele se torne um parâmetro substituível. Tal símbolo é chamado ligado . Mas e se houver outros símbolos na expressão? Por exemplo: λx.x/y+2 . Nesta expressão, o símbolo x é ligado pela abstração lambda λx. precedendo-a. Mas o outro símbolo, y , não está vinculado – é gratuito . Nós não sabemos o que é e de onde vem, então não sabemos o que significa e que valor representa, e portanto não podemos avaliar essa expressão até descobrirmos o que y significa.

Na verdade, o mesmo acontece com os outros dois símbolos, 2 e + . É que estamos tão familiarizados com esses dois símbolos que geralmente esquecemos que o computador não os conhece e precisamos dizer o que eles querem dizer, definindo-os em algum lugar, por exemplo, em uma biblioteca ou na própria linguagem.

Você pode pensar nos símbolos livres como definidos em algum outro lugar, fora da expressão, em seu “contexto circundante”, que é chamado de ambiente . O ambiente pode ser uma expressão maior da qual essa expressão é parte (como disse Qui-Gon Jinn: “Sempre há um peixe maior”;)), ou em alguma biblioteca, ou na própria linguagem (como primitiva ).

Isso nos permite dividir as expressões lambda em duas categorias:

  • Expressões FECHADAS: cada símbolo que ocorre nessas expressões é limitado por alguma abstração lambda. Em outras palavras, eles são auto-suficientes ; eles não exigem que qualquer contexto circundante seja avaliado. Eles também são chamados de combinadores .
  • Expressões ABERTAS: alguns símbolos nestas expressões não são vinculados – isto é, alguns dos símbolos que ocorrem neles são livres e requerem alguma informação externa, e assim eles não podem ser avaliados até que você forneça as definições desses símbolos.

Você pode FECHAR uma expressão lambda aberta fornecendo o ambiente , que define todos esses símbolos livres vinculando-os a alguns valores (que podem ser números, cadeias de caracteres, funções anônimas aka lambdas, etc.).

E aqui vem a parte do fechamento :
O fechamento de uma expressão lambda é esse conjunto particular de símbolos definidos no contexto externo (ambiente) que fornece valores para os símbolos livres nessa expressão, tornando-os não-livres mais. Acontece uma expressão lambda aberta , que ainda contém alguns símbolos livres “indefinidos”, em um fechado , que não tem mais nenhum símbolo livre.

Por exemplo, se você tem a seguinte expressão lambda: λx.x/y+2 , o símbolo x é ligado, enquanto o símbolo y é livre, portanto a expressão é open e não pode ser avaliada a menos que você diga o que y significa (e mesmo com + e 2 , que também são gratuitos). Mas suponha que você também tenha um ambiente como este:

 { y: 3, +: [built-in addition], 2: [built-in number], q: 42, w: 5 } 

Esse ambiente fornece definições para todos os símbolos “indefinidos” (livres) de nossa expressão lambda ( y , + , 2 ) e vários símbolos extras ( q , w ). Os símbolos que precisamos definir são este subconjunto do ambiente:

 { y: 3, +: [built-in addition], 2: [built-in number] } 

e este é precisamente o fechamento da nossa expressão lambda:>

Em outras palavras, fecha uma expressão lambda aberta. Este é o lugar onde o fechamento do nome veio em primeiro lugar, e é por isso que as respostas de tantas pessoas neste tópico não estão corretas: P


Então, por que eles estão enganados? Por que muitos deles dizem que encerramentos são algumas estruturas de dados na memory, ou alguns resources dos idiomas que eles usam, ou por que eles confundem closures com lambdas? : P

Bem, os marketoids corporativos da Sun / Oracle, Microsoft, Google etc. são os culpados, porque é isso que eles chamam de essas construções em suas linguagens (Java, C #, Go etc.). Eles freqüentemente chamam de “encerramentos” que deveriam ser apenas lambdas. Ou eles chamam de “fechamentos” uma técnica específica que eles usaram para implementar o escopo léxico, isto é, o fato de que uma function pode acessar as variables ​​que foram definidas em seu escopo externo no momento de sua definição. Eles costumam dizer que a function “encerra” essas variables, isto é, as captura em alguma estrutura de dados para evitar que sejam destruídas após a execução da function externa. Mas isso é apenas inventado post factumetnologia folclore” e marketing, o que só torna as coisas mais confusas, porque cada fornecedor de linguagem usa sua própria terminologia.

E é ainda pior por causa do fato de que há sempre um pouco de verdade no que eles dizem, o que não permite que você descarte isso como algo falso: P Deixe-me explicar:

Se você quer implementar uma linguagem que usa lambdas como cidadãos de primeira class, você precisa permitir que eles usem símbolos definidos em seu contexto circundante (isto é, usar variables ​​livres em seus lambdas). E esses símbolos devem estar lá mesmo quando a function circundante retornar. O problema é que esses símbolos estão ligados a algum armazenamento local da function (geralmente na pilha de chamadas), que não estará mais presente quando a function retornar. Portanto, para que um lambda funcione da maneira esperada, você precisa “capturar” de alguma forma todas essas variables ​​livres de seu contexto externo e salvá-las para mais tarde, mesmo quando o contexto externo tiver desaparecido. Ou seja, você precisa encontrar o fechamento do seu lambda (todas essas variables ​​externas que ele usa) e armazená-lo em outro lugar (seja fazendo uma cópia ou preparando espaço para elas antecipadamente, em algum outro lugar que não na pilha). O método real que você usa para atingir esse objective é um “detalhe de implementação” do seu idioma. O que é importante aqui é o fechamento , que é o conjunto de variables ​​livres do ambiente do seu lambda que precisa ser salvo em algum lugar.

Não demorou muito para que as pessoas começassem a chamar a estrutura de dados real que usam nas implementações de sua linguagem para implementar o encerramento como o “fechamento” em si. A estrutura geralmente se parece com algo assim:

 Closure { [pointer to the lambda function's machine code], [pointer to the lambda function's environment] } 

e essas estruturas de dados estão sendo passadas como parâmetros para outras funções, retornadas de funções, e armazenadas em variables, para representar lambdas, e permitindo que elas acessem seu ambiente envolvente, bem como o código de máquina a ser executado nesse contexto. Mas é apenas um caminho (um dos muitos) para implementar o fechamento, não o fechamento em si.

Como expliquei acima, o fechamento de uma expressão lambda é o subconjunto de definições em seu ambiente que fornece valores para as variables ​​livres contidas nessa expressão lambda, fechando efetivamente a expressão (transformando uma expressão lambda aberta , que não pode ser avaliada ainda, em uma expressão lambda fechada , que pode então ser avaliada, uma vez que todos os símbolos contidos nela são definidos agora).

Qualquer outra coisa é apenas um “culto à carga” e “magia do voo-doo” de programadores e vendedores de idiomas que desconhecem as verdadeiras raízes dessas noções.

Espero que responda às suas perguntas. Mas se você tiver alguma dúvida, sinta-se à vontade para perguntar nos comentários, e tentarei explicar melhor.

Nem todos os encerramentos são lambdas e nem todos os lambdas são encerramentos. Ambas são funções, mas não necessariamente da maneira que estamos acostumados a conhecer.

Um lambda é essencialmente uma function que é definida inline em vez do método padrão de declarar funções. Lambdas podem ser freqüentemente passados ​​como objects.

Um encerramento é uma function que envolve seu estado circundante referenciando campos externos ao seu corpo. O estado fechado permanece nas invocações do encerramento.

Em uma linguagem orientada a objects, os closures são normalmente fornecidos através de objects. No entanto, algumas linguagens OO (por exemplo, C #) implementam uma funcionalidade especial que está mais próxima da definição de closures fornecida por linguagens puramente funcionais (como o lisp) que não possuem objects para delimitar o estado.

O interessante é que a introdução do Lambdas e Closures em C # traz functional programming mais próxima do uso mainstream.

É tão simples assim: lambda é uma construção de linguagem, isto é, simplesmente syntax para funções anônimas; Um encerramento é uma técnica para implementá-lo – ou qualquer outra function de primeira class, nomeada ou anônima.

Mais precisamente, um fechamento é como uma function de primeira class é representada em tempo de execução, como um par de seu “código” e um ambiente “fechando” sobre todas as variables ​​não-locais usadas naquele código. Dessa forma, essas variables ​​ainda são acessíveis mesmo quando os escopos externos de onde eles originaram já foram encerrados.

Infelizmente, existem muitas linguagens por aí que não suportam funções como valores de primeira class, ou apenas as suportam de forma avariada. Então, as pessoas costumam usar o termo “fechamento” para distinguir “a coisa real”.

Do ponto de vista das linguagens de programação, elas são completamente duas coisas diferentes.

Basicamente, para uma linguagem completa de Turing, precisamos apenas de elementos muito limitados, por exemplo, abstração, aplicação e redução. A abstração e a aplicação fornecem a maneira de construir a expressão lamdba, e a redução determina o significado da expressão lambda.

O Lambda fornece uma maneira de abstrair o processo de computação. por exemplo, para calcular a sum de dois números, um processo que aceita dois parâmetros x, y e retorna x + y pode ser abstraído. No esquema, você pode escrever como

 (lambda (xy) (+ xy)) 

Você pode renomear os parâmetros, mas a tarefa que conclui não muda. Em quase todas as linguagens de programação, você pode dar um nome à expressão lambda, que são funções nomeadas. Mas não há muita diferença, eles podem ser conceitualmente considerados apenas como açúcar sintático.

OK, agora imagine como isso pode ser implementado. Sempre que aplicamos a expressão lambda a algumas expressões, por exemplo

 ((lambda (xy) (+ xy)) 2 3) 

Podemos simplesmente replace os parâmetros pela expressão a ser avaliada. Este modelo já é muito poderoso. Mas este modelo não nos permite alterar os valores dos símbolos, por exemplo, não podemos imitar a mudança de status. Assim, precisamos de um modelo mais complexo. Para resumir, sempre que queremos calcular o significado da expressão lambda, colocamos o par de símbolo e o valor correspondente em um ambiente (ou tabela). Em seguida, o resto (+ xy) é avaliado procurando os símbolos correspondentes na tabela. Agora, se fornecermos algumas primitivas para operar diretamente no ambiente, podemos modelar as mudanças de status!

Com este fundo, verifique esta function:

 (lambda (xy) (+ xyz)) 

Sabemos que quando avaliamos a expressão lambda, xy será vinculado em uma nova tabela. Mas como e onde podemos olhar para cima? Na verdade, z é chamado de variável livre. Deve haver um ambiente externo que contenha z. Caso contrário, o significado da expressão não pode ser determinado apenas pela vinculação de x e y. Para deixar isso claro, você pode escrever algo como segue no esquema:

 ((lambda (z) (lambda (xy) (+ xyz))) 1) 

Então, z estaria ligado a 1 em uma tabela externa. Ainda obtemos uma function que aceita dois parâmetros, mas o significado real disso também depende do ambiente externo. Em outras palavras, o ambiente externo se fecha nas variables ​​livres. Com a ajuda de set !, podemos tornar a function stateful, ou seja, não é uma function no sentido de matemática. O que ele retorna não depende apenas da input, mas também da z.

Isso é algo que você já conhece muito bem, um método de objects quase sempre depende do estado dos objects. É por isso que algumas pessoas dizem que “fechamentos são objects do homem pobre”. Mas também podemos considerar os objects como encerramentos do homem pobre, já que realmente gostamos de funções de primeira class.

Eu uso esquema para ilustrar as idéias devido a que o esquema é uma das primeiras linguagens que tem fechamentos reais. Todos os materiais aqui apresentados são muito melhores apresentados no capítulo 3 do SICP.

Para resumir, lambda e closure são conceitos realmente diferentes. Um lambda é uma function. Um fechamento é um par de lambda e o ambiente correspondente que fecha o lambda.

O conceito é o mesmo descrito acima, mas se você é do background do PHP, isto explica mais usando código PHP.

 $input = array(1, 2, 3, 4, 5); $output = array_filter($input, function ($v) { return $v > 2; }); 

function ($ v) {return $ v> 2; } é a definição da function lambda. Podemos até armazená-lo em uma variável, para que ele possa ser reutilizado:

 $max = function ($v) { return $v > 2; }; $input = array(1, 2, 3, 4, 5); $output = array_filter($input, $max); 

Agora, e se você quiser alterar o número máximo permitido na matriz filtrada? Você teria que escrever outra function lambda ou criar um encerramento (PHP 5.3):

 $max_comp = function ($max) { return function ($v) use ($max) { return $v > $max; }; }; $input = array(1, 2, 3, 4, 5); $output = array_filter($input, $max_comp(2)); 

Um encerramento é uma function que é avaliada em seu próprio ambiente, que possui uma ou mais variables ​​vinculadas que podem ser acessadas quando a function é chamada. Eles vêm do mundo da functional programming, onde há vários conceitos em jogo. Fechamentos são como funções lambda, mas mais inteligentes no sentido de que eles têm a capacidade de interagir com variables ​​do ambiente externo de onde o fechamento é definido.

Aqui está um exemplo mais simples de fechamento do PHP:

 $string = "Hello World!"; $closure = function() use ($string) { echo $string; }; $closure(); 

Bem explicado neste artigo.

Esta questão é antiga e tem muitas respostas. Agora, com o Java 8 e o Official Lambda, que são projetos de fechamento não oficiais, ele revive a questão.

A resposta no contexto Java (via Lambdas e closures – qual a diferença? ):

“Um encerramento é uma expressão lambda emparelhada com um ambiente que liga cada uma das suas variables ​​livres a um valor. Em Java, expressões lambda serão implementadas por meio de encerramentos, portanto os dois termos passaram a ser usados ​​de forma intercambiável na comunidade.”

Simplesmente falando, o encerramento é um truque sobre o escopo, lambda é uma function anônima. Podemos realizar o fechamento com lambda com mais elegância e o lambda é frequentemente usado como um parâmetro passado para uma function superior

Um lambda é apenas uma function anônima – uma function definida sem nome. Um encerramento é qualquer function que se fecha sobre o ambiente no qual ele foi definido. Isso significa que ele pode acessar variables ​​que não estão em sua lista de parâmetros.