c# functional-programming closures

c# - Cuándo usar el cierre?



functional-programming closures (9)

He visto muestras de cierre de - ¿Qué es un ''Cierre''?

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

Específicamente, ¿escenarios en los que el cierre tiene sentido?

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

Para no ofender a nadie, envíe muestras de código en un idioma como c #, python, javascript, ruby, etc.
Lo siento, todavía no entiendo los lenguajes funcionales.


Aquí hay un ejemplo de la biblioteca estándar de Python, inspect.py. Actualmente 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, 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 combinación, y recorre de manera recursiva 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 cierres, esto dice

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 los lenguajes OO, normalmente no usa cierres con demasiada frecuencia, ya que puede usar objetos para pasar métodos de estado y ligados, cuando su idioma los 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 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 sin parámetros invocada 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 enlazados, podrías 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í, la lambda se "auto" no de un parámetro, sino del contexto.


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

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

Pero digamos, por razones de argumento, que solo queremos llamar a f() con un argumento a la vez. Entonces, en lugar de f(2, 3) , queremos f(2)(3) . Esto se puede hacer así:

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 a 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 cierra sobre esas variables, de ahí el término cierre. Cuando llamamos a g(3) , se accede a la variable a (que está vinculada por la definición de f ) por g() , devolviendo 2 + 3 => 5

Esto es útil en varios escenarios. Por ejemplo, si tuviera una función que aceptara una gran cantidad de argumentos, pero solo unos pocos me fueran útiles, podría escribir una función genérica como esta:

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 ahora es una función que solo necesita 3 argumentos, en lugar de 9. useful_function 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 de curry nuevamente.


En Lua y Python es algo muy natural de hacer cuando se trata de "solo codificar", porque en el momento en que haces referencia a algo que no es un parámetro, estás 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)


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

Por ejemplo, en un lenguaje como Lisp, uno puede definir una función que devuelve una función (con un entorno cerrado) para agregar una cantidad predefinida a su argumento de esta manera:

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

y úsalo así:

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 idioma sin cierres, harías 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 usarlo así:

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

El cierre implícitamente lleva consigo su entorno; 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.

Uno puede implementar un sistema de objetos con cierres.


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 ni siquiera necesitas el `ParameterizedThreadStart ''que acabas de hacer:

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

Compare eso para crear una clase con un nombre y valor

O del mismo modo con la búsqueda de una lista (usando una lambda esta vez):

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

Nuevamente, 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 la forma en que el compilador lo hace 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); }); }

Es posible que esto no imprima lo que espera, ya que la variable i se usa para cada uno. Puedes ver cualquier combinación de repeticiones, incluso 10 10''s. Debe analizar cuidadosamente las variables capturadas en C #:

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

Aquí cada j se captura por separado (es decir, una instancia de clase generada por el compilador diferente).

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


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 de alguna manera es una reminiscencia de:

El venerable maestro Qc Na estaba caminando con su alumno, Anton. Esperando llevar al maestro a una discusión, Anton dijo: "Maestro, he oído que los objetos son algo muy bueno, ¿es cierto?" Qc Na miró compasivamente a su alumno y respondió: "Alumno insensato: los objetos son simplemente cierres de pobres".

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

En su siguiente caminata con Qc Na, Anton intentó impresionar a su maestro diciendo: "Maestro, he estudiado diligentemente el asunto, y ahora entiendo que los objetos son verdaderamente cierres de pobres". Qc Na respondió golpeando a Anton con su bastón, diciendo "¿Cuándo aprenderás? Los cierres son un objeto de los pobres". En ese momento, Anton se iluminó.

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


Este artículo incluye dos ejemplos de dónde los cierres son realmente útiles: cierre


Me dijeron que hay más usos en Haskell, pero solo he tenido el placer de utilizar cierres en JavaScript, y en JavaScript no veo 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 acerca de 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 de que "cerrar" no es realmente la mejor palabra para describir el concepto. Creo que debería llamarse "alcance volador".


Como una de las notas de respuestas anteriores, a menudo te encuentras utilizándolas sin apenas darte cuenta de que lo estás haciendo.

Un ejemplo de ello es que se usan con mucha frecuencia para configurar el manejo de eventos de IU para obtener la reutilización de código al tiempo que se permite el acceso al contexto de IU. Aquí hay un ejemplo de cómo la definición de una función de controlador anónimo para un evento de clic crea un cierre que incluye los button y color parámetros de color 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 mayor precisión, vale la pena señalar que el cierre no se crea realmente hasta que setColor() función setColor() .