2009-07-08 14 views
17

He leído los temas anteriores sobre cierres en stackflow y otras fuentes y una cosa todavía me confunde. De lo que he podido unir técnicamente un cierre es simplemente el conjunto de datos que contiene el código de una función y el valor de variables vinculadas en esa función.¿Cuál es la definición exacta de un cierre?

En otras palabras, técnicamente, el siguiente función C debe haber un cierre de mi entendimiento:

int count() 
{ 
    static int x = 0; 

    return x++; 
} 

Sin embargo, todo lo que leo parece implicar el cierre de alguna manera debe implicar pasar funciones como objetos de primera clase. Además, por lo general, parece estar implícito que los cierres no son parte de la programació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?

+1

Wikipedia es particularmente bueno en las definiciones: http://en.wikipedia.org/wiki/Closure_%28computer_science%29 –

+6

Sin embargo, las definiciones de Wikipedia como esto tiende a estar escrito por fanáticos del estrés de las tuberías y las criaturas de cristalografía (es decir, no siempre son las aves más fáciles de entender). –

+1

El artículo de la wikipedia fue uno que leí. Dice que cualquier función de primera clase con variables libres es un cierre. Sin embargo, eso no tiene ningún sentido porque incluye todas las funciones de primera clase que devuelven valores en función de sus argumentos. ¿Implica que se convierte en un cierre cuando la función usa variables libres para crear una instancia de sus variables vinculadas? – Amaron

Respuesta

6

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 programación funcional. Los lenguajes pueden tener elementos de diferentes tipos de perspectivas de programació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)

+0

Incluso si no usa esas variables? En otras palabras, un cierre tendría que mantener su propia copia de cada global accesible en caso de que el cambio global? – Amaron

+2

El cierre tiene acceso a la variable en el contexto * defined *, no al contexto * calling *. En teoría, puede definir cierres en un alcance y acceder en otro, lo que le permite pasar por alto las reglas de alcance, aunque no todo el lenguaje de programación lo permite. –

+0

@Amaron: No es "su propia copia", es la misma copia. Si modifica uno, modifica el otro. (De nuevo, no todo el lenguaje de programación lo permite, pero así es como se define el cierre) –

22

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 un cierre funcionaría:

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, diferentes invocaciones de makeCounter() producen diferentes funciones con su propia unión de variables en su entorno léxico que han "cerrado sobre".

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

+0

Eso es de hecho un cierre, pero estaba buscando la definición estricta exacta de lo que es y no es un cierre. El ejemplo que di tenía la intención de implicar algo que pensé que debería ser un cierre, pero sabía que probablemente no era un cierre. Sin embargo, le recomendaré un ejemplo claro y útil que no incluya lisp. – Amaron

12

Para exact definition, I suggest looking at its Wikipedia entry. Es especialmente bueno. Solo quiero aclararlo con un ejemplo.

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

List<string> list = new List<string> { "hello world", "goodbye world" }; 
IEnumerable<string> 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# para 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 en sí misma a la función, en lugar del valor de esa variable. Efectivamente, cuando se llama a este cierre, recupera el valor de la variable keyword en ese momento. El resultado de esta muestra es muy interesante.Imprime "Hola mundo" y "adiós mundo", que no es lo que queríamos. ¿Que pasó? Como ya he dicho anteriormente, la función que declaramos con la expresión lambda es un cierre sobre keywordvariables así que esto es lo que sucede: mundo

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

y en el momento de la ejecución de cierre, keyword tiene el valor" ", así que básicamente estamos filtrando la lista un par de veces con la misma palabra clave. La solución es:

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

Desde temporaryVariable tiene como alcance 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 temporaryVariable en cada iteración). Esta vez, se va a dar los resultados correctos ("Hola mundo"):

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

en el que temporaryVariable_1 tiene el valor de "hola" y temporaryVariable_2 tiene el valor "mundo" en el momento de la ejecución de cierre.

Tenga en cuenta que los cierres han causado una extensión de 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.

+3

Publicación fantástica. Claro, perspicaz. Bien hecho. – jason

+0

A partir de.NET Framework v4.5.2 el primer fragmento de código solo escribe hola mundo. FilterList es {System.Linq.Enumerable.WhereListIterator } después del primer bucle foreach en ambos snippits de código y se resuelve durante el segundo bucle foreach – JnJnBoo

1

¡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 se extrae de "maravilloso en acción" por Dierk Konig, que es un libro impresionante)

En el nivel más simple su fin es en realidad un código que está envuelto para convertirse en un objeto andrógina /método. 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 cierre objeto aquí es el valor de la variable sobre y Es uso es que es un parámetro a la cada método.

Algunos detalles: Alcance: El alcance de un cierre son los datos y los miembros a los que se puede acceder dentro de él. Devolución de un cierre: los cierres suelen utilizar un mecanismo de devolución de llamada para ejecutar y regresar desde sí mismo. Argumentos: si el cierre necesita tomar solo 1 param, Groovy y otras langs proporcionan un nombre predeterminado: "it", para hacer que la codificación sea más rápida. Así 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 usted está buscando!

0

Creo que Peter Eddy tiene razón, pero el ejemplo podría hacerse más interesante. Puede definir dos funciones que cierran sobre una variable local, incremente & decrement. El contador se compartiría entre ese par de funciones, y exclusivo para ellos. Si define un nuevo par de funciones de incremento/decremento, compartirían un contador diferente.

Además, no necesita pasar ese valor inicial de x, puede dejarlo predeterminado en cero dentro del bloque de funciones. Eso aclararía que está usando un valor al que ya no tienes acceso normal.

2

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

Todas las expresiones, incluyendo las funciones se evalúan en un environement, Un entorno es una secuencia de marcos. Un marco mapea nombres de variables a valores. Cada cuadro también tiene un puntero para 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. Imaginemos que tenemos una función llamada acumulador, que al ser evaluados, volverá 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 se evalúa la siguiente línea?

accum1 = accumulator(0); 

primer lugar un nuevo entorno se crea y un objeto entero (por contador) está obligado a 0 en su primer marco. 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 finalice la evaluación de la función . Aquí eso no sucederá. accum1 contiene una referencia, ya que necesita acceso a la variable contador. Cuando se llama accum1, se incrementará el valor de contador en el entorno al que se hace referencia. Ahora podemos llamar al accum1 una función con estado local o cierre.

He descrito algunos usos prácticos de los cierres en mi blog . (Ver las publicaciones "Diseños peligrosos" y "Al pasar el mensaje").

2

Hay una gran cantidad de respuestas ya, pero voy a añadir otra cualquiera ...

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

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

El procedimiento anidado todavía se refiere a variables que eran locales para el padre, específicamente a los valores que esas variables tenían cuando se ejecutó la línea que hace la referencia de función, 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 anidado construido en diferentes momentos pueden estar utilizando valores pasados ​​diferentes de las mismas variables.

La resolución a 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.

una pitón lambda es un ejemplo sencillo de estilo funcional ...

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

funcref = parent() 
print funcref() 

Mis pitones un poco oxidado, pero creo que es correcto. El punto es que la función anidada (lambda) todavía 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.

1

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

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

Cuestiones relacionadas