¿Cuál es la definición exacta de un cierre?

He leído los temas anteriores sobre cierres en stackflow y otras fonts y una cosa todavía me confunde. De lo que he podido reunir técnicamente, un cierre es simplemente el conjunto de datos que contiene el código de una función y el valor de las variables vinculadas en esa función.

En otras palabras, desde el punto de vista técnico, la siguiente función C debería ser un cierre:

int count() { static int x = 0; return x++; } 

Sin embargo, todo lo que leo parece implicar cierres que de alguna manera implican funciones de pase como objetos de primera clase. Además, por lo general, parece estar implícito que los cierres no son parte de la progtwigción de procedimientos. ¿Es este un caso de una solución excesivamente asociada con el problema que resuelve o estoy malinterpretando la definición exacta?

Por lo que entiendo, un cierre también debe tener acceso a las variables en el contexto de llamada. Los cierres generalmente están asociados con la progtwigción funcional. Los lenguajes pueden tener elementos de diferentes tipos de perspectivas de progtwigción, funcionales, de procedimiento, imperativos, declarativos, etc. Obtienen que su nombre se cierre en un contexto específico. También pueden tener una vinculación léxica, ya que pueden hacer referencia al contexto especificado con los mismos nombres que se utilizan en ese contexto. Su ejemplo no tiene referencia a ningún otro contexto sino a uno estático global.

De Wikipedia

Un cierre cierra sobre las variables libres (variables que no son variables locales)

No, eso no es un cierre. Su ejemplo es simplemente una función que devuelve el resultado de incrementar una variable estática.

Así es como funcionaría un cierre:

 function makeCounter( int x ) { return int counter() { return x++; } } c = makeCounter( 3 ); printf( "%d" c() ); => 4 printf( "%d" c() ); => 5 d = makeCounter( 0 ); printf( "%d" d() ); => 1 printf( "%d" c() ); => 6 

En otras palabras, las diferentes invocaciones de makeCounter () producen diferentes funciones con su propia unión de variables en su entorno léxico que han “cerrado”.

Editar: Creo que ejemplos como este hacen que los cierres sean más fáciles de entender que las definiciones, pero si quieres una definición, diría: “Un cierre es una combinación de una función y un entorno. El entorno contiene las variables que se definen en la función así como aquellos que son visibles para la función cuando fue creada. Estas variables deben permanecer disponibles para la función siempre que la función exista “.

Para la definición exacta, sugiero mirar su entrada de Wikipedia . Es especialmente bueno. Solo quiero aclararlo con un ejemplo.

Asum este fragmento de código C # (que se supone que realiza una búsqueda AND en una lista):

 List list = new List { "hello world", "goodbye world" }; IEnumerable filteredList = list; var keywords = new [] { "hello", "world" }; foreach (var keyword in keywords) filteredList = filteredList.Where(item => item.Contains(keyword)); foreach (var s in filteredList) // closure is called here Console.WriteLine(s); 

Es un error común en C # hacer algo así. Si observa la expresión lambda dentro de Where , verá que define una función que su comportamiento depende del valor de una variable en su sitio de definición. Es como pasar una variable a la función, en lugar del valor de esa variable . Efectivamente, cuando se llama a este cierre, recupera el valor de la variable de keyword en ese momento. El resultado de esta muestra es muy interesante. Imprime tanto “hello world” como “goodbye world”, que no es lo que queríamos. ¿Que pasó? Como dije antes, la función que declaramos con la expresión lambda es una variable de keyword cierre , así que esto es lo que sucede:

 filteredList = filteredList.Where(item => item.Contains(keyword)) .Where(item => item.Contains(keyword)); 

y en el momento de la ejecución del cierre, la keyword tiene el valor “mundo”, por lo que básicamente estamos filtrando la lista un par de veces con la misma palabra clave. La solucion es:

 foreach (var keyword in keywords) { var temporaryVariable = keyword; filteredList = filteredList.Where(item => item.Contains(temporaryVariable)); } 

Como temporaryVariable tiene un scope para el cuerpo del bucle foreach , en cada iteración, es una variable diferente. En efecto, cada cierre se vinculará a una variable distinta (esas son instancias diferentes de Variable temporaryVariable en cada iteración). Esta vez, dará los resultados correctos (“hello world”):

 filteredList = filteredList.Where(item => item.Contains(temporaryVariable_1)) .Where(item => item.Contains(temporaryVariable_2)); 

en el cual temporaryVariable_1 tiene el valor de “hello” y temporaryVariable_2 tiene el valor “world” en el momento de la ejecución del cierre.

Tenga en cuenta que los cierres han causado una extensión a la vida útil de las variables (se suponía que su vida terminaría después de cada iteración del ciclo). Este es también un efecto secundario importante de los cierres.

Un cierre es una técnica de implementación para representar procedimientos / funciones con el estado local. Una forma de implementar cierres se describe en SICP. Presentaré la esencia de eso, de todos modos.

Todas las expresiones, incluidas las funciones, se evalúan en una environement . Un entorno es una secuencia de marcos . Un marco mapea nombres de variables a valores. Cada cuadro también tiene un puntero a su entorno circundante. Una función se evalúa en un nuevo entorno con un marco que contiene enlaces para sus argumentos. Ahora echemos un vistazo al siguiente escenario interesante. Imagine que tenemos una función llamada acumulador , que cuando se evalúa, devolverá otra función:

 // This is some C like language that has first class functions and closures. function accumulator(counter) { return (function() { return ++counter; }); } 

¿Qué pasará cuando evaluemos la siguiente línea?

 accum1 = accumulator(0); 

Primero se crea un nuevo entorno y un objeto entero (para el contador ) se vincula a 0 en su primer fotogtwig. El valor devuelto, que es una función nueva, está vinculado en el entorno global. Por lo general, el nuevo entorno será basura recolectada una vez que la evaluación de la función haya terminado. Aquí eso no sucederá. accum1 mantiene una referencia a él, ya que necesita acceso al contador de variables. Cuando se invoca acumula1 , incrementará el valor del contador en el entorno al que se hace referencia. Ahora podemos llamar a accum1 una función con estado local o cierre.

He descrito algunos usos prácticos de los cierres en mi blog http://vijaymathew.wordpress.com . (Ver las publicaciones “Diseños peligrosos” y “Al pasar el mensaje”).

Ya hay muchas respuestas, pero agregaré otra más …

Los cierres no son exclusivos de los lenguajes funcionales. Ocurren en Pascal (y en la familia), por ejemplo, que tiene procedimientos nesteds. El estándar C no los tiene (todavía), pero IIRC hay una extensión GCC.

El problema básico es que un procedimiento nested puede referirse a las variables definidas en su padre. Además, el padre puede devolver una referencia al procedimiento nested a su llamador.

El procedimiento nested todavía se refiere a variables que eran locales para el padre, específicamente a los valores que tenían esas variables cuando se ejecutó la línea que hace la referencia a la función, incluso aunque esas variables ya no existan ya que el padre ha salido.

El problema se produce incluso si el procedimiento nunca se devuelve desde el elemento primario: diferentes referencias al procedimiento nested construido en diferentes momentos pueden estar usando valores pasados ​​diferentes de las mismas variables.

La resolución de esto es que cuando se hace referencia a la función anidada, se empaqueta en un “cierre” que contiene los valores variables que necesita para más adelante.

Un Python lambda es un simple ejemplo de estilo funcional …

 def parent () : a = "hello" return (lamda : a) funcref = parent () print funcref () 

My Pythons está un poco oxidado, pero creo que es correcto. El punto es que la función anidada (lambda) aún se refiere al valor de la variable local a aunque parent ha salido cuando se llama. La función necesita un lugar para preservar ese valor hasta que sea necesario, y ese lugar se llama cierre.

Un cierre es un poco como un conjunto implícito de parámetros.

Gran pregunta! Dado que uno de los principios de OOP de OOP es que los objetos tienen tanto comportamientos como datos, los cierres son un tipo especial de objeto porque su propósito más importante es su comportamiento. Dicho eso, ¿a qué me refiero cuando hablo de su “comportamiento”?

(Mucho de esto proviene de “Groovy in Action” de Dierk Konig, que es un libro increíble)

En el nivel más simple, un cierre es realmente solo un código que se convierte en un objeto / método andrógino. Es un método porque puede tomar params y devolver un valor, pero también es un objeto por el que puede pasarle una referencia.

En palabras de Dierk, imagine un sobre que tenga un trozo de papel dentro. Un objeto típico tendría variables y sus valores escritos en este documento, pero un cierre tendría una lista de instrucciones en su lugar. Digamos que la carta dice “Entregue este sobre y la carta a sus amigos”.

 In Groovy: Closure envelope = { person -> new Letter(person).send() } addressBookOfFriends.each (envelope) 

El objeto de cierre aquí es el valor de la variable de envolvente y su uso es que es un parámetro para cada método.

Algunos detalles: scope: el scope de un cierre son los datos y los miembros a los que se puede acceder dentro de él. Regresar de un cierre: los cierres a menudo usan un mecanismo de callback para ejecutar y regresar de sí mismo. Argumentos: si el cierre necesita tomar solo 1 param, Groovy y otras langs proporcionan un nombre predeterminado: “it”, para hacer que la encoding sea más rápida. Entonces, por ejemplo, en nuestro ejemplo anterior:

 addressBookOfFriends.each (envelope) is the same as: addressBookOfFriends.each { new Letter(it).send() } 

Espero que esto es lo que estás buscando!

Un objeto es estado más función. Un cierre, es función más estado.

función f es un cierre cuando se cierra (capturado) x