Alguém poderia explicar? Eu entendo os conceitos básicos por trás deles, mas geralmente os vejo usados de forma intercambiável e fico confuso.
E agora que estamos aqui, como eles diferem de uma função regular?
Alguém poderia explicar? Eu entendo os conceitos básicos por trás deles, mas geralmente os vejo usados de forma intercambiável e fico confuso.
E agora que estamos aqui, como eles diferem de uma função regular?
Respostas:
Um lambda é apenas uma função anônima - uma função definida sem nome. Em alguns idiomas, como Scheme, eles são equivalentes a funções nomeadas. De fato, a definição da função é reescrita como vinculando um lambda a uma variável internamente. Em outras linguagens, como Python, existem algumas distinções (desnecessárias) entre elas, mas elas se comportam da mesma maneira.
Um fechamento é qualquer função que se fecha sobre o ambiente em que foi definido. Isso significa que ele pode acessar variáveis 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 func
não fecha sobre o ambiente em anotherfunc
- h
é indefinido. func
somente fecha sobre o ambiente global. Isso funcionará:
def anotherfunc(h):
def func(): return h
return func()
Como aqui, func
é definido no anotherfunc
python 2.3 e superior (ou algum número como este) quando eles quase acertaram os fechamentos (a mutação ainda não funciona), isso significa que ele fecha anotherfunc
o ambiente do servidor e pode acessar variáveis dentro dele. isto. No Python 3.1+, a mutação também funciona ao usar a nonlocal
palavra - chave .
Outro ponto importante - func
continuará a fechar anotherfunc
o ambiente, mesmo quando não estiver mais sendo avaliado anotherfunc
. Este código também funcionará:
def anotherfunc(h):
def func(): return h
return func
print anotherfunc(10)()
Isso imprimirá 10.
Como você percebe, isso não tem nada a ver com lambda s - eles são dois conceitos diferentes (embora relacionados).
Há muita confusão em torno de lambdas e fechamentos, mesmo nas respostas a esta pergunta do StackOverflow aqui. Em vez de perguntar a programadores aleatórios que aprenderam sobre fechamentos da prática com certas linguagens de programação ou outros programadores sem noção, viaje até a fonte (onde tudo começou). E como as lambdas e fechamentos vêm do Lambda Calculus inventado pela Alonzo Church nos anos 30 antes da existência dos primeiros computadores eletrônicos, é dessa fonte que estou falando.
O Lambda Calculus é a linguagem de programação mais simples do mundo. As únicas coisas que você pode fazer nele: ►
f x
. f
está a função e x
é seu único parâmetro)λ
(lambda), o nome simbólico (por exemplo x
) e um ponto .
antes da expressão. Isso então converte a expressão em uma função esperando um parâmetro . λx.x+2
pega a expressão x+2
e informa que o símbolo x
nessa expressão é uma variável vinculada - ele pode ser substituído por um valor que você fornece como parâmetro. (λx.x+2) 7
. Em seguida, a expressão (neste caso, um valor literal) 7
é substituída como x
na subexpressão x+2
do lambda aplicado, para que você obtenha 7+2
, o qual será reduzido 9
por regras aritméticas comuns.Então resolvemos um dos mistérios:
lambda é a função anônima do exemplo acima λx.x+2
,.
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 esta função 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 depois, por exemplo:
alert( f(7) + f(10) ); // should print 21 in the message box
Mas você não precisava dar um nome. Você pode chamá-lo imediatamente:
alert( function(x) { return x+2; } (7) ); // should print 9 in the message box
No LISP, as lambdas são feitas assim:
(lambda (x) (+ x 2))
e você pode chamar esse lambda aplicando-o imediatamente a um parâmetro:
( (lambda (x) (+ x 2)) 7 )
Como eu disse, o que a abstração lambda faz é vincular um símbolo em sua subexpressão, para que ela se torne um parâmetro substituível . Esse símbolo é chamado de vinculado . Mas e se houver outros símbolos na expressão? Por exemplo: λx.x/y+2
. Nesta expressão, o símbolo x
é vinculado pela abstração lambda que o λx.
precede. Mas o outro símbolo y
,, não está vinculado - é gratuito . Não sabemos o que é e de onde vem; portanto, não sabemos o que significa e que valor representa; portanto, não podemos avaliar essa expressão até descobrirmos o que y
significa.
De fato, 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 significam 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 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 faz parte (como Qui-Gon Jinn disse: "Sempre há um peixe maior";)), ou em alguma biblioteca, ou na própria linguagem (como primitiva ).
Isso nos permite dividir expressões lambda em duas categorias:
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, seqüências de caracteres, funções anônimas, também conhecidas como lambdas, o que seja ...).
E aqui vem a parte do fechamento :
o fechamento de uma expressão lambda é esse conjunto específico de símbolos definidos no contexto externo (ambiente) que atribui valores aos símbolos livres nessa expressão, tornando-os não livres mais. Ele transforma uma expressão lambda aberta , que ainda contém alguns símbolos livres "indefinidos", em uma expressão fechada , que não possui mais símbolos livres.
Por exemplo, se você tiver a seguinte expressão lambda:, λx.x/y+2
o símbolo x
é vinculado, 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 o mesmo com +
e 2
, que também é gratuito). Mas suponha que você também tenha um ambiente como este:
{ y: 3,
+: [built-in addition],
2: [built-in number],
q: 42,
w: 5 }
Este ambiente fornece definições para todos os símbolos "Undefined" (gratuito) de nossa expressão lambda ( y
, +
, 2
), e vários símbolos extras ( q
, w
). Os símbolos que precisamos definir são esse 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. É daí que o fechamento do nome veio, em primeiro lugar, e é por isso que as respostas de tantas pessoas neste segmento não são muito corretas: P
Bem, os marketoids corporativos da Sun / Oracle, Microsoft, Google etc. são os culpados, porque foi assim que eles chamaram essas construções em suas linguagens (Java, C #, Go etc.). Eles costumam chamar de "fechamento" o que deveriam ser apenas lambdas. Ou eles chamam de "fechamento" uma técnica específica usada para implementar o escopo lexical, ou seja, o fato de uma função poder acessar as variáveis que foram definidas em seu escopo externo no momento de sua definição. Eles costumam dizer que a função "encerra" essas variáveis, ou seja, as captura em alguma estrutura de dados para evitar que sejam destruídas após a execução da função externa. Mas isso é apenas "etimologia folclórica" e marketing pós-factum , o que só torna as coisas mais confusas,
E é ainda pior pelo fato de sempre haver um pouco de verdade no que eles dizem, o que não permite que você a descarte facilmente como falsa: P Deixe-me explicar:
Se você deseja implementar uma linguagem que usa lambdas como cidadãos de primeira classe, é necessário permitir que eles usem símbolos definidos em seu contexto circundante (ou seja, use variáveis livres em suas lambdas). E esses símbolos devem estar lá mesmo quando a função circundante retornar. O problema é que esses símbolos estão vinculados a algum armazenamento local da função (geralmente na pilha de chamadas), que não estará mais lá quando a função retornar. Portanto, para que um lambda funcione da maneira esperada, você precisa "capturar" todas essas variáveis livres de seu contexto externo e salvá-las para mais tarde, mesmo quando o contexto externo desaparecer. Ou seja, você precisa encontrar o fechamentodo seu lambda (todas essas variáveis externas que ele usa) e armazene-o em outro lugar (fazendo uma cópia ou preparando espaço para eles antecipadamente, em outro lugar que não na pilha). O método real que você usa para atingir esse objetivo é um "detalhe da implementação" do seu idioma. O importante aqui é o fechamento , que é o conjunto de variáveis livres do ambiente do seu lambda que precisam ser salvas em algum lugar.
Não demorou muito tempo para as pessoas começarem a chamar a estrutura de dados real usada nas implementações de seus idiomas para implementar o fechamento como o próprio "fechamento". A estrutura geralmente se parece com isso:
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 das funções e armazenadas em variáveis, para representar lambdas e permitindo que elas acessem seu ambiente envolvente, bem como o código da máquina para executar nesse contexto. Mas é apenas uma maneira (uma de muitas) de 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 atribui valores às variáveis livres contidas nessa expressão lambda, efetivamente fechando a expressão (transformando uma expressão lambda aberta , que ainda não pode ser avaliada ainda, em uma expressão lambda fechada , que pode ser avaliada, já que todos os símbolos contidos nela estão definidos).
Qualquer outra coisa é apenas um "culto à carga" e a "mágica do vôo-doo" de programadores e fornecedores de idiomas que desconhecem as raízes reais 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.
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 à qual foi atribuído:
foo();
Mais útil do que atribuir funções anônimas a variáveis, no entanto, são passá-las para ou de funções de ordem superior, ou seja, funções que aceitam / retornam outras funções. Em muitos desses casos, nomear uma função é 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 função nomeada ou anônima, mas é conhecido como tal quando "fecha sobre" variáveis no escopo em que a função é definida, ou seja, o fechamento ainda se refere ao ambiente com quaisquer variáveis externas usadas no fechamento próprio. 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 função e você passasse incrementX
para uma função 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 (y.x == 0 + 1)
y(); //returns 2 (y.x == 1 + 1)
É assim que você obtém objetos com estado na programação funcional. Como não é necessário nomear "incrementX", você pode usar um lambda neste caso:
function foo()
{ @x = 0;
return lambda()
{ x = x + 1;
return x;
};
}
Nem todos os fechamentos são lambdas e nem todos os lambdas são fechamentos. Ambas são funções, mas não necessariamente da maneira que estamos acostumados a conhecer.
Um lambda é essencialmente uma função definida em linha, e não o método padrão de declaração de funções. Lambdas podem frequentemente ser passadas como objetos.
Um fechamento é uma função que encerra seu estado circundante ao referenciar campos externos ao seu corpo. O estado fechado permanece através das invocações do fechamento.
Em uma linguagem orientada a objetos, os fechamentos são normalmente fornecidos por meio de objetos. No entanto, algumas linguagens OO (por exemplo, C #) implementam funcionalidades especiais mais próximas da definição de fechamentos fornecidos por linguagens puramente funcionais (como lisp) que não possuem objetos para incluir o estado.
O interessante é que a introdução de Lambdas e Closures em C # aproxima a programação funcional do uso convencional.
É simples assim: lambda é uma construção de linguagem, ou seja, simplesmente sintaxe para funções anônimas; um fechamento é uma técnica para implementá-lo - ou quaisquer funções de primeira classe, nomeadas ou anônimas.
Mais precisamente, um fechamento é como uma função de primeira classe é representada em tempo de execução, como um par de seu "código" e um ambiente "fechado" sobre todas as variáveis não locais usadas nesse código. Dessa forma, essas variáveis ainda estão acessíveis, mesmo quando os escopos externos de onde se originam já foram encerrados.
Infelizmente, existem muitos idiomas por aí que não suportam funções como valores de primeira classe ou apenas os suportam na forma aleijada. Portanto, 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 o aplicativo fornecem a maneira de criar a expressão lamdba e a redução prejudica o significado da expressão lambda.
O Lambda fornece uma maneira de abstrair o processo de computação. por exemplo, para calcular a soma de dois números, um processo que usa dois parâmetros x, y e retorna x + y pode ser abstraído. No esquema, você pode escrevê-lo como
(lambda (x y) (+ x y))
Você pode renomear os parâmetros, mas a tarefa que ele conclui não muda. Em quase todas as linguagens de programação, você pode dar um nome à expressão lambda, que são denominadas funções. Mas não há muita diferença, eles podem ser considerados conceitualmente apenas como açúcar de sintaxe.
OK, agora imagine como isso pode ser implementado. Sempre que aplicamos a expressão lambda em algumas expressões, por exemplo,
((lambda (x y) (+ x y)) 2 3)
Podemos simplesmente substituir os parâmetros pela expressão a ser avaliada. Este modelo já é muito poderoso. Mas esse modelo não nos permite alterar os valores dos símbolos, por exemplo, não podemos imitar a mudança de status. Portanto, precisamos de um modelo mais complexo. Para encurtar, 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). O restante (+ xy) é avaliado pesquisando 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 função:
(lambda (x y) (+ x y z))
Sabemos que quando avaliarmos a expressão lambda, xy será vinculado a uma nova tabela. Mas como e onde podemos procurar? 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 poderá ser determinado apenas pela ligação x e y. Para deixar isso claro, você pode escrever algo da seguinte maneira no esquema:
((lambda (z) (lambda (x y) (+ x y z))) 1)
Portanto, z seria vinculado a 1 em uma tabela externa. Ainda temos uma função que aceita dois parâmetros, mas o significado real dela também depende do ambiente externo. Em outras palavras, o ambiente externo se fecha sobre as variáveis livres. Com a ajuda de set !, podemos tornar a função com estado, ou seja, não é uma função no sentido de matemática. O que ele retorna não depende apenas da entrada, mas também de z.
Isso é algo que você já conhece muito bem, um método de objetos quase sempre depende do estado dos objetos. É por isso que algumas pessoas dizem que "fechamentos são objetos de homens pobres". Mas também podemos considerar objetos como fechamentos de homens pobres, pois realmente gostamos de funções de primeira classe.
Eu uso o esquema para ilustrar as idéias, devido a esse esquema, é uma das línguas mais antigas que possui fechamentos reais. Todos os materiais aqui são apresentados muito melhor no capítulo 3 do SICP.
Em resumo, lambda e fechamento são conceitos realmente diferentes. Um lambda é uma função. Um fechamento é um par de lambda e o ambiente correspondente que fecha o lambda.
O conceito é o mesmo que o descrito acima, mas se você é do fundo do PHP, isso explica mais detalhadamente o uso do código PHP.
$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, function ($v) { return $v > 2; });
função ($ v) {return $ v> 2; } é a definição da função lambda. Podemos até armazená-lo em uma variável, para que possa ser reutilizável:
$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ê precisaria escrever outra função lambda ou criar um fechamento (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 fechamento é uma função avaliada em seu próprio ambiente, que possui uma ou mais variáveis associadas que podem ser acessadas quando a função é chamada. Eles vêm do mundo da programação funcional, onde existem vários conceitos em jogo. Os fechamentos são como funções lambda, mas mais inteligentes no sentido de que eles têm a capacidade de interagir com variáveis 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();
Esta pergunta é antiga e tem muitas respostas.
Agora, com o Java 8 e o Official Lambda, que são projetos não oficiais de fechamento, ele revive a questão.
A resposta no contexto Java (via Lambdas e fechamentos - qual é a diferença? ):
"Um fechamento é uma expressão lambda emparelhada com um ambiente que vincula cada uma de suas variáveis livres a um valor. Em Java, as expressões lambda serão implementadas por meio de fechamentos, portanto os dois termos passaram a ser usados de forma intercambiável na comunidade".
Simplificando, o fechamento é um truque sobre o escopo, o lambda é uma função anônima. Podemos realizar o fechamento com lambda de maneira mais elegante e o lambda é frequentemente usado como um parâmetro passado para uma função superior
Uma expressão Lambda é apenas uma função anônima. em java simples, por exemplo, você pode escrever assim:
Function<Person, Job> mapPersonToJob = new Function<Person, Job>() {
public Job apply(Person person) {
Job job = new Job(person.getPersonId(), person.getJobDescription());
return job;
}
};
onde a classe Function é construída apenas no código java. Agora você pode ligar mapPersonToJob.apply(person)
para algum lugar para usá-lo. isso é apenas um exemplo. Isso é um lambda antes que houvesse sintaxe para ele. Lambdas é um atalho para isso.
Fecho:
um Lambda se torna um fechamento quando ele pode acessar as variáveis fora deste escopo. Eu acho que você pode dizer sua mágica, ele pode envolver magicamente o ambiente em que foi criado e usar as variáveis fora de seu escopo (escopo externo., para ficar claro, um fechamento significa que um lambda pode acessar seu ESCOPO EXTERIOR.
no Kotlin, um lambda sempre pode acessar seu fechamento (as variáveis que estão em seu escopo externo)
Depende se uma função usa variável externa ou não para executar a operação.
Variáveis externas - variáveis definidas fora do escopo de uma função.
As expressões Lambda são sem estado porque dependem de parâmetros, variáveis internas ou constantes para executar operações.
Function<Integer,Integer> lambda = t -> {
int n = 2
return t * n
}
Os fechamentos mantêm o estado porque usam variáveis externas (isto é, variáveis definidas fora do escopo do corpo da função) junto com parâmetros e constantes para executar operações.
int n = 2
Function<Integer,Integer> closure = t -> {
return t * n
}
Quando o Java cria o fechamento, ele mantém a variável n com a função para que possa ser referenciada quando passada para outras funções ou usada em qualquer lugar.