2008-11-02 14 views
36

He visto muestras de cierre de - What is a 'Closure'?¿Cuándo usar el cierre?

¿Alguien puede dar un ejemplo simple de cuándo usar el cierre?

En concreto, ¿escenarios en los que el cierre tiene sentido?

Supongamos que el idioma no tiene soporte de cierre, ¿cómo se lograría algo similar?

Para no ofender a nadie, publique ejemplos de código en un idioma como C#, python, javascript, ruby, etc.
Lo siento, todavía no entiendo los idiomas funcionales.

+0

Creo que puede echar un vistazo al siguiente enlace sobre los patrones de diseño que los cierres nos permiten implementar de manera concisa [publicar en los patrones de diseño de cierre] (http://arturoherrero.com/2012/04/25/closure -design-patterns /) – Sudarshan

+0

Es desafortunado para la comunidad cuando se cierran preguntas como esta. Es una pregunta perfectamente razonable y que muchos cierres de aprendizaje, un tema difícil, tendrán. Comprender los casos de uso del mundo real es una gran manera de acelerar la curva de aprendizaje de un tema difícil. Afortunadamente, hubo muchas respuestas excelentes antes de que se cerrara. – Matt

Respuesta

30

Los cierres son simplemente excelentes herramientas. ¿Cuándo usarlos? Cada vez que quiera ... Como ya se ha dicho, la alternativa es escribir una clase; por ejemplo, pre C# 2.0, crear un hilo parametrizado fue una lucha real. Con C# 2.0 que ni siquiera necesita el `ParameterizedThreadStart' que acaba de hacer:

string name = // blah 
int value = // blah 
new Thread((ThreadStart)delegate { DoWork(name, value);}); // or inline if short 

Compare esto con la creación de una clase con un nombre y un valor

O lo mismo con la búsqueda de una lista (con un lambda esta vez):

Person person = list.Find(x=>x.Age > minAge && x.Region == region); 

otra vez - la alternativa sería escribir una clase con dos propiedades y un método:

internal sealed class PersonFinder 
{ 
    public PersonFinder(int minAge, string region) 
    { 
     this.minAge = minAge; 
     this.region = region; 
    } 
    private readonly int minAge; 
    private readonly string region; 
    public bool IsMatch(Person person) 
    { 
     return person.Age > minAge && person.Region == region; 
    } 
} 
... 
Person person = list.Find(new PersonFinder(minAge,region).IsMatch); 

Esto es bastante comparable a como lo hace el compilador bajo el capó (en realidad, usa campos públicos de lectura/escritura, no solo de forma privada).

La mayor advertencia con capturas de C# es observar el alcance; por ejemplo:

 for(int i = 0 ; i < 10 ; i++) { 
      ThreadPool.QueueUserWorkItem(delegate 
      { 
       Console.WriteLine(i); 
      }); 
     } 

Esto no puede imprimir lo que se espera, ya que la variable de i se utiliza para cada uno. Puedes ver cualquier combinación de repeticiones, incluso 10 10's. Es necesario cuidadosamente alcance variables de capturados en C#:

 for(int i = 0 ; i < 10 ; i++) { 
      int j = i; 
      ThreadPool.QueueUserWorkItem(delegate 
      { 
       Console.WriteLine(j); 
      }); 
     } 

Aquí cada j es capturado por separado (es decir, una instancia de clase generado por el compilador diferente).

Jon Skeet tiene una buena entrada de blog que cubre cierres de C# y java here; o para obtener más detalles, consulte su libro C# in Depth, que tiene un capítulo completo sobre ellos.

+0

Un poco fuera de tema, pero ¿podría sugerirnos cómo se pronuncia "Person person = list.Find (x => x.Age> minAge && x.Region == region);" si se lee en voz alta? –

+3

"asignar persona variable (de tipo Persona) con list.Find: x va a x.Age mayor que minAge y x.Region es igual a region". Aunque probablemente abreviaré a: "... con la lista. Encuentra dónde Edad mayor que minAge y Region equivale a región". Pero "goes to" es una interpretación común de "=>". –

+0

En relación con esto, podría estar interesado en responder http://stackoverflow.com/questions/1962539/using-closures-to-keep-track-of-a-variable-good-idea-or-dirty-trick :) – RCIX

11

Normalmente, si uno no tiene cierres, uno debe definir una clase para llevar consigo el equivalente al entorno del cierre, y pasarlo.

Por ejemplo, en un lenguaje como Lisp, se puede definir una función que devuelve una función (con un entorno más cerrado) para añadir una cierta cantidad predefinida a su argumento de esta manera:

(defun make-adder (how-much) 
    (lambda (x) 
    (+ x how-much))) 

y utilizarlo de esta manera:

cl-user(2): (make-adder 5) 
#<Interpreted Closure (:internal make-adder) @ #x10009ef272> 
cl-user(3): (funcall * 3)  ; calls the function you just made with the argument '3'. 
8 

En un lenguaje sin cierres, que haría algo como esto:

public class Adder { 
    private int howMuch; 

    public Adder(int h) { 
    howMuch = h; 
    } 

    public int doAdd(int x) { 
    return x + howMuch; 
    } 
} 

y luego utilizar de esta manera:

Adder addFive = new Adder(5); 
int addedFive = addFive.doAdd(3); 
// addedFive is now 8. 

El cierre lleva implícita su entorno con ella; se refiere sin problemas a ese entorno desde el interior de la parte ejecutora (el lambda). Sin cierres, debe hacer que ese entorno sea explícito.

Eso debería explicarte cuándo utilizarías los cierres: todo el tiempo. La mayoría de los casos en los que se crea una instancia de una clase para llevar consigo algún estado de otra parte del cálculo y aplicarlo en otro lugar se sustituyen elegantemente por cierres en los idiomas que los respaldan.

Se puede implementar un sistema de objetos con cierres.

+0

Gracias por su respuesta. ¿Podría describir una muestra de código donde es efectivo usar el cierre? – shahkalpesh

1

En Lua y Python es algo muy natural cuando se trata de "solo codificar", porque en el momento en que hace referencia a algo que no es un parámetro, está cerrando. (por lo que la mayoría de estos serán bastante aburridos como ejemplos.)

En cuanto a un caso concreto, imagine un sistema de deshacer/rehacer, donde los pasos son pares de (deshacer(), rehacer()) cierres. Las formas más engorrosas de hacer eso podrían ser: (a) Hacer que las clases no modificables tengan un método especial con argumentos universalmente tontos, o (b) la subclase UnReDooperación muchas veces.

Otro ejemplo concreto son las listas infinitas: en lugar de trabajar con contenedores genéricos, usted tiene una función que recupera el siguiente elemento. (Esto es parte del poder de los iteradores.) En este caso, puede mantener solo un poco de estado (el siguiente entero, para la lista de todos los enteros no negativos o similares) o una referencia a una posición en un contenedor real De cualquier manera, es una función que hace referencia a algo que está fuera de sí mismo.(En el caso de lista infinita, las variables de estado deben ser variables de cierre, porque de lo contrario estarían limpias para cada llamada)

+0

Gracias por su respuesta. ¿Podría describir una muestra de código donde es efectivo usar el cierre? – shahkalpesh

22

Estoy de acuerdo con una respuesta anterior de "todo el tiempo". Cuando programa en un lenguaje funcional o en cualquier otro idioma donde las lambdas y los cierres son comunes, los usa sin siquiera darse cuenta. Es como preguntar "¿cuál es el escenario para una función?" o "¿cuál es el escenario para un ciclo?" Esto no quiere decir que la pregunta original suene tonta, sino que es para señalar que hay construcciones en idiomas que no se definen en términos de escenarios específicos. Simplemente los usa todo el tiempo, para todo, es una segunda naturaleza.

Esto es una reminiscencia de alguna manera:

El venerable maestro Qc Na caminaba con su alumno, Anton. Con la esperanza de incitar al maestro a una discusión, Anton dijo: "Maestro, he oído que los objetos son algo muy bueno, ¿es verdad ?" Qc Na miró con lástima a su alumno y respondió: "tonto alumno - los objetos son meramente un pobre cierre de hombre ".

Castigado, Anton se despidió de su maestro y regresó a su celda, con la intención de estudiar los cierres. Él leyó cuidadosamente toda la serie de papeles "Lambda: The Ultimate ..." y sus primos , e implementó un pequeño intérprete de esquema con un sistema de objetos basado en el cierre . Él aprendió mucho, y esperaba informando a su maestro de su progreso.

En su siguiente paseo con Qc Na, Anton trató de impresionar a su maestro por la diciendo "Maestro, tengo diligencia estudiaron la materia, y ahora entiendo que los objetos son realmente cierres de un hombre pobre." Qc Na respondió golpeando Anton con su bastón, diciendo "Cuando va a aprender? Los cierres son un objeto del hombre pobre ". En ese momento, Anton se iluminó.

(http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg03277.html)

+0

Agradable. Como la programación funcional es diferente (en el sentido de administrar el estado), es natural usar cierres. No tengo mucha experiencia en programación funcional. Uso C#, introduce este concepto. Un ejemplo aún sería apreciado. – shahkalpesh

+0

Al observar la analogía que proporcionó (ya que FP no tiene un concepto de objeto), el estado pasa de una función a otra y, por lo tanto, se gestiona el estado de toda la aplicación. ¿verdad? – shahkalpesh

+0

En FP puro, generalmente no te importa el estado en absoluto. Cuando necesitas estado, lo haces explícito y lo pasas. –

14

El ejemplo más simple de utilizar cierres es en algo llamado adobado. Básicamente, supongamos que tenemos una función f() que, cuando se llama con dos argumentos a y b, los suma. Así, en Python, tenemos:

def f(a, b): 
    return a + b 

Pero digamos que, por el bien del argumento, que sólo queremos llamar f() con un argumento a la vez. Entonces, en lugar de f(2, 3), queremos f(2)(3). Esto se puede hacer de esta manera:

def f(a): 
    def g(b): # Function-within-a-function 
     return a + b # The value of a is present in the scope of g() 
    return g # f() returns a one-argument function g() 

Ahora, cuando llamamos f(2), obtenemos una nueva función, g(); esta nueva función lleva consigo variables del alcance de f(), por lo que se dice que se cierran sobre esas variables, de ahí el término cierre.Cuando llamamos g(3), la variable a (que está obligado por la definición de f) se accede por g(), volviendo 2 + 3 => 5

Esto es útil en varios escenarios. Por ejemplo, si tuviera una función que acepta un gran número de argumentos, pero sólo unos pocos de ellos eran útiles para mí, podría escribir una función genérica de este modo:

def many_arguments(a, b, c, d, e, f, g, h, i): 
    return # SOMETHING 

def curry(function, **curry_args): 
    # call is a closure which closes over the environment of curry. 
    def call(*call_args): 
     # Call the function with both the curry args and the call args, returning 
     # the result. 
     return function(*call_args, **curry_args) 
    # Return the closure. 
    return call 

useful_function = curry(many_arguments, a=1, b=2, c=3, d=4, e=5, f=6) 

useful_function es ahora una función que sólo necesita 3 argumentos, en lugar de 9. Evito tener que repetirme, y también he creado una solución genérica ; si escribo otra función de muchos argumentos, puedo usar la herramienta curry nuevamente.

3

Aquí hay un ejemplo de la biblioteca estándar de Python, inspect.py. Lee actualmente

def strseq(object, convert, join=joinseq): 
    """Recursively walk a sequence, stringifying each element.""" 
    if type(object) in (list, tuple): 
     return join(map(lambda o, c=convert, j=join: strseq(o, c, j), object)) 
    else: 
     return convert(object) 

Esto tiene, como parámetros, una función de conversión y una función de unir, de forma recursiva y se acerca listas y tuplas. La recursión se implementa usando map(), donde el primer parámetro es una función. El código es anterior a la compatibilidad con cierres en Python, por lo que necesita dos argumentos predeterminados adicionales para pasar la conversión y unirse a la llamada recursiva.Con el cierre, esto se lee

def strseq(object, convert, join=joinseq): 
    """Recursively walk a sequence, stringifying each element.""" 
    if type(object) in (list, tuple): 
     return join(map(lambda o: strseq(o, convert, join), object)) 
    else: 
     return convert(object) 

En lenguajes orientados a objetos, que normalmente no se utiliza muy a menudo el cierre, como se puede usar objetos para pasar del estado - métodos consolidados y, cuando su lengua las tiene. Cuando Python no tenía cierres, la gente decía que Python emula cierres con objetos, mientras que Lisp emula objetos con cierres. Como un ejemplo de IDLE (ClassBrowser.py):

class ClassBrowser: # shortened 
    def close(self, event=None): 
     self.top.destroy() 
     self.node.destroy() 
    def init(self, flist): 
     top.bind("<Escape>", self.close) 

Aquí, self.close es una devolución de llamada parámetro-menos invoca cuando se presiona Escape. Sin embargo, la implementación cercana necesita parámetros, a saber, self y luego self.top, self.node. Si Python no tenía métodos consolidados, se podría escribir

class ClassBrowser: 
    def close(self, event=None): 
     self.top.destroy() 
     self.node.destroy() 
    def init(self, flist): 
     top.bind("<Escape>", lambda:self.close()) 

Aquí, el lambda obtendría "auto" no de un parámetro, pero a partir del contexto.

0

Este artículo incluye dos ejemplos en los que los cierres son realmente útiles: Closure

1

Me han dicho que son más usos en Haskell, pero sólo he tenido el placer de usar cierres en javascript, y en javascript No veo mucho el punto. Mi primer instinto fue gritar "oh no, no otra vez", en un lío que la implementación debe ser para hacer que los cierres funcionen. Después de leer sobre cómo se implementaron los cierres (en JavaScript de todos modos), ahora no me parece tan malo y la implementación parece algo elegante, al menos para mí.

Pero de eso me di cuenta que el "cierre" no es realmente la mejor palabra para describir el concepto. Creo que debería llamarse "alcance volador".

1

Como una de las notas de respuestas anteriores, a menudo se encuentra utilizándolas sin apenas darse cuenta de que lo está.

Un ejemplo de ello es que se utilizan con mucha frecuencia en la configuración de la gestión de eventos de IU para obtener la reutilización de código al tiempo que se permite el acceso al contexto de IU. Aquí está un ejemplo de cómo definir una función de controlador anónimo para un evento de clic crea un cierre que incluye las button y color parámetros de la función setColor():

function setColor(button, color) { 

     button.addEventListener("click", function() 
     { 
      button.style.backgroundColor = color; 
     }, false); 
} 

window.onload = function() { 
     setColor(document.getElementById("StartButton"), "green"); 
     setColor(document.getElementById("StopButton"), "red"); 
} 

Nota: para la exactitud vale la pena señalar que el cierre no se crea realmente hasta que finalice la función setColor().

Cuestiones relacionadas