¿Alguien podría explicar? Entiendo los conceptos básicos detrás de ellos, pero a menudo veo que se usan indistintamente y me confundo.
Y ahora que estamos aquí, ¿en qué se diferencian de una función normal?
¿Alguien podría explicar? Entiendo los conceptos básicos detrás de ellos, pero a menudo veo que se usan indistintamente y me confundo.
Y ahora que estamos aquí, ¿en qué se diferencian de una función normal?
Respuestas:
Una lambda es solo una función anónima, una función definida sin nombre. En algunos idiomas, como Scheme, son equivalentes a funciones con nombre. De hecho, la definición de la función se reescribe como un enlace interno de una lambda a una variable. En otros lenguajes, como Python, hay algunas distinciones (bastante innecesarias) entre ellos, pero de lo contrario se comportan de la misma manera.
Un cierre es cualquier función que se cierra sobre el entorno en el que se definió. Esto significa que puede acceder a variables que no están en su lista de parámetros. Ejemplos:
def func(): return h
def anotherfunc(h):
return func()
Esto provocará un error, porque func
no se cierra sobre el entorno en anotherfunc
- h
está indefinido. func
solo cierra sobre el entorno global. Esto funcionará:
def anotherfunc(h):
def func(): return h
return func()
Porque aquí, func
se define en anotherfunc
, y en Python 2.3 y superior (o un número como este) cuando casi se cierran correctamente (la mutación todavía no funciona), esto significa que se cierra sobre anotherfunc
el entorno y puede acceder a variables dentro de eso. En Python 3.1+, la mutación también funciona cuando se usa la nonlocal
palabra clave .
Otro punto importante: func
continuará cerrándose sobre anotherfunc
el entorno incluso cuando ya no se esté evaluando anotherfunc
. Este código también funcionará:
def anotherfunc(h):
def func(): return h
return func
print anotherfunc(10)()
Esto imprimirá 10.
Esto, como notará, no tiene nada que ver con lambda s: son dos conceptos diferentes (aunque relacionados).
Hay mucha confusión sobre lambdas y cierres, incluso en las respuestas a esta pregunta de StackOverflow aquí. En lugar de preguntar a los programadores aleatorios que aprendieron acerca de los cierres de la práctica con ciertos lenguajes de programación u otros programadores despistados, emprendan un viaje hacia la fuente (donde todo comenzó). Y dado que las lambdas y los cierres provienen del cálculo Lambda inventado por Alonzo Church en los años 30 antes de que existieran las primeras computadoras electrónicas, esta es la fuente de la que estoy hablando.
Lambda Calculus es el lenguaje de programación más simple del mundo. Las únicas cosas que puedes hacer en él: ►
f x
. f
está la función y x
es su único parámetro)λ
(lambda), luego el nombre simbólico (por ejemplo x
), luego un punto .
antes de la expresión. Esto convierte la expresión en una función que espera un parámetro . λx.x+2
toma la expresión x+2
y le dice que el símbolo x
en esta expresión es una variable enlazada ; se puede sustituir con un valor que usted proporcione como parámetro. (λx.x+2) 7
. Luego, la expresión (en este caso, un valor literal) 7
se sustituye como x
en la subexpresión x+2
de la lambda aplicada, por lo que se obtiene 7+2
, que luego se reduce a 9
las reglas aritméticas comunes.Así que hemos resuelto uno de los misterios:
lambda es la función anónima del ejemplo anterior λx.x+2
,.
function(x) { return x+2; }
e inmediatamente puede aplicarlo a algún parámetro como este:
(function(x) { return x+2; })(7)
o puede almacenar esta función anónima (lambda) en alguna variable:
var f = function(x) { return x+2; }
que efectivamente le da un nombre f
, lo que le permite consultarlo y llamarlo varias veces más tarde, por ejemplo:
alert( f(7) + f(10) ); // should print 21 in the message box
Pero no tenías que nombrarlo. Podrías llamarlo de inmediato:
alert( function(x) { return x+2; } (7) ); // should print 9 in the message box
En LISP, las lambdas se hacen así:
(lambda (x) (+ x 2))
y puede llamar a tal lambda aplicándola inmediatamente a un parámetro:
( (lambda (x) (+ x 2)) 7 )
Como dije, lo que hace la abstracción lambda es unir un símbolo en su subexpresión, de modo que se convierta en un parámetro sustituible . Tal símbolo se llama obligado . Pero, ¿y si hay otros símbolos en la expresión? Por ejemplo: λx.x/y+2
. En esta expresión, el símbolo x
está obligado por la abstracción lambda que lo λx.
precede. Pero el otro símbolo, y
no está limitado, es gratis . No sabemos qué es y de dónde viene, así que no sabemos qué significa y qué valor representa, y por lo tanto no podemos evaluar esa expresión hasta que descubramos qué y
significa.
De hecho, lo mismo ocurre con los otros dos símbolos, 2
y +
. Es solo que estamos tan familiarizados con estos dos símbolos que generalmente olvidamos que la computadora no los conoce y tenemos que decirle lo que significan definiéndolos en algún lugar, por ejemplo, en una biblioteca o en el propio lenguaje.
Puede pensar en los símbolos libres como se define en otro lugar, fuera de la expresión, en su "contexto circundante", que se llama su entorno . El entorno podría ser una expresión más grande de la que forma parte esta expresión (como dijo Qui-Gon Jinn: "Siempre hay un pez más grande";)), o en alguna biblioteca, o en el lenguaje mismo (como primitivo ).
Esto nos permite dividir las expresiones lambda en dos categorías:
Puede CERRAR una expresión lambda abierta suministrando el entorno , que define todos estos símbolos libres uniéndolos a algunos valores (que pueden ser números, cadenas, funciones anónimas, también conocidas como lambdas, lo que sea ...).
Y aquí viene la parte de cierre :
el cierre de una expresión lambda es este conjunto particular de símbolos definidos en el contexto externo (entorno) que dan valores a los símbolos libres en esta expresión, haciéndolos ya no libres. Convierte una expresión lambda abierta , que todavía contiene algunos símbolos libres "indefinidos", en una cerrada , que ya no tiene símbolos libres.
Por ejemplo, si tiene la siguiente expresión lambda:, λx.x/y+2
el símbolo x
está enlazado, mientras que el símbolo y
está libre, por lo tanto, la expresión es open
y no puede evaluarse a menos que diga qué y
significa (y lo mismo con +
y 2
, que también están libres). Pero suponga que también tiene un entorno como este:
{ y: 3,
+: [built-in addition],
2: [built-in number],
q: 42,
w: 5 }
Este entorno suministros definiciones para todos los símbolos "indefinido" (libres) de nuestro expresión lambda ( y
, +
, 2
), y varios símbolos adicionales ( q
, w
). Los símbolos que necesitamos definir son este subconjunto del entorno:
{ y: 3,
+: [built-in addition],
2: [built-in number] }
y este es precisamente el cierre de nuestra expresión lambda:>
En otras palabras, cierra una expresión lambda abierta. Aquí es de donde vino el cierre del nombre en primer lugar, y es por eso que las respuestas de tantas personas en este hilo no son del todo correctas: P
Bueno, los marketoides corporativos de Sun / Oracle, Microsoft, Google, etc. tienen la culpa, porque eso es lo que llamaron estas construcciones en sus lenguajes (Java, C #, Go, etc.). A menudo llaman "cierres" lo que se supone que son solo lambdas. O llaman a los "cierres" una técnica particular que usaron para implementar el alcance léxico, es decir, el hecho de que una función puede acceder a las variables que se definieron en su alcance externo en el momento de su definición. A menudo dicen que la función "encierra" estas variables, es decir, las captura en alguna estructura de datos para evitar que se destruyan una vez que la función externa termina de ejecutarse. Pero esto es solo una "etimología del folklore" inventada post factum y marketing, que solo hace las cosas más confusas,
Y es aún peor por el hecho de que siempre hay un poco de verdad en lo que dicen, lo que no le permite descartarlo fácilmente como falso: P Permítanme explicar:
Si desea implementar un lenguaje que use lambdas como ciudadanos de primera clase, debe permitirles usar símbolos definidos en su contexto (es decir, usar variables libres en sus lambdas). Y estos símbolos deben estar allí incluso cuando la función circundante regrese. El problema es que estos símbolos están vinculados a algún almacenamiento local de la función (generalmente en la pila de llamadas), que ya no estará allí cuando la función regrese. Por lo tanto, para que una lambda funcione de la manera esperada, debe de alguna manera "capturar" todas estas variables libres de su contexto externo y guardarlas para más adelante, incluso cuando el contexto externo se haya ido. Es decir, necesitas encontrar el cierrede su lambda (todas estas variables externas que usa) y almacénelo en otro lugar (ya sea haciendo una copia o preparando espacio para ellos por adelantado, en otro lugar que no sea en la pila). El método real que utiliza para lograr este objetivo es un "detalle de implementación" de su idioma. Lo importante aquí es el cierre , que es el conjunto de variables libres del entorno de su lambda que deben guardarse en algún lugar.
Las personas no tardaron demasiado en comenzar a llamar a la estructura de datos real que usan en las implementaciones de su lenguaje para implementar el cierre como el "cierre" en sí. La estructura generalmente se ve así:
Closure {
[pointer to the lambda function's machine code],
[pointer to the lambda function's environment]
}
y estas estructuras de datos se pasan como parámetros a otras funciones, se devuelven de las funciones y se almacenan en variables, para representar lambdas, y les permiten acceder a su entorno envolvente y al código de máquina para ejecutarse en ese contexto. Pero es solo una forma (una de muchas) de implementar el cierre, no el cierre en sí.
Como expliqué anteriormente, el cierre de una expresión lambda es el subconjunto de definiciones en su entorno que dan valores a las variables libres contenidas en esa expresión lambda, cerrando efectivamente la expresión (convirtiendo una expresión lambda abierta , que aún no se puede evaluar, en una expresión lambda cerrada , que luego puede evaluarse, ya que todos los símbolos contenidos en ella ahora están definidos).
Cualquier otra cosa es solo un "culto a la carga" y una "magia voo-doo" de programadores y vendedores de idiomas que desconocen las verdaderas raíces de estas nociones.
Espero que eso responda tus preguntas. Pero si tiene alguna pregunta de seguimiento, no dude en hacerla en los comentarios, y trataré de explicarla mejor.
Cuando la mayoría de las personas piensan en funciones , piensan en funciones con nombre :
function foo() { return "This string is returned from the 'foo' function"; }
Estos se llaman por su nombre, por supuesto:
foo(); //returns the string above
Con expresiones lambda , puede tener funciones anónimas :
@foo = lambda() {return "This is returned from a function without a name";}
Con el ejemplo anterior, puede llamar al lambda a través de la variable a la que se le asignó:
foo();
Sin embargo, es más útil que asignar funciones anónimas a variables, pasarlas ao desde funciones de orden superior, es decir, funciones que aceptan / devuelven otras funciones. En muchos de estos casos, nombrar una función no es necesario:
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)});
Un cierre puede ser una función nombrada o anónima, pero se conoce como tal cuando se "cierra sobre" las variables en el ámbito donde se define la función, es decir, el cierre aún se referirá al entorno con cualquier variable externa que se use en el cierre en sí mismo. Aquí hay un cierre con nombre:
@x = 0;
function incrementX() { x = x + 1;}
incrementX(); // x now equals 1
Eso no parece mucho, pero ¿y si todo esto estuviera en otra función y pasaras incrementX
a una función 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)
Así es como se obtienen objetos con estado en la programación funcional. Como no es necesario nombrar "incrementX", puede usar una lambda en este caso:
function foo()
{ @x = 0;
return lambda()
{ x = x + 1;
return x;
};
}
No todos los cierres son lambdas y no todos los lambdas son cierres. Ambas son funciones, pero no necesariamente en la forma en que estamos acostumbrados a saber.
Una lambda es esencialmente una función que se define en línea en lugar del método estándar de declarar funciones. Las lambdas con frecuencia se pueden pasar como objetos.
Un cierre es una función que encierra su estado circundante haciendo referencia a campos externos a su cuerpo. El estado cerrado permanece a través de invocaciones del cierre.
En un lenguaje orientado a objetos, los cierres se proporcionan normalmente a través de objetos. Sin embargo, algunos lenguajes OO (por ejemplo, C #) implementan una funcionalidad especial que está más cerca de la definición de cierres proporcionada por lenguajes puramente funcionales (como lisp) que no tienen objetos para encerrar el estado.
Lo interesante es que la introducción de Lambdas y Closures en C # acerca la programación funcional al uso general.
Es tan simple como esto: lambda es una construcción de lenguaje, es decir, simplemente sintaxis para funciones anónimas; un cierre es una técnica para implementarlo, o cualquier función de primera clase, de hecho, nombrada o anónima.
Más precisamente, un cierre es cómo se representa una función de primera clase en tiempo de ejecución, como un par de su "código" y un entorno "cerrado" sobre todas las variables no locales utilizadas en ese código. De esta manera, esas variables aún son accesibles incluso cuando los ámbitos externos donde se originan ya se han salido.
Desafortunadamente, hay muchos idiomas que no admiten funciones como valores de primera clase, o solo los admiten en forma inválida. Por lo tanto, las personas a menudo usan el término "cierre" para distinguir "lo real".
Desde el punto de vista de los lenguajes de programación, son completamente dos cosas diferentes.
Básicamente, para un lenguaje completo de Turing solo necesitamos elementos muy limitados, por ejemplo, abstracción, aplicación y reducción. La abstracción y la aplicación proporcionan la forma en que puede construir la expresión lamdba, y la reducción determina el significado de la expresión lambda.
Lambda proporciona una manera de abstraer el proceso de cálculo. por ejemplo, para calcular la suma de dos números, se puede extraer un proceso que toma dos parámetros x, y y devuelve x + y. En el esquema, puedes escribirlo como
(lambda (x y) (+ x y))
Puede cambiar el nombre de los parámetros, pero la tarea que completa no cambia. En casi todos los lenguajes de programación, puede asignar un nombre a la expresión lambda, que se denominan funciones. Pero no hay mucha diferencia, pueden considerarse conceptualmente como solo azúcar de sintaxis.
Bien, ahora imagina cómo se puede implementar esto. Cada vez que aplicamos la expresión lambda a algunas expresiones, p. Ej.
((lambda (x y) (+ x y)) 2 3)
Simplemente podemos sustituir los parámetros con la expresión a evaluar. Este modelo ya es muy poderoso. Pero este modelo no nos permite cambiar los valores de los símbolos, por ejemplo, no podemos imitar el cambio de estado. Por lo tanto, necesitamos un modelo más complejo. Para abreviar, siempre que queramos calcular el significado de la expresión lambda, colocamos el par de símbolos y el valor correspondiente en un entorno (o tabla). Luego, el resto (+ xy) se evalúa buscando los símbolos correspondientes en la tabla. Ahora, si proporcionamos algunas primitivas para operar directamente en el entorno, ¡podemos modelar los cambios de estado!
Con estos antecedentes, verifique esta función:
(lambda (x y) (+ x y z))
Sabemos que cuando evaluamos la expresión lambda, xy se vinculará en una nueva tabla. Pero, ¿cómo y dónde podemos buscar z? En realidad, z se llama variable libre. Debe haber un entorno externo que contenga z. De lo contrario, el significado de la expresión no se puede determinar solo vinculando x e y. Para aclarar esto, puede escribir algo de la siguiente manera en el esquema:
((lambda (z) (lambda (x y) (+ x y z))) 1)
Entonces z estaría unido a 1 en una tabla externa. Aún obtenemos una función que acepta dos parámetros, pero el significado real de la misma también depende del entorno externo. En otras palabras, el entorno externo se cierra con las variables libres. Con la ayuda de set !, podemos hacer que la función tenga estado, es decir, no es una función en el sentido de las matemáticas. Lo que devuelve no solo depende de la entrada, sino también de z.
Esto es algo que ya sabes muy bien, un método de objetos casi siempre se basa en el estado de los objetos. Es por eso que algunas personas dicen que "los cierres son objetos de los pobres". Pero también podríamos considerar los objetos como cierres de los pobres, ya que realmente nos gustan las funciones de primera clase.
Utilizo el esquema para ilustrar las ideas debido a que ese esquema es uno de los primeros idiomas que tiene cierres reales. Todos los materiales aquí se presentan mucho mejor en el capítulo 3 del SICP.
En resumen, lambda y cierre son conceptos realmente diferentes. Una lambda es una función. Un cierre es un par de lambda y el entorno correspondiente que cierra el lambda.
El concepto es el mismo que se describió anteriormente, pero si usted es de un fondo PHP, esto explica más a fondo el uso del código PHP.
$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, function ($v) { return $v > 2; });
función ($ v) {return $ v> 2; } es la definición de la función lambda. Incluso podemos almacenarlo en una variable, por lo que puede ser reutilizable:
$max = function ($v) { return $v > 2; };
$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, $max);
Ahora, ¿qué pasa si desea cambiar el número máximo permitido en la matriz filtrada? Tendría que escribir otra función lambda o crear un cierre (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));
Un cierre es una función que se evalúa en su propio entorno, que tiene una o más variables enlazadas a las que se puede acceder cuando se llama a la función. Vienen del mundo de la programación funcional, donde hay una serie de conceptos en juego. Los cierres son como las funciones lambda, pero más inteligentes en el sentido de que tienen la capacidad de interactuar con variables del entorno exterior de donde se define el cierre.
Aquí hay un ejemplo más simple de cierre de PHP:
$string = "Hello World!";
$closure = function() use ($string) { echo $string; };
$closure();
Esta pregunta es antigua y tiene muchas respuestas.
Ahora con Java 8 y Lambda oficial que son proyectos de cierre no oficiales, revive la pregunta.
La respuesta en contexto Java (a través de Lambdas y cierres: ¿cuál es la diferencia? ):
"Un cierre es una expresión lambda combinada con un entorno que une cada una de sus variables libres a un valor. En Java, las expresiones lambda se implementarán por medio de cierres, por lo que los dos términos se han utilizado indistintamente en la comunidad".
Simplemente hablando, el cierre es un truco sobre el alcance, lambda es una función anónima. Podemos realizar el cierre con lambda de manera más elegante y lambda se usa a menudo como un parámetro pasado a una función superior
Una expresión Lambda es solo una función anónima. en Java simple, por ejemplo, puedes escribirlo así:
Function<Person, Job> mapPersonToJob = new Function<Person, Job>() {
public Job apply(Person person) {
Job job = new Job(person.getPersonId(), person.getJobDescription());
return job;
}
};
donde la clase Function solo está construida en código java. Ahora puedes llamar a mapPersonToJob.apply(person)
algún lugar para usarlo. ese es solo un ejemplo. Eso es una lambda antes de que hubiera sintaxis para ello. Lambdas un atajo para esto.
Cierre:
un Lambda se convierte en un cierre cuando puede acceder a las variables fuera de este alcance. Supongo que puedes decir que es mágico, mágicamente puede envolverse alrededor del entorno en el que se creó y usar las variables fuera de su alcance (alcance externo, por lo que, para ser claros, un cierre significa que una lambda puede acceder a su ALCANCE EXTERIOR.
en Kotlin, una lambda siempre puede acceder a su cierre (las variables que están en su alcance externo)
Depende de si una función utiliza una variable externa o no para realizar la operación.
Variables externas : variables definidas fuera del alcance de una función.
Las expresiones lambda no tienen estado porque depende de parámetros, variables internas o constantes para realizar operaciones.
Function<Integer,Integer> lambda = t -> {
int n = 2
return t * n
}
Los cierres mantienen el estado porque utiliza variables externas (es decir, variables definidas fuera del alcance del cuerpo de la función) junto con parámetros y constantes para realizar operaciones.
int n = 2
Function<Integer,Integer> closure = t -> {
return t * n
}
Cuando Java crea el cierre, mantiene la variable n con la función para que pueda ser referenciada cuando se pasa a otras funciones o se usa en cualquier lugar.