TL; DR Como outros apontaram: a notação lambda é apenas uma maneira de definir funções sem ser forçado a dar um nome a elas.
Versão longa
Eu gostaria de elaborar um pouco sobre esse tópico porque acho muito interessante. Disclaimer: Eu fiz meu curso de cálculo lambda há muito tempo. Se alguém com melhor conhecimento encontrar alguma imprecisão na minha resposta, fique à vontade para me ajudar a melhorá-la.
Vamos começar com expressões, por exemplo, 1 + 2
e x + 2
. Literais como 1
e 2
são chamados constantes porque estão vinculados a valores fixos específicos.
Um identificador como x
é chamado variável e, para avaliá-lo, é necessário vinculá-lo a algum valor primeiro. Então, basicamente, você não pode avaliar x + 1
enquanto não souber o que x
é.
A notação lambda fornece um esquema para vincular valores de entrada específicos a variáveis. Uma expressão lambda pode ser formada adicionando λx .
na frente de uma expressão existente, por exemplo λx . x + 1
. Variável x
é dito ser livre em x + 1
e ligados emλx . x + 1
Como isso ajuda na avaliação de expressões? Se você alimentar um valor para a expressão lambda, assim
(λx . x + 1) 2
então você pode avaliar toda a expressão substituindo (vinculando) todas as ocorrências da variável x
pelo valor 2:
(λx . x + 1) 2
2 + 1
3
Portanto, a notação lambda fornece um mecanismo geral para vincular itens a variáveis que aparecem em um bloco de expressão / programa. Dependendo do contexto, isso cria conceitos visivelmente diferentes nas linguagens de programação:
- Em uma linguagem puramente funcional como Haskell, as expressões lambda representam funções no sentido matemático: um valor de entrada é injetado no corpo do lambda e um valor de saída é produzido.
- Em muitas línguas (por exemplo, JavaScript, Python, Scheme), avaliar o corpo de uma expressão lambda pode ter efeitos colaterais. Nesse caso, pode-se usar o termo procedimento para marcar a diferença em funções puras.
Além das diferenças, a notação lambda é sobre definir parâmetros formais e vinculá-los aos parâmetros reais.
O próximo passo é atribuir um nome a uma função / procedimento. Em vários idiomas, funções são valores como qualquer outro, portanto, você pode nomear uma função da seguinte maneira:
(define f (lambda (x) (+ x 1))) ;; Scheme
f = \x -> x + 1 -- Haskell
val f: (Int => Int) = x => x + 1 // Scala
var f = function(x) { return x + 1 } // JavaScript
f = lambda x: x + 1 # Python
Como Eli Barzilay apontou, essas definições apenas vinculam o nome f
a um valor, que passa a ser uma função. Portanto, nesse aspecto, funções, números, seqüências de caracteres e caracteres são todos valores que podem ser associados a nomes da mesma maneira:
(define n 42) ;; Scheme
n = 42 -- Haskell
val n: Int = 42 // Scala
var n = 42 // JavaScript
n = 42 # Python
Nesses idiomas, você também pode vincular uma função a um nome usando a notação mais familiar (mas equivalente):
(define (f x) (+ x 1)) ;; Scheme
f x = x + 1 -- Haskell
def f(x: Int): Int = x + 1 // Scala
function f(x) { return x + 1 } // JavaScript
def f(x): return x + 1 # Python
Alguns idiomas, por exemplo, C, suportam apenas a última notação para definir funções (nomeadas).
Encerramentos
Algumas observações finais sobre fechamentos . Considere a expressão x + y
. Isso contém duas variáveis livres. Se você ligar x
usando a notação lambda, obtém:
\x -> x + y
Essa ainda não é uma função porque ainda contém uma variável livre y
. Você pode criar uma função vinculando y
também:
\x -> \y -> x + y
ou
\x y -> x + y
que é igual à +
função.
Mas você pode vincular, digamos, y
de outra maneira (*):
incrementBy y = \x -> x + y
O resultado da aplicação da função incrementBy a um número é um fechamento, ou seja, uma função / procedimento cujo corpo contém uma variável livre (por exemplo y
) que foi vinculada a um valor do ambiente em que o fechamento foi definido.
O mesmo incrementBy 5
acontece com a função (fechamento) que incrementa os números em 5.
NOTA (*)
Estou trapaceando um pouco aqui:
incrementBy y = \x -> x + y
é equivalente a
incrementBy = \y -> \x -> x + y
então o mecanismo de ligação é o mesmo. Intuitivamente, acho que um fechamento representa um pedaço de uma expressão lambda mais complexa. Quando essa representação é criada, algumas das ligações da expressão mãe já foram definidas e o fechamento as utiliza posteriormente quando é avaliada / invocada.