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.
Respuestas:
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 :
Aquí,
threshold
se 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.filter
no 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:
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.
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.
fuente
bestSellingBooks
código como en elfilter
código, como una interfaz específica o un argumento de datos de usuario, para poder comunicar losthreshold
datos. Eso une las dos funciones en formas mucho menos reutilizables.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í:
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:
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:
La magia que está sucediendo aquí es que
renderInt
retiene el acceso a latotal
variable, a pesar de querenderInt
se 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
fuente
El propósito de
closures
es simplemente preservar el estado; de ahí el nombreclosure
: se cierra sobre el estado. Para facilitar la explicación adicional, usaré Javascript.Normalmente tienes una función
donde el alcance de las variables está vinculado a esta función. Entonces, después de la ejecución, la variable
txt
queda 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 .
Un ejemplo simple, pero ciertamente no muy útil es:
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ónadd5
como resultado de llamar a la función de orden superiormakeadder
.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ámetroa
era5
.O para mostrar un ejemplo al menos de alguna manera útil:
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 esfunction(){}()
. 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í:
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.
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.
fuente
Hay dos casos de uso principales para los cierres:
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.
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.
fuente
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 terriblerand()
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 simplerand()
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.
fuente
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:
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.
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):
Aquí, tomamos un designador de función
function
y devolvemos dos cierres, ambos capturando una variable local llamadaactive
:function
, solo cuandoactive
es ciertoaction
ennil
, akafalse
.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:
Tenga en cuenta que los cierres de desactivación también podrían darse a otras funciones también; aquí, las
active
variables locales no se comparten entref
yg
; además, además deactive
,f
solo se refiereobj1
yg
solo se refiere aobj2
.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).
fuente
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 manipularloList<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.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:
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á elmessage
que se pasó originalmentedelayedLog()
, porque todavía está disponible para elfire()
cierre. Puede llamardelayedLog()
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 quedelayedLog()
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
message
variable en algún otro ámbito al que se pueda acceder despuésdelayedLog()
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
message
variable cuando la necesita. Y debido a eso, puede salirse con la suya escribiendodelayedLog()
arriba.fuente
message
está incluido en el alcance de la funciónfire
y, por lo tanto, se menciona en otras llamadas; pero lo hace accidentalmente, por así decirlo. Técnicamente es un cierre. +1 de todos modos;)makeadder
ejemplo 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 pasosetTimeout
.message
enfire
genera el cierre. Y cuando sesetTimeout
invoca, utiliza el estado preservado.PHP puede usarse para ayudar a mostrar un ejemplo real en un lenguaje diferente.
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.
fuente
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.
fuente
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.
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 ...
fuente