function - programming - closures meaning
¿Cuál es la diferencia entre un ''cierre'' y un ''lambda''? (11)
Cuando la mayoría de la gente piensa en funciones , piensa en funciones con nombre :
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 fue asignado:
foo();
Sin embargo, más útiles que asignar funciones anónimas a variables, son pasarlas ao desde funciones de orden superior, 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 con nombre o anónima, pero se conoce como tal cuando "cierra sobre" las variables en el alcance 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í. 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 pasara 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 los objetos con estado en la programación funcional. Como no es necesario nombrar "incrementX", puede usar un lambda en este caso:
function foo()
{ @x = 0;
return lambda()
{ x = x + 1;
return x;
};
}
¿Podría alguien explicar? Entiendo los conceptos básicos detrás de ellos, pero a menudo los veo usados indistintamente y me confundo.
Y ahora que estamos aquí, ¿en qué se diferencian de una función regular?
Depende de si una función usa 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 los 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, una variable definida 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 un 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.
Desde el punto de vista de los lenguajes de programación, son dos cosas completamente 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 dertermines el significado de la expresión lambda.
Lambda proporciona una forma en que puede 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 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 simplemente azúcar de sintaxis.
Bien, ahora imagina cómo se puede implementar esto. Cada vez que aplicamos la expresión lambda a algunas expresiones, por ejemplo
((lambda (x y) (+ x y)) 2 3)
Simplemente podemos sustituir los parámetros con la expresión a evaluar. Este modelo ya es muy potente. Pero este modelo no nos permite cambiar los valores de los símbolos, por ejemplo, no podemos imitar el cambio de estado. Por eso necesitamos un modelo más complejo. Para abreviar, cuando 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 (+ 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, marque 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 mirar hacia arriba? 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 se puede determinar vinculando solamente x e y. Para aclarar esto, puede escribir algo como sigue en el esquema:
((lambda (z) (lambda (x y) (+ x y z))) 1)
Entonces z se uniría a 1 en una tabla externa. Todavía tenemos una función que acepta dos parámetros, pero su significado real también depende del entorno externo. En otras palabras, el entorno exterior se cierra en las variables libres. Con la ayuda de set !, podemos hacer que la función sea completa, 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 sabe 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 del hombre pobre". Pero también podríamos considerar los objetos como los cierres del hombre pobre, ya que realmente nos gustan las funciones de primera clase.
Utilizo el esquema para ilustrar las ideas debido a que el 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 el 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 usted es de fondo PHP, esto explica con más detalle 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 las 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();
En pocas palabras, 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 que pasa a una función superior
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, con ese nombre, 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 siguen siendo accesibles incluso cuando los ámbitos externos donde se originan ya se han salido.
Desafortunadamente, hay muchos idiomas por ahí que no admiten funciones como valores de primera clase, o solo las admiten en forma paralizada. Así que las personas a menudo usan el término "cierre" para distinguir "lo real".
Esta pregunta es antigua y tiene muchas respuestas. Ahora con Java 8 y Official Lambda que son proyectos de cierre no oficiales, 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 combinada con un entorno que une 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 han utilizado indistintamente en la comunidad".
Hay mucha confusión en torno a las lambdas y los cierres, incluso en las respuestas a esta pregunta de aquí. En lugar de preguntar a los programadores aleatorios que aprendieron sobre los cierres de la práctica con ciertos lenguajes de programación u otros programadores despistados, diríjase a la fuente (donde comenzó todo). 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 programación más simple del mundo. Las únicas cosas que puedes hacer en él: ►
- APLICACIÓN: Aplicar una expresión a otra, denotada
fx
.
(Piense en ello como una llamada de función , dondef
es la función yx
es su único parámetro) - ABSTRACCIÓN: une un símbolo que aparece en una expresión para indicar que este símbolo es solo una "ranura", un cuadro en blanco que espera ser llenado con un valor, una "variable" como tal. Se realiza al anteponer una letra griega
λ
(lambda), luego el nombre simbólico (por ejemplo,x
), luego un punto.
antes de la expresion Esto convierte la expresión en una función que espera un parámetro .
Por ejemplo:λx.x+2
toma la expresiónx+2
y le dice que el símbolox
en esta expresión es una variable vinculada , que puede ser sustituida por un valor que proporcione como parámetro.
Tenga en cuenta que la función definida de esta manera es anónima : no tiene un nombre, por lo que aún no puede referirse a ella, pero puede llamarla inmediatamente (¿recuerda la aplicación?) Proporcionándole el parámetro que está esperando, como esto:(λx.x+2) 7
. Luego, la expresión (en este caso, un valor literal)7
se sustituye comox
en la subexpresiónx+2
del lambda aplicado, de modo que se obtiene7+2
, que luego se reduce a9
según 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
.
function(x) { return x+2; }
e inmediatamente puede aplicarlo 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
, que le permite referirse a él y llamarlo varias veces después, por ejemplo:
alert( f(7) + f(10) ); // should print 21 in the message box
Pero no tenías que nombrarlo. Podrías 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 llamar a tal lambda aplicándolo inmediatamente a un parámetro:
( (lambda (x) (+ x 2)) 7 )
Bien, ahora es el momento de resolver el otro misterio: lo que es un cierre . Para hacer eso, hablemos de los símbolos ( variables ) en las 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 atado . 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á limitado por la abstracción lambda λx.
precediéndolo Pero el otro símbolo, y
, no está vinculado, es libre . 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 entendamos 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 usualmente olvidamos que la computadora no los conoce y necesitamos decirles qué significan definiéndolos en algún lugar, por ejemplo, en una biblioteca o en el idioma en sí.
Puede pensar en los símbolos libres como se definen 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 propio idioma (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 autocontenidos ; no requieren ningún contexto circundante para ser evaluado. También se les llama combinadores .
- Expresiones ABIERTAS: algunos símbolos en estas expresiones no están vinculados , es decir, algunos de los símbolos que aparecen en ellas son libres y requieren cierta información externa, por lo que no pueden evaluarse hasta que usted 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 con algunos valores (que pueden ser números, cadenas, funciones anónimas, también llamadas 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 aún contiene algunos símbolos libres "indefinidos", en un símbolo cerrado 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á vinculado, mientras que el símbolo y
está libre, por lo tanto, la expresión está open
y no se puede evaluar a menos que diga qué significa y
(y Lo mismo con +
y 2
, que también son gratis). Pero supongamos que usted 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 en primer lugar, y es por eso que las respuestas de tantas 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 las lambdas? :PAGBueno, los marketoids corporativos de Sun / Oracle, Microsoft, Google, etc. son los culpables, porque eso es lo que llamaron estas construcciones en sus idiomas (Java, C #, Go, etc.). A menudo llaman "cierres" a lo que se supone que son simplemente lambdas. O llaman "cierres" a 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 una vez que la función externa termina de ejecutarse. Pero esto es solo un invento post factum "etimología del folklore" y marketing, que solo hace que las cosas sean más confusas, porque cada proveedor 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 te permite descartarlo fácilmente como falso: P Déjame explicarte:
Si desea implementar un lenguaje que use lambdas como ciudadanos de primera clase, debe permitirles usar los símbolos definidos en el contexto que los rodea (es decir, usar variables libres en sus lambdas). Y estos símbolos deben estar allí incluso cuando la función circundante vuelve. 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 que usted espera, necesita de alguna manera "capturar" todas estas variables libres de su contexto externo y guardarlas para más tarde, incluso cuando el contexto externo se haya ido. 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 ellas por adelantado, en otro lugar que no esté 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.
No tomó mucho tiempo para que las personas comiencen 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 usualmente se ve algo como esto:
Closure {
[pointer to the lambda function''s machine code],
[pointer to the lambda function''s environment]
}
y estas estructuras de datos se transfieren como parámetros a otras funciones, se devuelven desde las funciones y se almacenan en variables para representar a las lambdas, y les permite acceder a su entorno adjunto, así como al código de la máquina para ejecutarse 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 da 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 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 la "magia voo-doo" de los programadores y vendedores de idiomas que desconocen las raíces reales de estas nociones.
Espero que responda a sus preguntas. Pero si tuvo alguna pregunta de seguimiento, no dude en preguntarlas en los comentarios, y trataré de explicarlo mejor.
No todos los cierres son lambdas y no todos los cierres son cierres. Ambas son funciones, pero no necesariamente de la manera 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 declaración de funciones. 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 a través de invocaciones del cierre.
En un lenguaje orientado a objetos, los cierres normalmente se proporcionan a través de objetos. Sin embargo, algunos lenguajes OO (p. Ej., C #) implementan una funcionalidad especial más cercana a la definición de cierres proporcionados por lenguajes puramente funcionales (como lisp) que no tienen objetos que encierran el estado.
Lo que es interesante es que la introducción de Lambdas y Closures en C # acerca la programación funcional al uso general.
Un lambda es solo una función anónima, una función definida sin nombre. En algunos idiomas, como Scheme, son equivalentes a las funciones nombradas. De hecho, la definición de la función se reescribe como vinculando un lambda a una variable internamente. En otros idiomas, como Python, hay algunas distinciones (bastante innecesarias) entre ellos, pero se comportan de la misma manera en caso 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 la func
no se cierra sobre el entorno en otra anotherfunc
, h
no está definido. func
solo se cierra sobre el entorno global. Esto funcionará:
def anotherfunc(h):
def func(): return h
return func()
Porque aquí, la func
se define en anotherfunc
, y en python 2.3 y superior (o algún número como este) cuando casi se cierran correctamente (la mutación aún no funciona), esto significa que se cierra sobre el entorno de otra función 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: la func
continuará anotherfunc
el entorno de otro anotherfunc
, incluso cuando ya no se esté evaluando 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 notará, no tiene nada que ver con lambda s, son dos conceptos diferentes (aunque relacionados).
Un lambda es simplemente 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.