TL; DR Como han señalado otros: la notación lambda es solo una forma de definir funciones sin que se las obligue a darles un nombre.
Versión larga
Me gustaría elaborar un poco sobre este tema porque me parece muy interesante. Descargo de responsabilidad: he tomado mi curso en el cálculo lambda hace mucho tiempo. Si alguien con mejor conocimiento encuentra alguna inexactitud en mi respuesta, no dude en ayudarme a mejorarla.
Comencemos con expresiones, por ejemplo, 1 + 2
y x + 2
. Los literales como 1
y 2
se llaman constantes porque están vinculados a valores fijos específicos.
Un identificador como x
se llama variable y para evaluarla, primero debe vincularlo a algún valor. Por lo tanto, básicamente no puedes evaluar x + 1
mientras no sepas qué es x
.
La notación lambda proporciona un esquema para vincular valores de entrada específicos a variables. Una expresión lambda se puede formar agregando λx .
delante de una expresión existente, por ejemplo. %código%. Se dice que la variable λx . x + 1
está libre en x
y enlazada en x + 1
¿Cómo ayuda esto a evaluar expresiones? Si alimenta un valor a la expresión lambda, de este modo
(λx . x + 1) 2
luego puedes evaluar la expresión completa reemplazando (enlazando) todas las apariciones de la variable λx . x + 1
con el valor 2:
(λx . x + 1) 2
2 + 1
3
Entonces, la notación lambda proporciona un mecanismo general para vincular cosas a variables que aparecen en un bloque de expresión / programa. Dependiendo del contexto, esto crea conceptos muy diferentes en los lenguajes de programación:
- En un lenguaje puramente funcional como Haskell, las expresiones lambda representan funciones en el sentido matemático: un valor de entrada se inyecta en el cuerpo de la lambda y se produce un valor de salida.
- En muchos idiomas (por ejemplo, JavaScript, Python, Scheme), evaluar el cuerpo de una expresión lambda puede tener efectos secundarios. En este caso, se puede usar el término procedimiento para marcar la diferencia entre las funciones puras.
Aparte de las diferencias, la notación lambda trata sobre la definición de parámetros formales y su vinculación con los parámetros reales.
El siguiente paso es dar un nombre a una función / procedimiento.
En varios idiomas, las funciones son valores como cualquier otro, por lo que puede asignar un nombre a la función de la siguiente manera:
(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 señaló Eli Barzilay, esta definición simplemente vincula el nombre x
a un valor, que es una función. Entonces, a este respecto, las funciones, los números, las cadenas y los caracteres son todos valores que pueden vincularse a los nombres de la misma manera:
(define n 42) ;; Scheme
n = 42 -- Haskell
val n: Int = 42 // Scala
var n = 42 // JavaScript
n = 42 # Python
En estos idiomas, también puede vincular una función a un nombre usando la notación más familiar (pero 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
Algunos idiomas, por ejemplo, C, solo admite la última notación para definir funciones (nombradas).
Cierros
Algunas observaciones finales sobre cierres . Considera la expresión f
. Esto contiene dos variables libres. Si unes x + y
usando la notación lambda obtienes:
\x -> x + y
Esto no es (todavía) una función porque todavía contiene una variable libre x
. Podrías hacer una función fuera de ella también vinculando y
:
\x -> \y -> x + y
o
\x y -> x + y
que es exactamente igual a la función y
.
Pero puede vincular, digamos, +
de otra manera (*):
incrementBy y = \x -> x + y
El resultado de aplicar la función incrementBy a un número es un cierre, es decir, una función / procedimiento cuyo cuerpo contiene una variable libre (por ejemplo, y
) que se ha vinculado a un valor del entorno en el que se definió el cierre.
Por lo tanto, y
es la función (cierre) que incrementa los números en 5.
NOTA (*)
Estoy haciendo trampa un poco aquí:
incrementBy y = \x -> x + y
es equivalente a
incrementBy = \y -> \x -> x + y
por lo que el mecanismo de enlace es el mismo. Intuitivamente, pienso que un cierre representa una parte de una expresión lambda más compleja. Cuando se crea esta representación, algunos de los enlaces de la expresión madre ya se han establecido y el cierre los utiliza más adelante cuando se evalúa / invoca.