¿Cuál es la diferencia entre un ‘cierre’ y un ‘lambda’?

¿Alguien podría explicar? Entiendo los conceptos básicos detrás de ellos, pero a menudo los veo intercambiados y me confundo.

Y ahora que estamos aquí, ¿cómo difieren de una función normal?

Una lambda es solo una función anónima, una función definida sin nombre. En algunos idiomas, como Scheme, son equivalentes a funciones nombradas. De hecho, la definición de función se reescribe como vinculante a lambda a una variable internamente. En otros idiomas, como Python, hay algunas distinciones (más bien innecesarias) entre ellos, pero se comportan de la misma manera de lo contrario.

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 causará un error, porque func no se cierra sobre el entorno en anotherfunch no está definido. func solo se cierra en 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 en algún número como este) cuando casi logran cierres correctos (la mutación aún no funciona), esto significa que cierra el anotherfunc de anotherfunc y puede acceder variables dentro de ella. En Python 3.1+, la mutación también funciona cuando se usa la palabra clave nonlocal .

Otro punto importante: func continuará anotherfunc sobre el entorno de otra organización incluso cuando ya no se evalúe en otra anotherfunc . Este código también funcionará:

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

Esto imprimirá 10.

Esto, como usted nota, no tiene nada que ver con lambda s – son dos conceptos diferentes (aunque relacionados).

Cuando la mayoría de las personas piensa en funciones , piensan en funciones nombradas :

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

Estos son llamados por su nombre, por supuesto:

 foo(); //returns the string above 

Con las 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 a la lambda a través de la variable a la que se le asignó:

 foo(); 

Sin embargo, son más útiles que la asignación de funciones anónimas a las variables, ya que las transfieren a funciones de orden superior o desde ellas, es decir, funciones que aceptan / devuelven otras funciones. En muchos de estos casos, nombrar una función es innecesario:

 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 “cierra” variables en el ámbito donde se define la función, es decir, el cierre todavía 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 (yx == 0 + 1) y(); //returns 2 (yx == 1 + 1) 

Así es como obtienes objetos con estado en la progtwigción funcional. Dado que no es necesario nombrar “incrementX”, puede usar un lambda en este caso:

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

Existe una gran confusión sobre lambdas y cierres, incluso en las respuestas a esta pregunta de StackOverflow aquí. En lugar de preguntar a los progtwigdores aleatorios que aprendieron sobre los cierres de la práctica con ciertos lenguajes de progtwigción u otros progtwigdores despistados, hagan un viaje a la fuente (donde todo comenzó). Y dado que las lambdas y los cierres provienen de Lambda Calculus 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 progtwigción más simple del mundo. Lo único que puedes hacer con eso: ►

  • APLICACIÓN: aplicar una expresión a otra, denotada fx .
    (Piense en ello como una llamada a función , donde f es la función x es su único parámetro)
  • ABSTRACCIÓN: enlaza un símbolo que aparece en una expresión para marcar que este símbolo es solo un “espacio”, un cuadro en blanco que espera ser llenado con valor, una “variable” por así decirlo. Se realiza anteponiendo una letra griega λ (lambda), luego el nombre simbólico (por ejemplo, x ), luego un punto . antes de la expresión. Esto luego convierte la expresión en una función que espera un parámetro .
    Por ejemplo: λx.x+2 toma la expresión x+2 y dice que el símbolo x en esta expresión es una variable ligada – puede ser sustituido por un valor que usted suministre como parámetro.
    Tenga en cuenta que la función definida de esta manera es anónima , no tiene un nombre, por lo que no puede referirse a ella todavía, pero puede llamarla inmediatamente (¿recuerda la aplicación?) Proporcionándole el parámetro que está esperando, como esto: (λx.x+2) 7 . Entonces 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 obtienes 7+2 , que luego se reduce a 9 por las reglas de aritmética comunes.

Así que hemos resuelto uno de los misterios:
lambda es la función anónima del ejemplo anterior, λx.x+2 .


En diferentes lenguajes de progtwigción, la syntax para la abstracción funcional (lambda) puede diferir. Por ejemplo, en JavaScript se ve así:

 function(x) { return x+2; } 

y puedes aplicarlo de inmediato a un 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 de hecho 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 tienes que nombrarlo. Puedes llamarlo inmediatamente:

 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 invocar dicho lambda aplicándolo inmediatamente a un parámetro:

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


OK, ahora es el momento de resolver el otro misterio: qué es un cierre . Para hacer eso, hablemos de símbolos ( variables ) en expresiones lambda.

Como dije, lo que hace la abstracción lambda es vincular un símbolo en su subexpresión, de modo que se convierta en un parámetro sustituible. Tal símbolo se llama ligado . 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á vinculado por la abstracción lambda λx. precediéndolo Pero el otro símbolo, y , no está vinculado, es gratis . No sabemos qué es ni de dónde viene, por lo que no sabemos qué significa ni qué valor representa, y por lo tanto no podemos evaluar esa expresión hasta que descubramos qué significa y .

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 nos olvidamos de que la computadora no los conoce y debemos decir lo que significan al definirlos en alguna parte, por ejemplo, en una biblioteca o en el idioma en sí.

Puedes pensar en los símbolos libres como definidos en otro lugar, fuera de la expresión, en su “contexto circundante”, que se llama su entorno . El entorno puede 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:

  • Expresiones CERRADAS: cada símbolo que aparece en estas expresiones está vinculado por alguna abstracción lambda. En otras palabras, son autónomos ; no requieren ningún contexto circundante para ser evaluado. También se llaman combinadores .
  • Expresiones ABIERTAS: algunos símbolos en estas expresiones no están vinculados , es decir, algunos de los símbolos que aparecen en ellos son gratuitos y requieren cierta información externa, por lo que no pueden evaluarse hasta que proporcione las definiciones de estos símbolos.

Puede CERRAR una expresión lambda abierta suministrando el entorno , que define todos estos símbolos libres vinculándolos a algunos valores (que pueden ser números, cadenas, funciones anónimas aka 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 no libres más. Convierte una expresión lambda abierta , que todavía contiene algunos símbolos libres “indefinidos”, en una cerrada , que ya no tiene ningún símbolo libre.

Por ejemplo, si tiene la siguiente expresión lambda: λx.x/y+2 , el símbolo x está vinculado, mientras que el símbolo y es libre, por lo tanto, la expresión está open y no se puede evaluar a menos que diga qué y quiere decir (y el lo mismo con + y 2 , que también son gratis). Pero supongamos que también tiene un entorno como este:

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

Este entorno proporciona definiciones para todos los símbolos “indefinidos” (libres) de nuestra 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, y esta es la razón por la cual las respuestas de muchas personas en este hilo no son del todo correctas: P


Entonces, ¿por qué están equivocados? ¿Por qué tantos de ellos dicen que los cierres son algunas estructuras de datos en la memoria, o algunas características de los idiomas que usan, o por qué confunden los cierres con lambdas? :PAG

Bueno, los marketoids corporativos de Sun / Oracle, Microsoft, Google, etc. tienen la culpa, porque eso es lo que llamaron estos constructos en sus idiomas (Java, C #, Go, etc.). A menudo llaman “cierres”, que se supone que son solo lambdas. O llaman a los “cierres” una técnica particular que utilizaron para implementar el scope léxico, es decir, el hecho de que una función puede acceder a las variables que se definieron en su ámbito 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 después de que la función externa termine de ejecutarse. Pero esto es simplemente una “etimología del folklore” post-factum y marketing, que solo hace las cosas más confusas, porque cada vendedor de idiomas usa su propia terminología.

Y es aún peor por el hecho de que siempre hay algo de verdad en lo que dicen, lo que no le permite descartarlo fácilmente como falso: P Déjenme 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 ligados a algún almacenamiento local de la función (generalmente en la stack 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 “capturar” de algún modo todas estas variables libres de su contexto externo y guardarlas para más adelante, incluso cuando el contexto externo desaparezca. Es decir, debe encontrar el cierre de su lambda (todas estas variables externas que utiliza) y almacenarlo en otro lugar (ya sea haciendo una copia, o preparando espacio para ellos por adelantado, en otro lugar que no sea en la stack). El método real que utiliza para lograr este objective 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.

No tomó mucho tiempo para que las personas comenzaran a llamar a la estructura de datos real que utilizan en las implementaciones de su idioma para implementar el cierre como el “cierre” en sí mismo. La estructura generalmente se ve más o menos 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 desde funciones y se almacenan en variables, para representar lambdas, y les permite acceder a su entorno adjunto, así como también al código de máquina para ejecutar en ese contexto. Pero es solo una forma (una de muchas) de implementar el cierre, no el cierre en sí mismo.

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 todavía no se puede evaluar, en una expresión lambda cerrada , que luego se puede evaluar, ya que todos los símbolos que contiene están ahora definidos).

Cualquier otra cosa es solo un “culto a la carga” y una “magia del voo-doo” de progtwigdores y vendedores de idiomas que desconocen las raíces reales de estas nociones.

Espero que responda tus preguntas. Pero si tiene preguntas de seguimiento, siéntase libre de preguntarlas en los comentarios, y trataré de explicarlo mejor.

No todos los cierres son lambdas y no todas las lambdas son cierres. Ambas son funciones, pero no necesariamente de la manera 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 para declarar funciones. Las lambdas con frecuencia se pueden pasar como objetos.

Un cierre es una función que encierra su estado circundante al hacer referencia a campos externos a su cuerpo. El estado cerrado permanece en todas las 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 se acerca más a la definición de cierres proporcionados 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 progtwigción funcional al uso general.

Es tan simple como esto: lambda es una construcción de lenguaje, es decir, simplemente syntax para funciones anónimas; un cierre es una técnica para implementarlo, o cualquier función de primera clase, para el caso, 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 forma, esas variables siguen siendo accesibles incluso cuando los ámbitos externos en los que se originan ya se han eliminado.

Desafortunadamente, hay muchos idiomas que no admiten funciones como valores de primera clase, o solo los admiten en forma paralizada. Entonces la gente a menudo usa el término “cierre” para distinguir “lo real”.

Desde la vista de los lenguajes de progtwigció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 manera en que puedes construir la expresión de lamdba, y la reducción determina el significado de la expresión lambda.

Lambda proporciona una forma de abstraer el proceso de cálculo. por ejemplo, para calcular la sum de dos números, se puede abstraer un proceso que toma dos parámetros x, y y devuelve x + y. En el esquema, puede escribirlo como

 (lambda (xy) (+ xy)) 

Puede cambiar el nombre de los parámetros, pero la tarea que completa no cambia. En casi todos los lenguajes de progtwigción, puede darle un nombre a la expresión lambda, que se denominan funciones. Pero no hay mucha diferencia, se pueden considerar conceptualmente como solo azúcar sintáctico.

OK, ahora imagina cómo se puede implementar esto. Siempre que apliquemos la expresión lambda a algunas expresiones, por ejemplo

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

Simplemente podemos sustituir los parámetros con la expresión que se 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, cada vez que queremos 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 rest (+ xy) se evalúa buscando los símbolos correspondientes en la tabla. Ahora, si proporcionamos algunas primitivas para operar en el entorno directamente, ¡podemos modelar los cambios de estado!

Con este fondo, verifica esta función:

 (lambda (xy) (+ xyz)) 

Sabemos que cuando evaluamos la expresión lambda, xy estará vinculado en una nueva tabla. Pero, ¿cómo y dónde podemos buscar z? En realidad, z se llama una variable libre. Debe haber un entorno externo que contenga z. De lo contrario, el significado de la expresión no puede determinarse solo vinculando xey. Para aclarar esto, puede escribir algo de la siguiente manera en el esquema:

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

Entonces z estaría limitado a 1 en una tabla externa. Todavía obtenemos una función que acepta dos parámetros, pero el significado real también depende del entorno exterior. En otras palabras, el entorno externo se cierra en las variables libres. Con la ayuda de set !, podemos hacer que la función sea estable, 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 depende del 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 pobres porque realmente nos gustan las funciones de primera clase.

Yo uso el esquema para ilustrar las ideas debido a que el esquema es uno de los primeros lenguajes que tiene cierres reales. Todos los materiales aquí están mucho mejor presentados 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 la lambda.

El concepto es el mismo que se describió anteriormente, pero si eres de origen PHP, esto explicará más el uso de 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é sucede 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 vinculadas a las que se puede acceder cuando se llama a la función. Vienen del mundo de la progtwigció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 externo 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(); 

Bien explicado en este artículo.

Esta pregunta es antigua y obtuvo muchas respuestas. Ahora con Java 8 y Official Lambda que son proyectos de cierre no oficiales, se reaviva la pregunta.

La respuesta en el contexto de Java (a través de Lambdas y cierres: ¿cuál es la diferencia? ):

“Un cierre es una expresión lambda emparejada con un entorno que vincula cada una de sus variables libres a un valor. En Java, las expresiones lambda se implementarán mediante cierres, por lo que los dos términos se utilizarán indistintamente en la comunidad”.

Simplemente hablando, el cierre es un truco sobre el scope, lambda es una función anónima. Podemos realizar el cierre con lambda de forma más elegante y lambda se utiliza a menudo como un parámetro pasado a una función superior

Una lambda es solo una función anónima, una función definida sin nombre. 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.