¿Por qué un programa usaría un cierre?

58

Después de leer muchas publicaciones que explican los cierres aquí, todavía me falta un concepto clave: ¿por qué escribir un cierre? ¿Qué tarea específica estaría realizando un programador que podría ser mejor atendida por un cierre?

Ejemplos de cierres en Swift son los accesos de un NSUrl y el uso del geocodificador inverso. Aquí hay uno de esos ejemplos. Desafortunadamente, esos cursos solo presentan el cierre; no explican por qué la solución de código se escribe como un cierre.

Un ejemplo de un problema de programación del mundo real que podría hacer que mi cerebro diga: "Ajá, debería escribir un cierre para esto", sería más informativo que una discusión teórica. No hay escasez de discusiones teóricas disponibles en este sitio.

Bendrix
fuente
77
"Explicación de cierres recientemente revisada aquí" - ¿Te falta un enlace?
Dan Pichelman
2
Debería poner esa aclaración sobre la tarea asincrónica en un comentario, debajo de la respuesta relevante. No es parte de su pregunta original, y el respondedor nunca será notificado de su edición.
Robert Harvey
55
Solo un dato: el punto crucial de los cierres es el alcance léxico (a diferencia del alcance dinámico), los cierres sin alcance léxico son un poco inútiles. Los cierres a veces se llaman cierres léxicos.
Hoffmann
1
Los cierres le permiten instanciar funciones .
user253751
1
Lea cualquier buen libro sobre programación funcional. Quizás comience con SICP
Basile Starynkevitch

Respuestas:

33

En primer lugar, no hay nada imposible sin usar cierres. Siempre puede reemplazar un cierre por un objeto que implemente una interfaz específica. Es solo cuestión de brevedad y acoplamiento reducido.

En segundo lugar, tenga en cuenta que los cierres a menudo se usan de manera inapropiada, donde una referencia de función simple u otra construcción sería más clara. No debe tomar cada ejemplo que vea como una mejor práctica.

Cuando los cierres realmente brillan sobre otras construcciones es cuando se usan funciones de orden superior, cuando realmente se necesita comunicar el estado, y se puede hacer una línea, como en este ejemplo de JavaScript de la página de wikipedia sobre cierres :

// Return a list of all books with at least 'threshold' copies sold.
function bestSellingBooks(threshold) {
  return bookList.filter(
      function (book) { return book.sales >= threshold; }
    );
}

Aquí, thresholdse comunica de manera muy sucinta y natural desde donde se define hasta donde se usa. Su alcance es precisamente limitado lo más pequeño posible. filterno tiene que escribirse para permitir la posibilidad de pasar datos definidos por el cliente como un umbral. No tenemos que definir ninguna estructura intermedia con el único propósito de comunicar el umbral en esta pequeña función. Es completamente autónomo.

Usted puede escribir esto sin un cierre, pero se requerirá un código mucho más y ser más difícil de seguir. Además, JavaScript tiene una sintaxis lambda bastante detallada. En Scala, por ejemplo, todo el cuerpo de la función sería:

bookList filter (_.sales >= threshold)

Sin embargo, si puede usar ECMAScript 6 , gracias a las funciones de flecha gruesa, incluso el código JavaScript se vuelve mucho más simple y se puede colocar en una sola línea.

const bestSellingBooks = (threshold) => bookList.filter(book => book.sales >= threshold);

En su propio código, busque lugares donde genere una gran cantidad de repeticiones solo para comunicar valores temporales de un lugar a otro. Estas son excelentes oportunidades para considerar reemplazarlas con un cierre.

Karl Bielefeldt
fuente
¿Cómo exactamente los cierres reducen el acoplamiento?
sbichenko
Sin cierres, debe imponer restricciones tanto en el bestSellingBookscódigo como en el filtercódigo, como una interfaz específica o un argumento de datos de usuario, para poder comunicar los thresholddatos. Eso une las dos funciones en formas mucho menos reutilizables.
Karl Bielefeldt
51

A modo de explicación, voy a tomar prestado un código de esta excelente publicación de blog sobre cierres . Es JavaScript, pero ese es el lenguaje que la mayoría de las publicaciones de blog hablan sobre el uso de cierres, porque los cierres son muy importantes en JavaScript.

Digamos que desea representar una matriz como una tabla HTML. Podrías hacerlo así:

function renderArrayAsHtmlTable (array) {
  var table = "<table>";
  for (var idx in array) {
    var object = array[idx];
    table += "<tr><td>" + object + "</td></tr>";
  }
  table += "</table>";
  return table;
}

Pero estás a merced de JavaScript en cuanto a cómo se representará cada elemento de la matriz. Si desea controlar la representación, puede hacer esto:

function renderArrayAsHtmlTable (array, renderer) {
  var table = "<table>";
  for (var idx in array) {
    var object = array[idx];
    table += "<tr><td>" + renderer(object) + "</td></tr>";
  }
  table += "</table>";
  return table;
}

Y ahora puede pasar una función que devuelve la representación que desea.

¿Qué sucede si desea mostrar un total acumulado en cada fila de la tabla? Necesitarías una variable para rastrear ese total, ¿no? Un cierre le permite escribir una función de renderizador que se cierra sobre la variable de total acumulado, y le permite escribir un renderizador que puede realizar un seguimiento del total acumulado:

function intTableWithTotals (intArray) {
  var total = 0;
  var renderInt = function (i) {
    total += i;
    return "Int: " + i + ", running total: " + total;
  };
  return renderObjectsInTable(intArray, renderInt);
}

La magia que está sucediendo aquí es que renderInt retiene el acceso a la totalvariable, a pesar de que renderIntse llama repetidamente y sale.

En un lenguaje más tradicionalmente orientado a objetos que JavaScript, puede escribir una clase que contenga esta variable total y pasarla en lugar de crear un cierre. Pero un cierre es una forma mucho más potente, limpia y elegante de hacerlo.

Otras lecturas

Robert Harvey
fuente
10
En general, puede decir que "función de primera clase == objeto con un solo método", "Cierre == objeto con un solo método y estado", "objeto == paquete de cierres".
Jörg W Mittag
Se ve bien. Otra cosa, y esto podría ser pedantería, es que Javascript está orientado a objetos, y puede crear un objeto que contenga la variable total y pasarlo. Los cierres siguen siendo mucho más potentes, limpios y elegantes, y también hacen que Javascript sea mucho más idiomático, pero la forma en que está escrito puede implicar que Javascript no puede hacer esto de una manera orientada a objetos.
KRyan
2
En realidad, para ser realmente pedante: ¡los cierres son lo que hace que JavaScript esté orientado a objetos en primer lugar! OO se trata de la abstracción de datos, y los cierres son la forma de realizar la abstracción de datos en JavaScript.
Jörg W Mittag
@ JörgWMittag: como puede ver las nubes como objetos con un solo método, puede ver los objetos como una colección de cierres que se cierran sobre las mismas variables: las variables miembro de un objeto son solo las variables locales del constructor del objeto y los métodos del objeto son cierres definidos dentro del alcance del constructor y disponibles para ser llamados posteriormente. La función de constructor devuelve una función de orden superior (el objeto) que puede despacharse en cada cierre de acuerdo con el nombre del método que se utiliza para la invocación.
Giorgio
@Giorgio: De hecho, así es como los objetos se implementan típicamente en Scheme, por ejemplo, y de hecho también es cómo se implementan los objetos en JavaScript (no es sorprendente teniendo en cuenta su estrecha relación con Scheme, después de todo, Brendan Eich fue contratado originalmente para diseñar un Schele dialecto e implemente un intérprete de Scheme integrado dentro de Netscape Navigator, y solo más tarde se le ordenó hacer un lenguaje "con objetos que se parezca a C ++", después de lo cual realizó la cantidad mínima absoluta de cambios para cumplir con esos requisitos de marketing).
Jörg W Mittag
23

El propósito de closureses simplemente preservar el estado; de ahí el nombre closure: se cierra sobre el estado. Para facilitar la explicación adicional, usaré Javascript.

Normalmente tienes una función

function sayHello(){
    var txt="Hello";
    return txt;
}

donde el alcance de las variables está vinculado a esta función. Entonces, después de la ejecución, la variable txtqueda fuera de alcance. No hay forma de acceder o usarlo una vez que la función ha finalizado la ejecución.

Los cierres son construcciones de lenguaje, que permiten, como se dijo anteriormente, preservar el estado de las variables y prolongar así el alcance.

Esto podría ser útil en diferentes casos. Un caso de uso es la construcción de funciones de orden superior .

En matemáticas e informática, una función de orden superior (también forma funcional, funcional o functor) es una función que realiza al menos uno de los siguientes: 1

  • toma una o más funciones como entrada
  • emite una función

Un ejemplo simple, pero ciertamente no muy útil es:

 makeadder=function(a){
     return function(b){
         return a+b;
     }
 }

 add5=makeadder(5);
 console.log(add5(10)); 

Usted define una función makedadder, que toma un parámetro como entrada y devuelve una función . Hay una función externafunction(a){} y una interna. function(b){}{} Además, define (implícitamente) otra función add5como resultado de llamar a la función de orden superior makeadder. makeadder(5)devuelve una función anónima ( interna ), que a su vez toma 1 parámetro y devuelve la suma del parámetro de la función externa y el parámetro de la función interna .

El truco es que, al devolver la función interna , que realiza la suma real, se conserva el alcance del parámetro de la función externa ( a). add5 recuerda , que el parámetro aera 5.

O para mostrar un ejemplo al menos de alguna manera útil:

  makeTag=function(openTag, closeTag){
     return function(content){
         return openTag +content +closeTag;
     }
 }

 table=makeTag("<table>","</table>")
 tr=makeTag("<tr>", "</tr>");
 td=makeTag("<td>","</td>");
 console.log(table(tr(td("I am a Row"))));

Otro caso de uso común es la llamada expresión de función IIFE = invocada inmediatamente. Es muy común en javascript falsificar variables de miembros privados. Esto se hace a través de una función, que crea un ámbito privado = closure, porque se invoca inmediatamente después de la definición. La estructura es function(){}(). Observe los corchetes ()después de la definición. Esto hace posible usarlo para la creación de objetos con un patrón de módulo revelador . El truco es crear un ámbito y devolver un objeto, que tiene acceso a este ámbito después de la ejecución del IIFE.

El ejemplo de Addi se ve así:

 var myRevealingModule = (function () {

         var privateVar = "Ben Cherry",
             publicVar = "Hey there!";

         function privateFunction() {
             console.log( "Name:" + privateVar );
         }

         function publicSetName( strName ) {
             privateVar = strName;
         }

         function publicGetName() {
             privateFunction();
         }


         // Reveal public pointers to
         // private functions and properties

         return {
             setName: publicSetName,
             greeting: publicVar,
             getName: publicGetName
         };

     })();

 myRevealingModule.setName( "Paul Kinlan" );

El objeto devuelto tiene referencias a funciones (por ejemplo publicSetName), que a su vez tienen acceso a variables "privadas" privateVar.

Pero estos son casos de uso más especiales para Javascript.

¿Qué tarea específica estaría realizando un programador que podría ser mejor atendida por un cierre?

Hay varias razones para eso. Una podría ser que es natural para él, ya que sigue un paradigma funcional . O en Javascript: es mera necesidad confiar en los cierres para sortear algunas peculiaridades del lenguaje.

Thomas Junk
fuente
"El propósito de los cierres es simplemente preservar el estado; de ahí el cierre del nombre: se cierra sobre el estado". Depende del idioma, realmente. Los cierres se cierran sobre nombres / variables externas. Estos pueden denotar estado (ubicaciones de memoria que pueden cambiarse) pero también pueden denotar valores (inmutables). Los cierres en Haskell no conservan el estado: conservan la información que se conocía en ese momento y en el contexto en el que se creó el cierre.
Giorgio
1
»Conservan la información que se conocía en ese momento y en el contexto en el que se creó el cierre« independientemente de si se puede cambiar o no, es un estado , tal vez un estado inmutable .
Thomas Junk
16

Hay dos casos de uso principales para los cierres:

  1. Asincronía Supongamos que desea realizar una tarea que llevará un tiempo y luego hacer algo cuando haya terminado. Puede hacer que su código espere a que se realice, lo que bloquea la ejecución posterior y puede hacer que su programa no responda, o llame a su tarea de forma asincrónica y diga "comience esta larga tarea en segundo plano, y cuando termine, ejecute este cierre", donde el cierre contiene el código para ejecutar cuando se hace.

  2. Callbacks Estos también se conocen como "delegados" o "controladores de eventos" según el idioma y la plataforma. La idea es que tenga un objeto personalizable que, en ciertos puntos bien definidos, ejecutará un evento , que ejecuta un cierre pasado por el código que lo configura. Por ejemplo, en la interfaz de usuario de su programa, es posible que tenga un botón, y le da un cierre que contiene el código que se ejecutará cuando el usuario haga clic en el botón.

Hay varios otros usos para los cierres, pero esos son los dos principales.

Mason Wheeler
fuente
23
Entonces, básicamente devoluciones de llamada, ya que el primer ejemplo también es una devolución de llamada.
Robert Harvey
2
@RobertHarvey: Técnicamente cierto, pero son diferentes modelos mentales. Por ejemplo, (en términos generales), espera que se llame al controlador de eventos varias veces, pero su continuación asincrónica solo se llamará una vez. Pero sí, técnicamente cualquier cosa que haría con un cierre es una devolución de llamada. (A menos que lo esté convirtiendo en un árbol de expresión, pero ese es un asunto completamente diferente.);)
Mason Wheeler
44
@RobertHarvey: ver los cierres como devoluciones de llamada lo pone en un estado de ánimo que realmente le impedirá usarlos de manera efectiva. Los cierres son devoluciones de llamada con esteroides.
gnasher729
13
@MasonWheeler Ponlo de esta manera, suena mal. Las devoluciones de llamada no tienen nada que ver con los cierres; por supuesto, puede devolver una función dentro de un cierre o llamar a un cierre como devolución de llamada; pero una devolución de llamada no es necesaria un cierre. Una devolución de llamada es solo una simple llamada de función. Un controlador de eventos no es per se un cierre. Un delegado no es necesario un cierre: se trata principalmente de punteros de función C #. Por supuesto, podría usarlo para construir un cierre. Tu explicación es imprecisa. El punto es cerrar el estado y hacer uso de él.
Thomas Junk
55
Tal vez hay aquí connotaciones específicas de JS que me están haciendo entender mal, pero esta respuesta me parece absolutamente incorrecta. La asincronía o las devoluciones de llamada pueden usar cualquier cosa que sea 'invocable'. No se requiere un cierre para ninguno de ellos, una función regular funcionará bien. Mientras tanto, un cierre, como se define en la programación funcional, o como se usa en Python, es útil, como dice Thomas, porque 'cierra' algún estado, es decir, una variable, y le da a una función en un alcance interno acceso consistente a esa variable valor incluso si la función se llama y sale muchas veces.
Jonathan Hartley
13

Un par de otros ejemplos:

Clasificación
La mayoría de las funciones de clasificación operan comparando pares de objetos. Se necesita alguna técnica de comparación. Restringir la comparación a un operador específico significa un tipo bastante inflexible. Un enfoque mucho mejor es recibir una función de comparación como argumento para la función de clasificación. A veces, una función de comparación sin estado funciona bien (por ejemplo, ordenar una lista de números o nombres), pero ¿qué pasa si la comparación necesita un estado?

Por ejemplo, considere ordenar una lista de ciudades por distancia a alguna ubicación específica. Una solución fea es almacenar las coordenadas de esa ubicación en una variable global. Esto hace que la función de comparación sea apátrida, pero a costa de una variable global.

Este enfoque impide tener múltiples hilos simultáneamente ordenando la misma lista de ciudades por su distancia a dos ubicaciones diferentes. Un cierre que encierra la ubicación resuelve este problema y elimina la necesidad de una variable global.


Números aleatorios
El original rand()no tomó argumentos. Los generadores de números pseudoaleatorios necesitan estado. Algunos (por ejemplo, Mersenne Twister) necesitan mucho estado. Incluso el simple pero terrible rand()estado necesario. Lea un artículo de un diario de matemáticas sobre un nuevo generador de números aleatorios e inevitablemente verá variables globales. Eso es bueno para los desarrolladores de la técnica, no tan bueno para las personas que llaman. Encapsular ese estado en una estructura y pasar la estructura al generador de números aleatorios es una forma de evitar el problema de los datos globales. Este es el enfoque utilizado en muchos lenguajes que no son OO para hacer reentrante un generador de números aleatorios. Un cierre oculta ese estado de la persona que llama. Un cierre ofrece la secuencia de llamada simple rand()y la reentrada del estado encapsulado.

Hay más en los números aleatorios que solo un PRNG. La mayoría de las personas que desean aleatoriedad quieren que se distribuya de cierta manera. Comenzaré con números extraídos al azar entre 0 y 1, o U (0,1) para abreviar. Cualquier PRNG que genere enteros entre 0 y algún máximo funcionará; simplemente divida (como punto flotante) el entero aleatorio por el máximo. Una forma conveniente y genérica de implementar esto es crear un cierre que tome un cierre (el PRNG) y el máximo como entradas. Ahora tenemos un generador aleatorio genérico y fácil de usar para U (0,1).

Hay una serie de otras distribuciones además de U (0,1). Por ejemplo, una distribución normal con una cierta media y desviación estándar. Cada algoritmo de generador de distribución normal que he encontrado utiliza un generador U (0,1). Una forma conveniente y genérica de crear un generador normal es crear un cierre que encapsule el generador U (0,1), la media y la desviación estándar como estado. Esto es, al menos conceptualmente, un cierre que toma un cierre que toma un cierre como argumento.

David Hammen
fuente
7

Los cierres son equivalentes a los objetos que implementan un método run (), e inversamente, los objetos se pueden emular con cierres.

  • La ventaja de los cierres es que se pueden usar fácilmente en cualquier lugar donde se espere una función: también conocidas como funciones de orden superior, devoluciones de llamada simples (o Patrón de estrategia). No necesita definir una interfaz / clase para crear cierres ad-hoc.

  • La ventaja de los objetos es la posibilidad de tener interacciones más complejas: múltiples métodos y / o interfaces diferentes.

Por lo tanto, el uso de cierre u objetos es principalmente una cuestión de estilo. Aquí hay un ejemplo de cosas que los cierres facilitan pero que es inconveniente implementar con objetos:

 (let ((seen))
    (defun register-name (name)
       (pushnew name seen :test #'string=))

    (defun all-names ()
       (copy-seq seen))

    (defun reset-name-registry ()
       (setf seen nil)))

Básicamente, encapsula un estado oculto al que solo se accede a través de cierres globales: no necesita hacer referencia a ningún objeto, solo use el protocolo definido por las tres funciones.


  • Estoy extendiendo la respuesta para abordar este comentario de supercat *.

Confío en el primer comentario de supercat sobre el hecho de que en algunos idiomas, es posible controlar con precisión la vida útil de los objetos, mientras que lo mismo no es cierto para los cierres. Sin embargo, en el caso de los lenguajes recolectados de basura, la vida útil de los objetos generalmente no tiene límites y, por lo tanto, es posible construir un cierre que podría llamarse en un contexto dinámico donde no debería llamarse (lectura de un cierre después de una secuencia está cerrado, por ejemplo).

Sin embargo, es bastante simple evitar dicho mal uso capturando una variable de control que protegerá la ejecución de un cierre. Más precisamente, esto es lo que tengo en mente (en Common Lisp):

(defun guarded (function)
  (let ((active t))
    (values (lambda (&rest args)
              (when active
                (apply function args)))
            (lambda ()
              (setf active nil)))))

Aquí, tomamos un designador de función functiony devolvemos dos cierres, ambos capturando una variable local llamada active:

  • el primero delega function, solo cuando activees cierto
  • el segundo se establece actionen nil, aka false.

En lugar de (when active ...), por supuesto, es posible tener una (assert active)expresión, que podría generar una excepción en caso de que se llame al cierre cuando no debería serlo. Además, tenga en cuenta que el código inseguro ya puede generar una excepción por sí solo cuando se usa mal, por lo que rara vez necesita un envoltorio de este tipo.

Así es como lo usarías:

(use-package :metabang-bind) ;; for bind

(defun example (obj1 obj2)
  (bind (((:values f f-deactivator)(guarded (lambda () (do-stuff obj1))))
         ((:values g g-deactivator)(guarded (lambda () (do-thing obj2)))))

    ;; ensure the closure are inactive when we exit
    (unwind-protect
         ;; pass closures to other functions
         (progn
           (do-work f)
           (do-work g))

      ;; cleanup code: deactivate closures
      (funcall f-deactivator)
      (funcall g-deactivator))))

Tenga en cuenta que los cierres de desactivación también podrían darse a otras funciones también; aquí, las activevariables locales no se comparten entre fy g; además, además de active, fsolo se refiere obj1y gsolo se refiere a obj2.

El otro punto mencionado por supercat es que los cierres pueden provocar pérdidas de memoria, pero desafortunadamente, es el caso de casi todo en entornos de recolección de basura. Si están disponibles, esto puede resolverse mediante punteros débiles (el cierre en sí mismo puede mantenerse en la memoria, pero no evita la recolección de basura de otros recursos).

volcado de memoria
fuente
1
Una desventaja de los cierres implementados comúnmente es que una vez que un cierre que usa una de las variables locales de un método está expuesto al mundo exterior, el método que define el cierre puede perder el control sobre cuándo se lee y escribe esa variable. No existe una manera conveniente, en la mayoría de los idiomas, de definir un cierre efímero, pasarlo a un método y garantizar que dejará de existir una vez que el método que lo recibe haya regresado, ni existe una forma en idiomas multiproceso para garantiza que no se ejecutará un cierre en un contexto de subprocesos inesperado.
supercat
@supercat: Echa un vistazo a Objective-C y Swift.
gnasher729
1
@supercat ¿No existiría la misma desventaja con los objetos con estado?
Andres F.
@AndresF .: es posible encapsular un objeto con estado en un contenedor que admita la invalidación; si el código encapsula a un privado List<T>en una (clase hipotética) TemporaryMutableListWrapper<T>y lo expone al código externo, se podría asegurar que si invalida el contenedor, el código externo ya no tendrá ninguna forma de manipularlo List<T>. Uno puede diseñar cierres para permitir la invalidación una vez que hayan cumplido su propósito esperado, pero no es conveniente. Existen cierres para hacer ciertos patrones convenientes, y el esfuerzo requerido para protegerlos lo negaría.
supercat
1
@coredump: en C #, si dos cierres tienen alguna variable en común, el mismo objeto generado por el compilador servirá a ambos, ya que los cambios realizados en una variable compartida por un cierre deben ser vistos por el otro. Evitar ese intercambio habría requerido que cada cierre sea su propio objeto que contiene sus propias variables no compartidas, así como una referencia a un objeto compartido que contiene las variables compartidas. No es imposible, pero habría agregado un nivel adicional de desreferenciación a la mayoría de los accesos variables, ralentizando todo.
supercat
6

Nada que no se haya dicho ya, pero quizás un ejemplo más simple.

Aquí hay un ejemplo de JavaScript con tiempos de espera:

// Example function that logs something to the browser's console after a given delay
function delayedLog(message, delay) {
  // this function will be called when the timer runs out
  var fire = function () {
    console.log(message); // closure magic!
  };

  // set a timeout that'll call fire() after a delay
  setTimeout(fire, delay);
}

Lo que sucede aquí, es que cuando delayedLog()se llama, regresa inmediatamente después de establecer el tiempo de espera, y el tiempo de espera sigue marcando en segundo plano.

Pero cuando se agota el tiempo de espera y llama a la fire()función, la consola mostrará el messageque se pasó originalmente delayedLog(), porque todavía está disponible para el fire()cierre. Puede llamar delayedLog()todo lo que quiera, con un mensaje diferente y retrasar cada vez, y hará lo correcto.

Pero imaginemos que JavaScript no tiene cierres.

Una forma sería hacer el setTimeout()bloqueo, más como una función de "suspensión", para que delayedLog()el alcance no desaparezca hasta que se agote el tiempo de espera. Pero bloquear todo no es muy agradable.

Otra forma sería colocar la messagevariable en algún otro ámbito al que se pueda acceder después delayedLog()de que el alcance desaparezca.

Podría usar variables globales, o al menos "de alcance más amplio", pero tendría que descubrir cómo hacer un seguimiento de qué mensaje va con qué tiempo de espera. Pero no puede ser solo una cola FIFO secuencial, porque puede establecer cualquier retraso que desee. Entonces podría ser "primero en entrar, tercero en salir" o algo así. Por lo tanto, necesitaría algún otro medio para vincular una función temporizada a las variables que necesita.

Puede crear una instancia de un objeto de tiempo de espera que "agrupe" el temporizador con el mensaje. El contexto de un objeto es más o menos un alcance que se queda. Luego, el temporizador se ejecutará en el contexto del objeto, de modo que tenga acceso al mensaje correcto. Pero tendría que almacenar ese objeto porque sin ninguna referencia se recolectaría basura (sin cierres, tampoco habría referencias implícitas). Y tendrías que quitar el objeto una vez que se dispara su tiempo de espera, de lo contrario simplemente se quedará. Por lo tanto, necesitaría algún tipo de lista de objetos de tiempo de espera y verificará periódicamente si hay objetos "gastados" para eliminar, o los objetos se agregarían y eliminarían de la lista, y ...

Entonces ... sí, esto se está volviendo aburrido.

Afortunadamente, no tiene que usar un alcance más amplio, o disputar objetos solo para mantener ciertas variables. Debido a que JavaScript tiene cierres, ya tiene exactamente el alcance que necesita. Un ámbito que le da acceso a la messagevariable cuando la necesita. Y debido a eso, puede salirse con la suya escribiendo delayedLog()arriba.

Flambino
fuente
Tengo un problema llamando a eso un cierre . Quizás lo acuñaría por cierre accidental . messageestá incluido en el alcance de la función firey, por lo tanto, se menciona en otras llamadas; pero lo hace accidentalmente, por así decirlo. Técnicamente es un cierre. +1 de todos modos;)
Thomas Junk
@ThomasJunk No estoy seguro de seguirlo. ¿Cómo se vería un " cierre no accidental"? Vi su makeadderejemplo arriba, que, a mis ojos, parece muy parecido. Devuelve una función "curry" que toma un solo argumento en lugar de dos; usando los mismos medios, creo una función que toma cero argumentos. Simplemente no lo devuelvo, sino que se lo paso setTimeout.
Flambino
»Simplemente no lo devuelvo« tal vez, ese es el punto, que marca la diferencia para mí. No puedo articular mi "preocupación";) En términos técnicos, usted tiene 100% de razón. Hacer referencia messageen firegenera el cierre. Y cuando se setTimeoutinvoca, utiliza el estado preservado.
Thomas Junk
1
@ThomasJunk Veo cómo puede oler un poco diferente. Sin embargo, es posible que no comparta su preocupación :) O, en cualquier caso, no lo llamaría "accidental", estoy bastante seguro de que lo codifiqué de esa manera a propósito;)
Flambino
3

PHP puede usarse para ayudar a mostrar un ejemplo real en un lenguaje diferente.

protected function registerRoutes($dic)
{
  $router = $dic['router'];

  $router->map(['GET','OPTIONS'],'/api/users',function($request,$response) use ($dic)
  {
    $controller = $dic['user_api_controller'];
    return $controller->findAllAction($request,$response);
  })->setName('api_users');
}

Básicamente, estoy registrando una función que se ejecutará para el URI / api / users . Esta es en realidad una función de middleware que termina siendo almacenada en una pila. Otras funciones se envolverán a su alrededor. Al igual que Node.js / Express.js lo hace.

El contenedor de inyección de dependencia está disponible (a través de la cláusula de uso) dentro de la función cuando se llama. Es posible hacer algún tipo de clase de acción de ruta, pero este código resulta ser más simple, más rápido y más fácil de mantener.

Cerad
fuente
-1

Un cierre es una pieza de código arbitrario, que incluye variables, que puede manejarse como datos de primera clase.

Un ejemplo trivial es el viejo qsort: es una función para ordenar datos. Tienes que darle un puntero a una función que compara dos objetos. Entonces tienes que escribir una función. Es posible que sea necesario parametrizar esa función, lo que significa que le da variables estáticas. Lo que significa que no es seguro para subprocesos. Estás en DS Entonces escribe una alternativa que toma un cierre en lugar de un puntero de función. Al instante resuelve el problema de la parametrización porque los parámetros se convierten en parte del cierre. Hace que su código sea más legible porque escribe cómo se comparan los objetos directamente con el código que llama a la función de clasificación.

Hay toneladas de situaciones en las que desea realizar alguna acción que requiere una gran cantidad de código de placa de caldera, además de un código pequeño pero esencial que debe adaptarse. Evita el código repetitivo escribiendo una función una vez que toma un parámetro de cierre y hace todo el código repetitivo a su alrededor, y luego puede llamar a esta función y pasar el código para adaptarlo como un cierre. Una forma muy compacta y legible de escribir código.

Tiene una función en la que se debe realizar un código no trivial en muchas situaciones diferentes. Esto solía producir duplicación de código o código contorsionado para que el código no trivial estuviera presente solo una vez. Trivial: asigna un cierre a una variable y lo llama de la manera más obvia donde sea necesario.

Multithreading: iOS / MacOS X tiene funciones para hacer cosas como "realizar este cierre en un hilo de fondo", "... en el hilo principal", "... en el hilo principal, dentro de 10 segundos". Hace que los subprocesos múltiples sean triviales .

Llamadas asincrónicas: eso fue lo que vio el OP. Cualquier llamada que acceda a Internet, o cualquier otra cosa que pueda tomar tiempo (como leer las coordenadas GPS) es algo en lo que no puede esperar el resultado. Entonces, tiene funciones que hacen cosas en segundo plano, y luego pasa un cierre para decirles qué hacer cuando hayan terminado.

Eso es un pequeño comienzo. Cinco situaciones en las que los cierres son revolucionarios en términos de producción de código compacto, legible, confiable y eficiente.

gnasher729
fuente
-4

Un cierre es una forma abreviada de escribir un método donde se va a utilizar. Le ahorra el esfuerzo de declarar y escribir un método separado. Es útil cuando el método se usará solo una vez y la definición del método es corta. Los beneficios se reducen al escribir, ya que no es necesario especificar el nombre de la función, su tipo de retorno o su modificador de acceso. Además, al leer el código no tiene que buscar en otro lado la definición del método.

Lo anterior es un resumen de Comprender las expresiones lambda de Dan Avidar.

Esto aclaró el uso de cierres para mí porque aclara las alternativas (cierre versus método) y los beneficios de cada uno.

El siguiente código se usa una vez y solo una vez durante la configuración. Al escribirlo en viewDidLoad se ahorra la molestia de buscarlo en otro lugar y se acorta el tamaño del código.

myPhoton!.getVariable("Temp", completion: { (result:AnyObject!, error:NSError!) -> Void in
  if let e = error {
    self.getTempLabel.text = "Failed reading temp"
  } else {
    if let res = result as? Float {
    self.getTempLabel.text = "Temperature is \(res) degrees"
    }
  }
})

Además, proporciona un proceso asincrónico para completar sin bloquear otras partes del programa, y ​​un cierre retendrá un valor para su reutilización en llamadas de funciones posteriores.

Otro cierre; este captura un valor ...

let animals = ["fish", "cat", "chicken", "dog"]
let sortedStrings = animals.sorted({ (one: String, two: String) -> Bool in return one > two
}) println(sortedStrings)
Bendrix
fuente
44
Desafortunadamente, esto confunde los cierres y las lambdas. Las lambdas a menudo usan cierres, porque generalmente son mucho más útiles si se definen a) muy brevemente yb) dentro y según el contexto de un método dado, incluidas las variables. Sin embargo, el cierre real no tiene nada que ver con la idea de una lambda, que es básicamente la capacidad de definir una función de primera clase en su lugar y transmitirla para su uso posterior.
Nathan Tuggy
Mientras vota esta respuesta, lea esto ... ¿Cuándo debo votar? Use sus votos negativos cada vez que encuentre una publicación atrozmente descuidada, sin esfuerzo, o una respuesta que sea clara y quizás peligrosamente incorrecta. Mi respuesta puede carecer de algunas de las razones para usar un cierre, pero no es atrozmente descuidada ni peligrosamente incorrecta. Hay muchos documentos y debates sobre cierres que se centran en la programación funcional y la notación lambda. Todo lo que mi respuesta dice es que esta explicación de la programación funcional me ayudó a comprender los cierres. Eso y el valor persistente.
Bendrix
1
La respuesta quizás no sea peligrosa , pero ciertamente es "claramente [...] incorrecta". Utiliza los términos incorrectos para conceptos que son altamente complementarios y, por lo tanto, a menudo se usan juntos, exactamente donde la claridad de la distinción es esencial. Es probable que esto cause problemas de diseño para los programadores que leen esto si no se abordan. (Y dado que, por lo que puedo decir, el ejemplo de código simplemente no contiene ningún cierre, solo una función lambda, no ayuda a explicar por qué los cierres serían útiles.)
Nathan Tuggy
Nathan, ese ejemplo de código es un cierre en Swift. Puedes preguntarle a alguien más si dudas de mí. Entonces, si es un cierre, ¿lo votarías?
Bendrix
1
Apple describe con bastante claridad exactamente lo que quieren decir con cierre en su documentación . "Los cierres son bloques de funcionalidad autónomos que se pueden pasar y usar en su código. Los cierres en Swift son similares a los bloques en C y Objective-C y a lambdas en otros lenguajes de programación". Cuando llegas a la implementación y la terminología puede ser confuso .