Hice una pregunta sobre Curry y se mencionaron cierres. ¿Qué es un cierre? ¿Cómo se relaciona con el curry?
432
Hice una pregunta sobre Curry y se mencionaron cierres. ¿Qué es un cierre? ¿Cómo se relaciona con el curry?
Respuestas:
Alcance variable
Cuando declara una variable local, esa variable tiene un alcance. En general, las variables locales solo existen dentro del bloque o función en la que las declara.
Si intento acceder a una variable local, la mayoría de los idiomas la buscarán en el ámbito actual y luego subirán a través de los ámbitos principales hasta que alcancen el ámbito raíz.
Cuando se realiza un bloque o función, sus variables locales ya no son necesarias y, por lo general, se quedan sin memoria.
Así es como normalmente esperamos que las cosas funcionen.
Un cierre es un alcance variable local persistente
Un cierre es un alcance persistente que conserva las variables locales incluso después de que la ejecución del código se haya salido de ese bloque. Los idiomas que admiten el cierre (como JavaScript, Swift y Ruby) le permitirán mantener una referencia a un ámbito (incluidos sus ámbitos principales), incluso después de que el bloque en el que se declararon esas variables haya terminado de ejecutarse, siempre que mantenga una referencia a ese bloque o función en alguna parte.
El objeto de alcance y todas sus variables locales están vinculadas a la función y persistirán mientras esa función persista.
Esto nos da portabilidad de funciones. Podemos esperar que cualquier variable que estuviera dentro del alcance cuando la función se definió por primera vez aún esté dentro del alcance cuando luego la llamemos, incluso si llamamos a la función en un contexto completamente diferente.
Por ejemplo
Aquí hay un ejemplo realmente simple en JavaScript que ilustra el punto:
Aquí he definido una función dentro de una función. La función interna obtiene acceso a todas las variables locales de la función externa, incluida
a
. La variablea
está dentro del alcance de la función interna.Normalmente, cuando sale una función, todas sus variables locales quedan impresionadas. Sin embargo, si devolvemos la función interna y la asignamos a una variable
fnc
para que persista después de queouter
haya salido, todas las variables que estaban en el alcance cuandoinner
se definió también persisten . La variablea
ha sido cerrada, está dentro de un cierre.Tenga en cuenta que la variable
a
es totalmente privada parafnc
. Esta es una forma de crear variables privadas en un lenguaje de programación funcional como JavaScript.Como puede adivinar, cuando lo llamo
fnc()
imprime el valor dea
, que es "1".En un lenguaje sin cierre, la variable
a
habría sido recogida de basura y descartada cuando la funciónouter
salió. Llamar a fnc habría arrojado un error porquea
ya no existe.En JavaScript, la variable
a
persiste porque el alcance de la variable se crea cuando la función se declara por primera vez y persiste mientras la función continúe existiendo.a
pertenece al ámbito deouter
. El alcance deinner
tiene un puntero padre al alcance deouter
.fnc
es una variable que apunta ainner
.a
persiste mientrasfnc
persista.a
Está dentro del cierre.fuente
Daré un ejemplo (en JavaScript):
Lo que hace esta función, makeCounter, es devolver una función, que hemos llamado x, que contará por uno cada vez que se llame. Como no estamos proporcionando ningún parámetro para x, de alguna manera debe recordar el recuento. Sabe dónde encontrarlo en función de lo que se denomina alcance léxico: debe buscar el lugar donde está definido para encontrar el valor. Este valor "oculto" es lo que se llama un cierre.
Aquí está mi ejemplo de curry nuevamente:
Lo que puede ver es que cuando llama a add con el parámetro a (que es 3), ese valor está contenido en el cierre de la función devuelta que estamos definiendo como add3. De esa forma, cuando llamamos a add3, sabe dónde encontrar el valor para realizar la suma.
fuente
La respuesta de Kyle es bastante buena. Creo que la única aclaración adicional es que el cierre es básicamente una instantánea de la pila en el momento en que se crea la función lambda. Luego, cuando la función se vuelve a ejecutar, la pila se restaura a ese estado antes de ejecutar la función. Por lo tanto, como Kyle menciona, ese valor oculto (
count
) está disponible cuando se ejecuta la función lambda.fuente
En primer lugar, al contrario de lo que la mayoría de las personas aquí te dicen, ¡el cierre no es una función ! Entonces que es ?
Es un conjunto de símbolos definidos en el "contexto circundante" de una función (conocido como su entorno ) que la convierten en una expresión CERRADA (es decir, una expresión en la que cada símbolo está definido y tiene un valor, por lo que puede evaluarse).
Por ejemplo, cuando tiene una función de JavaScript:
es una expresión cerrada porque todos los símbolos que aparecen en él están definidos en él (sus significados son claros), por lo que puede evaluarlo. En otras palabras, es autónomo .
Pero si tienes una función como esta:
es una expresión abierta porque contiene símbolos que no se han definido en ella. A saber,
y
. Al observar esta función, no podemos decir quéy
es y qué significa, no sabemos su valor, por lo que no podemos evaluar esta expresión. Es decir, no podemos llamar a esta función hasta que digamos quéy
se supone que significa en ella. Estoy
se llama una variable libre .Esto
y
pide una definición, pero esta definición no es parte de la función: se define en otro lugar, en su "contexto circundante" (también conocido como el entorno ). Al menos eso es lo que esperamos: PPor ejemplo, podría definirse globalmente:
O podría definirse en una función que lo envuelva:
La parte del entorno que da a las variables libres en una expresión sus significados, es el cierre . Se llama de esta manera, porque convierte una expresión abierta en una cerrada , al proporcionar estas definiciones faltantes para todas sus variables libres , de modo que podamos evaluarla.
En el ejemplo anterior, la función interna (que no le dimos un nombre porque no la necesitamos) es una expresión abierta porque la variable
y
en ella es libre : su definición está fuera de la función, en la función que la envuelve . El entorno para esa función anónima es el conjunto de variables:Ahora, el cierre es esa parte de este entorno que cierra la función interna al proporcionar las definiciones para todas sus variables libres . En nuestro caso, la única variable libre en la función interna era
y
, por lo que el cierre de esa función es este subconjunto de su entorno:Los otros dos símbolos definidos en el entorno no forman parte del cierre de esa función, ya que no requiere que se ejecuten. No son necesarios para cerrarlo .
Más sobre la teoría detrás de eso aquí: https://stackoverflow.com/a/36878651/434562
Vale la pena señalar que en el ejemplo anterior, la función de contenedor devuelve su función interna como un valor. El momento en que llamamos a esta función puede ser remoto en el tiempo desde el momento en que la función se ha definido (o creado). En particular, su función de ajuste ya no se está ejecutando, y sus parámetros que han estado en la pila de llamadas ya no están allí: P Esto causa un problema, porque la función interna necesita
y
estar allí cuando se llama. En otras palabras, requiere que las variables de su cierre sobrevivan de alguna manera a la función de envoltura y estén allí cuando sea necesario. Por lo tanto, la función interna tiene que hacer una instantánea de estas variables que cierran y las almacenan en un lugar seguro para su uso posterior. (En algún lugar fuera de la pila de llamadas).Y esta es la razón por la cual las personas a menudo confunden el término cierre con ese tipo especial de función que puede hacer tales instantáneas de las variables externas que usan, o la estructura de datos utilizada para almacenar estas variables para más adelante. Pero espero que entienda ahora que son no el propio cierre - que son sólo maneras de poner en práctica los cierres en un lenguaje de programación o mecanismos del lenguaje que permite a las variables de cierre de la función para estar allí cuando sea necesario. Hay muchos conceptos erróneos sobre los cierres que (innecesariamente) hacen que este tema sea mucho más confuso y complicado de lo que realmente es.
fuente
Un cierre es una función que puede hacer referencia al estado en otra función. Por ejemplo, en Python, esto usa el cierre "interno":
fuente
Para facilitar la comprensión de los cierres, puede ser útil examinar cómo se pueden implementar en un lenguaje de procedimiento. Esta explicación seguirá una implementación simplista de cierres en Scheme.
Para comenzar, debo presentar el concepto de un espacio de nombres. Cuando ingresa un comando en un intérprete de esquemas, debe evaluar los diversos símbolos en la expresión y obtener su valor. Ejemplo:
Las expresiones de definición almacenan el valor 3 en el lugar para x y el valor 4 en el lugar para y. Luego, cuando llamamos (+ xy), el intérprete busca los valores en el espacio de nombres y puede realizar la operación y devolver 7.
Sin embargo, en Scheme hay expresiones que le permiten anular temporalmente el valor de un símbolo. Aquí hay un ejemplo:
Lo que hace la palabra clave let es introducir un nuevo espacio de nombres con x como valor 5. Notarás que todavía puede ver que y es 4, haciendo que la suma devuelta sea 9. También puedes ver que una vez que la expresión ha terminado x vuelve a ser 3. En este sentido, x ha sido temporalmente enmascarado por el valor local.
Los lenguajes procesales y orientados a objetos tienen un concepto similar. Cada vez que declaras una variable en una función que tiene el mismo nombre que una variable global, obtienes el mismo efecto.
¿Cómo implementaríamos esto? Una manera simple es con una lista vinculada: la cabecera contiene el nuevo valor y la cola contiene el antiguo espacio de nombres. Cuando necesita buscar un símbolo, comienza en la cabeza y baja por la cola.
Ahora pasemos a la implementación de funciones de primera clase por el momento. Más o menos, una función es un conjunto de instrucciones para ejecutar cuando se llama a la función que culmina en el valor de retorno. Cuando leemos una función, podemos almacenar estas instrucciones detrás de escena y ejecutarlas cuando se llama a la función.
Definimos x para que sea 3 y plus-x para que sea su parámetro, y, más el valor de x. Finalmente llamamos a plus-x en un entorno donde x ha sido enmascarado por una nueva x, esta valoró 5. Si simplemente almacenamos la operación, (+ xy), para la función plus-x, ya que estamos en el contexto de x siendo 5 el resultado devuelto sería 9. Esto es lo que se llama alcance dinámico.
Sin embargo, Scheme, Common Lisp y muchos otros lenguajes tienen lo que se denomina alcance léxico: además de almacenar la operación (+ xy) también almacenamos el espacio de nombres en ese punto en particular. De esa manera, cuando buscamos los valores, podemos ver que x, en este contexto, es realmente 3. Esto es un cierre.
En resumen, podemos usar una lista vinculada para almacenar el estado del espacio de nombres en el momento de la definición de la función, lo que nos permite acceder a las variables desde los ámbitos adjuntos, así como también nos brinda la capacidad de enmascarar localmente una variable sin afectar el resto del programa.
fuente
Why do we want to access variables that are out of scope? when we say let x = 5, we want x to be 5 and not 3. What is happening?
Aquí hay un ejemplo del mundo real de por qué Closures patea traseros ... Esto es sacado directamente de mi código Javascript. Déjame ilustrar.
Y así es como lo usarías:
Ahora imagine que desea que la reproducción comience con retraso, como por ejemplo 5 segundos después de que se ejecute este fragmento de código. Bueno, eso es fácil con
delay
su cierre:Cuando llama
delay
con5000
ms, se ejecuta el primer fragmento y almacena los argumentos pasados en su cierre. Luego, 5 segundos después, cuandosetTimeout
ocurre la devolución de llamada, el cierre aún mantiene esas variables, por lo que puede llamar a la función original con los parámetros originales.Este es un tipo de curry o decoración de funciones.
Sin cierres, tendría que mantener de alguna manera el estado de esas variables fuera de la función, ensuciando el código fuera de la función con algo que lógicamente pertenece dentro de ella. El uso de cierres puede mejorar en gran medida la calidad y la legibilidad de su código.
fuente
Las funciones que no contienen variables libres se denominan funciones puras.
Las funciones que contienen una o más variables libres se denominan cierres.
src: https://leanpub.com/javascriptallongesix/read#leanpub-auto-if-functions-without-free-variables-are-pure-are-closures-impure
fuente
tl; dr
Un cierre es una función y su alcance se asigna a (o se usa como) una variable. Por lo tanto, el cierre del nombre: el alcance y la función están encerrados y utilizados como cualquier otra entidad.
Explicación profunda del estilo de Wikipedia
Según Wikipedia, un cierre es:
Qué significa eso? Veamos algunas definiciones.
Explicaré los cierres y otras definiciones relacionadas utilizando este ejemplo:
Funciones de primera clase
Básicamente, eso significa que podemos usar funciones como cualquier otra entidad . Podemos modificarlos, pasarlos como argumentos, devolverlos de funciones o asignarlos a variables. Técnicamente hablando, son ciudadanos de primera clase. , de ahí el nombre: funciones de primera clase.
En el ejemplo anterior,
startAt
devuelve una función ( anónima ) a la que se asigna la funciónclosure1
yclosure2
. Entonces, como ve, JavaScript trata las funciones como cualquier otra entidad (ciudadanos de primera clase).Enlace de nombre
El enlace de nombres consiste en descubrir a qué datos hace referencia una variable (identificador) . El alcance es realmente importante aquí, ya que eso es lo que determinará cómo se resuelve un enlace.
En el ejemplo anterior:
y
está obligado a3
.startAt
el ámbito de aplicación,x
está vinculado1
ao5
(según el cierre).Dentro del alcance de la función anónima,
x
no está vinculado a ningún valor, por lo que debe resolverse en unstartAt
alcance superior ( s).Alcance léxico
Como dice Wikipedia , el alcance:
Hay dos técnicas:
Para obtener más explicaciones, consulte esta pregunta y eche un vistazo a Wikipedia .
En el ejemplo anterior, podemos ver que JavaScript tiene un ámbito léxico, porque cuando
x
se resuelve, el enlace se busca en el ámbito superior (startAt
's), en función del código fuente (la función anónima que busca x se define dentrostartAt
) y no se basa en la pila de llamadas, la forma (el alcance donde) se llamó a la función.Envolver (cerrar)
En nuestro ejemplo, cuando llamamos
startAt
, devolverá una función (de primera clase) a la que se le asignaráclosure1
y, por loclosure2
tanto, se crea un cierre, porque las variables pasadas1
y5
se guardarán dentrostartAt
del alcance, que se adjuntará con el devuelto función anónima. Cuando llamamos a esta función anónima víaclosure1
yclosure2
con el mismo argumento (3
), el valor dey
se encontrará de inmediato (ya que ese es el parámetro de esa función), perox
no está vinculado en el alcance de la función anónima, por lo que la resolución continúa en el alcance de la función superior (léxicamente) (que se guardó en el cierre) dondex
se encuentra vinculado a cualquiera1
o5
. Ahora sabemos todo para la suma, por lo que el resultado puede devolverse y luego imprimirse.Ahora debe comprender los cierres y cómo se comportan, que es una parte fundamental de JavaScript.
Zurra
Ah, y también aprendiste de qué se trata el curry : utilizas funciones (cierres) para pasar cada argumento de una operación en lugar de usar una función con múltiples parámetros.
fuente
El cierre es una característica en JavaScript donde una función tiene acceso a sus propias variables de alcance, acceso a las variables de la función externa y acceso a las variables globales.
El cierre tiene acceso a su alcance de función externa incluso después de que la función externa ha regresado. Esto significa que un cierre puede recordar y acceder a variables y argumentos de su función externa incluso después de que la función haya finalizado.
La función interna puede acceder a las variables definidas en su propio alcance, el alcance de la función externa y el alcance global. Y la función externa puede acceder a la variable definida en su propio alcance y el alcance global.
Ejemplo de cierre :
La salida será 20, que suma la variable propia de su función interna, la variable de función externa y el valor de la variable global.
fuente
En una situación normal, las variables están sujetas a una regla de alcance: las variables locales solo funcionan dentro de la función definida. El cierre es una forma de romper esta regla temporalmente por conveniencia.
en el código anterior,
lambda(|n| a_thing * n}
es el cierre porquea_thing
es referido por lambda (un creador de funciones anónimas).Ahora, si coloca la función anónima resultante en una variable de función.
foo romperá la regla de alcance normal y comenzará a usar 4 internamente.
devuelve 12.
fuente
En resumen, el puntero de función es solo un puntero a una ubicación en la base del código del programa (como el contador del programa). Mientras que cierre = puntero de función + marco de pila .
.
fuente
• Un cierre es un subprograma y el entorno de referencia donde se definió.
- El entorno de referencia es necesario si se puede llamar al subprograma desde cualquier lugar arbitrario del programa
- Un lenguaje de alcance estático que no permite subprogramas anidados no necesita cierres
- Los cierres solo son necesarios si un subprograma puede acceder a variables en ámbitos de anidamiento y se puede llamar desde cualquier lugar
- Para admitir los cierres, una implementación puede necesitar proporcionar una extensión ilimitada a algunas variables (porque un subprograma puede acceder a una variable no local que normalmente ya no está activa)
Ejemplo
fuente
Aquí hay otro ejemplo de la vida real, y el uso de un lenguaje de script popular en los juegos: Lua. Necesitaba cambiar ligeramente la forma en que funcionaba una función de biblioteca para evitar un problema con stdin no disponible.
El valor de old_dofile desaparece cuando este bloque de código termina su alcance (porque es local), sin embargo, el valor se ha encerrado en un cierre, por lo que la nueva función de dofile redefinida PUEDE acceder a él, o más bien una copia almacenada junto con la función como un 'upvalue'.
fuente
Desde Lua.org :
fuente
Si eres del mundo Java, puedes comparar un cierre con una función miembro de una clase. Mira este ejemplo
La función
g
es un cierre: seg
cierraa
. Porg
lo tanto, se puede comparar con una función miembro,a
se puede comparar con un campo de clase y la funciónf
con una clase.fuente
Cierres Cuando tenemos una función definida dentro de otra función, la función interna tiene acceso a las variables declaradas en la función externa. Los cierres se explican mejor con ejemplos. En el Listado 2-18, puede ver que la función interna tiene acceso a una variable (variableInOuterFunction) desde el alcance externo. Las variables en la función externa han sido cerradas (o ligadas) por la función interna. De ahí el término cierre. El concepto en sí mismo es bastante simple y bastante intuitivo.
fuente: http://index-of.es/Varios/Basarat%20Ali%20Syed%20(auth.)-Beginning%20Node.js-Apress%20(2014).pdf
fuente
Eche un vistazo debajo del código para comprender el cierre en más profundidad:
Aquí lo que saldrá?
0,1,2,3,4
eso no será5,5,5,5,5
por cierreEntonces, ¿cómo se resolverá? La respuesta está abajo:
Permítanme explicar de manera simple, cuando una función creada no sucede nada hasta que se llama así para el bucle en el primer código llamado 5 veces, pero no se llama inmediatamente, así que cuando se llama, es decir, después de 1 segundo y también esto es asíncrono, entonces antes de que finalice el bucle y almacene el valor 5 en var i y finalmente ejecutar la
setTimeout
función cinco veces e imprimir5,5,5,5,5
Aquí cómo se resuelve usando IIFE, es decir, la expresión de la función de invocación inmediata
Para obtener más información, comprenda el contexto de ejecución para comprender el cierre.
Hay una solución más para resolver esto usando let (función ES6) pero bajo el capó la función anterior funciona
=> Más explicación:
En la memoria, cuando para el bucle ejecute la imagen haga lo siguiente:
Bucle 1)
Lazo 2)
Lazo 3)
Lazo 4)
Lazo 5)
Aquí no se ejecuta y luego, después de completar el ciclo, var i almacenó el valor 5 en la memoria, pero su alcance siempre es visible en su función secundaria, por lo que cuando la función se ejecuta al revés
setTimeout
cinco veces se imprime5,5,5,5,5
para resolver este uso IIFE como se explica anteriormente.
fuente
Curry: le permite evaluar parcialmente una función pasando solo un subconjunto de sus argumentos. Considera esto:
Cierre: Un cierre no es más que acceder a una variable fuera del alcance de una función. Es importante recordar que una función dentro de una función o una función anidada no es un cierre. Los cierres siempre se usan cuando es necesario acceder a las variables fuera del alcance de la función.
fuente
El cierre es muy fácil. Podemos considerarlo de la siguiente manera: Cierre = función + su entorno léxico
Considere la siguiente función:
¿Cuál será el cierre en el caso anterior? Función init () y variables en su entorno léxico, es decir, nombre. Cierre = init () + nombre
Considere otra función:
¿Cuáles serán los cierres aquí? La función interna puede acceder a las variables de la función externa. displayName () puede acceder al nombre de la variable declarada en la función padre, init (). Sin embargo, se utilizarán las mismas variables locales en displayName () si existen.
Cierre 1: función init + (variable de nombre + displayName () función) -> alcance léxico
Cierre 2: función función + (variable de nombre) -> alcance léxico
fuente
Los cierres proporcionan JavaScript con estado.
El estado en la programación simplemente significa recordar cosas.
Ejemplo
En el caso anterior, el estado se almacena en la variable "a". Seguimos agregando 1 a "a" varias veces. Solo podemos hacer eso porque podemos "recordar" el valor. El titular del estado, "a", mantiene ese valor en la memoria.
A menudo, en los lenguajes de programación, desea realizar un seguimiento de las cosas, recordar información y acceder a ella en otro momento.
Esto, en otros idiomas , se logra comúnmente mediante el uso de clases. Una clase, al igual que las variables, realiza un seguimiento de su estado. Y las instancias de esa clase, a su vez, también tienen estado dentro de ellas. Estado simplemente significa información que puede almacenar y recuperar más tarde.
Ejemplo
¿Cómo podemos acceder al "peso" desde el método "render"? Bueno, gracias al estado. Cada instancia de la clase Pan puede representar su propio peso al leerla desde el "estado", un lugar en la memoria donde podríamos almacenar esa información.
Ahora, JavaScript es un lenguaje muy único que históricamente no tiene clases (ahora sí, pero bajo el capó solo hay funciones y variables), por lo que Closures proporciona una forma para que JavaScript recuerde cosas y acceda a ellas más tarde.
Ejemplo
El ejemplo anterior logró el objetivo de "mantener el estado" con una variable. ¡Esto es genial! Sin embargo, esto tiene la desventaja de que la variable (el "titular" del estado) ahora está expuesta. Podemos hacerlo mejor. Podemos usar cierres.
Ejemplo
Esto es fantástico.
Ahora nuestra función "contar" puede contar. Solo puede hacerlo porque puede "mantener" el estado. El estado en este caso es la variable "n". Esta variable ahora está cerrada. Cerrado en tiempo y espacio. A tiempo porque nunca podrás recuperarlo, cambiarlo, asignarle un valor o interactuar directamente con él. En el espacio porque está anidado geográficamente dentro de la función "countGenerator".
¿Por qué es esto fantástico? Porque sin involucrar ninguna otra herramienta sofisticada y complicada (por ejemplo, clases, métodos, instancias, etc.) podemos 1. ocultar 2. control desde la distancia
Ocultamos el estado, la variable "n", lo que la convierte en una variable privada. También hemos creado una API que puede controlar esta variable de forma predefinida. En particular, podemos llamar a la API así "count ()" y eso agrega 1 a "n" desde una "distancia". De ninguna manera, nadie podrá acceder a "n" excepto a través de la API.
JavaScript es realmente sorprendente en su simplicidad.
Los cierres son una gran parte de por qué esto es así.
fuente
Un ejemplo simple en Groovy para su referencia:
fuente