Pregunta ¿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?


679
2017-10-21 03:12


origen


Respuestas:


UN 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 cierra sobre el ambiente en que fue definido. 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 cerrar sobre el medio ambiente en anotherfunc - h es indefinido. 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 posteriores (o algunos números como este) cuando casi obtuve los cierres correctos (la mutación aún no funciona), esto significa que cierra sobre  anotherfuncdel entorno y puede acceder a variables dentro de él. En Python 3.1+, la mutación también funciona cuando se usa el nonlocal palabra clave.

Otro punto importante: func continuará acercándose anotherfuncel entorno, incluso cuando ya no se evalúa en 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 lambdas - son dos conceptos diferentes (aunque relacionados).


605
2017-10-21 03:58



Cuando la mayoría de la gente 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 expresiones lambda, tu puedes 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 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 esto fuera todo 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 obtienes objetos con estado en la programació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;
           };
 }

164
2017-10-21 03:46



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 programadores al azar que aprendieron sobre los cierres de la práctica con ciertos lenguajes de programación u otros programadores desorientados, emprendan un viaje al fuente (donde todo comenzó). Y dado que las lambdas y los cierres provienen de Cálculo Lambda inventado por Alonzo Church en los años 30 antes de que existieran las primeras computadoras electrónicas, este es el fuente Estoy hablando de.

Lambda Calculus es el lenguaje de programación más simple del mundo. Lo único que puedes hacer con eso: ►

  • APLICACIÓN: aplicar una expresión a otra, denotada f x.
    (Piense en ello como Llamada de función, dónde f es la función y 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 hace anteponiendo una letra griega λ (lambda), luego el nombre simbólico (p. x), luego un punto . antes de la expresión. Esto luego convierte la expresión en una función esperando uno 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 un variable enlazada - 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ónimo - No tiene nombre, por lo que no puede referirse a él todavía, pero puede llame inmediatamente (¿recuerda la aplicación?) suministrándole el parámetro que está esperando, como este: (λx.x+2) 7. Luego la expresión (en este caso, un valor literal) 7 es sustituido como x en la subexpresion x+2 de la lambda aplicada, así obtienes 7+2, que luego se reduce a 9 por reglas comunes de aritmética.

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


En diferentes lenguajes de programación, la sintaxis 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 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 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 Unión un símbolo en su subexpresión, para que se convierta en un sustituto parámetro. 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á obligado, es gratis. No sabemos qué es ni de dónde viene, así que no sabemos qué medio y qué valor representa, y por lo tanto no podemos evaluar esa expresión hasta que descubramos qué y medio.

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 decir lo que significan definiéndolos en algún lugar, p. en una biblioteca o en el idioma en sí.

Puedes pensar en el gratis símbolos como se define en otro lugar, fuera de la expresión, en su "contexto circundante", que se llama su ambiente. 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 idioma en sí (como primitivo)

Esto nos permite dividir las expresiones lambda en dos categorías:

  • Expresiones CERRADAS: cada símbolo que aparece en estas expresiones es ligado por alguna abstracción lambda. En otras palabras, son autónomo; no requieren ningún contexto circundante para ser evaluado. También se les llama combinadores.
  • Expresiones ABIERTAS: algunos símbolos en estas expresiones no son ligado - es decir, algunos de los símbolos que aparecen en ellos son gratis y requieren cierta información externa, y por lo tanto no pueden evaluarse hasta que proporciones las definiciones de estos símbolos.

Puedes CERRAR una abierto expresión lambda mediante el suministro de la ambiente, que define todos estos símbolos libres vinculándolos a algunos valores (que pueden ser números, cadenas, funciones anónimas, también conocidas como lambdas, lo que sea ...).

Y aquí viene el cierre parte:
los cierre de un expresión lambda es este conjunto particular de símbolos definidos en el contexto externo (entorno) que dan valores al símbolos gratis en esta expresión, haciéndolos no libres más. Resulta un abierto expresión lambda, que todavía contiene algunos símbolos libres "indefinidos", en una cerrado uno, que ya no tiene ningún símbolo gratis

Por ejemplo, si tiene la siguiente expresión lambda: λx.x/y+2, el símbolo x está atado, mientras que el símbolo y es gratis, por lo tanto, la expresión es open y no puede ser evaluado a menos que diga lo que y significa (y lo mismo con + y 2, que también son gratis). Pero supongamos que también tiene un ambiente Me gusta esto:

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

Esta ambiente 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 esta es precisamente la cierre de nuestra expresión lambda:>

En otras palabras, cierra una expresión lambda abierta. Aquí es donde el nombre cierre vino desde el principio, 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 alcance 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 solo está hecho post factum "etimología del folklore" 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 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 "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, necesitas 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 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 desde el ambiente de tu lambda que necesitan ser salvados en alguna parte.

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 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, efectivamente clausura la expresión (convirtiendo un abierto expresión lambda, que aún no se puede evaluar, en una cerrado expresión lambda, que luego puede evaluarse, ya que todos los símbolos que contiene ahora están definidos).

Cualquier otra cosa es solo un "culto a la carga" y una "magia del voo-doo" de programadores 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.


146
2018-04-27 01:18



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 proporcionada por puramente lenguajes 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.


51
2017-10-21 03:29



Es tan simple como esto: lambda es una construcción del lenguaje, es decir, simplemente sintaxis 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 la forma en que función de primera clase se representa 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".


13
2018-03-19 08:31



Desde la 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, p. 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 suma 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 (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 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, p.

((lambda (x y) (+ x y)) 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, p. 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 resto (+ x y) 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 (x y) (+ x y z))

Sabemos que cuando evaluamos la expresión lambda, x y se vinculará en una nueva tabla. Pero, ¿cómo y dónde podemos buscar z? En realidad, z se llama una variable libre. Debe haber un exterior un entorno que contiene 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 (x y) (+ x y z))) 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.


12
2018-03-19 00:11



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 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 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. 


7
2017-07-31 07:41



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".


6
2017-11-09 17:46