Cierre (programación informática)

ImprimirCitar
Técnica para crear funciones de primera clase de alcance lexicamente

En los lenguajes de programación, un cierre, también cierre léxico o cierre de función, es una técnica para implementar la vinculación de nombres con alcance léxico en un lenguaje con primer funciones de clase. Operacionalmente, un cierre es un registro que almacena una función junto con un entorno. El entorno es un mapeo que asocia cada variable libre de la función (variables que se usan localmente, pero definidas en un ámbito adjunto) con el valor o la referencia a la que estaba vinculado el nombre cuando se creó el cierre. A diferencia de una función simple, un cierre permite que la función acceda a esas variables capturadas a través de las copias del cierre de sus valores o referencias, incluso cuando la función se invoca fuera de su alcance.

Historia y etimología

El concepto de cierres se desarrolló en la década de 1960 para la evaluación mecánica de expresiones en el cálculo λ y se implementó completamente por primera vez en 1970 como una característica del lenguaje de programación PAL para admitir funciones de primera clase con alcance léxico.

Peter J. Landin definió el término cierre en 1964 como tener una parte ambiental y una parte de control tal como lo usa su máquina SECD para evaluación de expresiones. Joel Moses le da crédito a Landin por introducir el término cierre para referirse a una expresión lambda cuyos enlaces abiertos (variables libres) han sido cerrados por (o enlazados en) el entorno léxico, lo que da como resultado una expresión cerrada , o cierre. Este uso fue adoptado posteriormente por Sussman y Steele cuando definieron Scheme en 1975, una variante de Lisp con alcance léxico, y se generalizó.

Sussman y Abelson también usan el término cierre en la década de 1980 con un segundo significado no relacionado: la propiedad de un operador que agrega datos a una estructura de datos para poder agregar estructuras de datos anidadas. Este uso del término proviene del uso matemático en lugar del uso anterior en informática. Los autores consideran que esta superposición de terminología es "desafortunada".

Funciones anónimas

El término cierre se usa a menudo como sinónimo de función anónima, aunque estrictamente, una función anónima es una función literal sin nombre, mientras que un cierre es una instancia de una función, un valor, cuyas variables no locales se han vinculado a valores o a ubicaciones de almacenamiento (según el idioma; consulte la sección de entorno léxico a continuación).

Por ejemplo, en el siguiente código de Python:

def f()x): def g()Sí.): retorno x + Sí. retorno g # Devuelve un cierre.def h()x): retorno lambda Sí.: x + Sí. # Devuelve un cierre.# Asignar cierres específicos a variables.a = f()1)b = h()1)# Utilizando los cierres almacenados en variables.afirmación a()5) == 6afirmación b()5) == 6# Utilizando cierres sin encuadernarlos a variables primero.afirmación f()1)5) == 6 # f(1) es el cierre.afirmación h()1)5) == 6 # h(1) es el cierre.

los valores de a y b son cierres, en ambos casos producidos al devolver una función anidada con una variable libre de la función envolvente, de modo que la variable libre se vincula al valor del parámetro x de la función envolvente. Los cierres en a y b son funcionalmente idénticos. La única diferencia en la implementación es que en el primer caso usamos una función anidada con un nombre, g, mientras que en el segundo caso usamos una función anidada anónima (usando la palabra clave de Python lambda para crear una función anónima). El nombre original, si alguno, usado para definirlos es irrelevante.

Un cierre es un valor como cualquier otro valor. No es necesario asignarlo a una variable y, en cambio, se puede usar directamente, como se muestra en las dos últimas líneas del ejemplo. Este uso puede considerarse un "cierre anónimo".

Las definiciones de funciones anidadas no son en sí mismas cierres: tienen una variable libre que aún no está vinculada. Solo una vez que la función envolvente se evalúa con un valor para el parámetro, la variable libre de la función anidada se vincula, creando un cierre, que luego se devuelve desde la función envolvente.

Por último, un cierre solo es distinto de una función con variables libres cuando está fuera del alcance de las variables no locales; de lo contrario, el entorno de definición y el entorno de ejecución coinciden y no hay nada que los distinga (el enlace estático y dinámico no pueden distinguirse porque los nombres se resuelven en los mismos valores). Por ejemplo, en el siguiente programa, las funciones con una variable libre x (ligada a la variable no local x con alcance global) se ejecutan en el mismo entorno donde x está definido, por lo que es irrelevante si estos son realmente cierres:

x = 1nums = [1, 2, 3]def f()Sí.): retorno x + Sí.mapa()f, nums)mapa()lambda Sí.: x + Sí., nums)

La mayoría de las veces, esto se logra mediante el retorno de una función, ya que la función debe definirse dentro del alcance de las variables no locales, en cuyo caso, por lo general, su propio alcance será más pequeño.

Esto también se puede lograr mediante el sombreado de variables (que reduce el alcance de la variable no local), aunque esto es menos común en la práctica, ya que es menos útil y se desaconseja el sombreado. En este ejemplo, se puede ver que f es un cierre porque x en el cuerpo de f está vinculado a x en el espacio de nombres global, no en el x local a g:

x = 0def f()Sí.): retorno x + Sí.def g()z): x = 1 # local x shadows global x retorno f()z)g()1) # Evalua a 1, no 2

Aplicaciones

El uso de cierres está asociado con lenguajes donde las funciones son objetos de primera clase, en los que las funciones pueden devolverse como resultados de funciones de orden superior, o pasarse como argumentos a otras llamadas a funciones; si las funciones con variables libres son de primera clase, devolver una crea un cierre. Esto incluye lenguajes de programación funcionales como Lisp y ML, así como muchos lenguajes modernos de múltiples paradigmas, como julia, pitón y Óxido. Los cierres también se usan con frecuencia con devoluciones de llamada, en particular para controladores de eventos, como en JavaScript, donde se usan para interacciones con una página web dinámica.

Los cierres también se pueden usar en un estilo de paso de continuación para ocultar el estado. Construcciones tales como objetos y estructuras de control pueden implementarse con cierres. En algunos lenguajes, un cierre puede ocurrir cuando una función se define dentro de otra función y la función interna se refiere a las variables locales de la función externa. En tiempo de ejecución, cuando se ejecuta la función externa, se forma un cierre, que consiste en el código de la función interna y las referencias (los valores superiores) a cualquier variable de la función externa requerida por el cierre.

Funciones de primera clase

Los cierres suelen aparecer en lenguajes con funciones de primera clase; en otras palabras, dichos lenguajes permiten que las funciones se pasen como argumentos, se devuelvan desde llamadas a funciones, se vinculen a nombres de variables, etc., al igual que los tipos más simples, como cadenas y números enteros.. Por ejemplo, considere la siguiente función Scheme:

; Devuelve una lista de todos los libros con al menos THRESHOLD copias vendidas.()definir ()best-selling-books umbral) ()filtro ()lambda ()libro) ()>= ()book-sales libro) umbral) lista de libros)

En este ejemplo, la expresión lambda (lambda (libro) (>= (umbral de ventas de libros)) aparece dentro de la función libros más vendidos. Cuando se evalúa la expresión lambda, Scheme crea un cierre que consiste en el código de la expresión lambda y una referencia a la variable threshold, que es una variable libre dentro de la expresión lambda.

El cierre se pasa luego a la función filter, que la llama repetidamente para determinar qué libros se agregarán a la lista de resultados y cuáles se descartarán. Debido a que el cierre en sí tiene una referencia a threshold, puede usar esa variable cada vez que filter lo llama. La función filter en sí podría estar definida en un archivo completamente separado.

Aquí está el mismo ejemplo reescrito en JavaScript, otro lenguaje popular con soporte para cierres:

// Devuelve una lista de todos los libros con al menos copias 'de confianza' vendidas.función bestSellingBooks()umbral) {} retorno libro Lista.filtro() función ()libro) {} retorno libro.ventas >= umbral; } );}

La palabra clave function se usa aquí en lugar de lambda, y un método Array.filter en lugar de un filter función, pero por lo demás la estructura y el efecto del código son los mismos.

Una función puede crear un cierre y devolverlo, como en el siguiente ejemplo:

// Devuelve una función que aproxima el derivado de f// utilizando un intervalo de dx, que debe ser apropiadamente pequeño.función derivados()f, dx) {} retorno función ()x) {} retorno ()f()x + dx) - f()x) / dx; };}

Debido a que el cierre en este caso sobrevive a la ejecución de la función que lo crea, las variables f y dx viven después de la función derivada devuelve, aunque la ejecución haya salido de su alcance y ya no sean visibles. En lenguajes sin cierres, el tiempo de vida de una variable local automática coincide con la ejecución del marco de pila donde se declara esa variable. En lenguajes con clausuras, las variables deben continuar existiendo siempre que las clausuras existentes tengan referencias a ellas. Esto se implementa más comúnmente usando alguna forma de recolección de basura.

Representación del Estado

Se puede usar un cierre para asociar una función con un conjunto de "privados" variables, que persisten a lo largo de varias invocaciones de la función. El alcance de la variable abarca solo la función cerrada, por lo que no se puede acceder desde otro código de programa. Estos son análogos a las variables privadas en la programación orientada a objetos y, de hecho, los cierres son análogos a un tipo de objeto, específicamente objetos de función, con un solo método público (llamada a la función) y posiblemente muchas variables privadas (las variables cerradas)..

En los lenguajes con estado, los cierres se pueden usar para implementar paradigmas para la representación del estado y la ocultación de información, ya que los valores superiores del cierre (sus variables cerradas) tienen un alcance indefinido, por lo que permanece un valor establecido en una invocación disponible en el siguiente. Las clausuras utilizadas de esta manera ya no tienen transparencia referencial y, por lo tanto, ya no son funciones puras; sin embargo, se usan comúnmente en lenguajes funcionales impuros como Scheme.

Otros usos

Los cierres tienen muchos usos:

  • Debido a que los cierres retrasan la evaluación, es decir, no "hacen" nada hasta que se llamen, pueden utilizarse para definir estructuras de control. Por ejemplo, todas las estructuras de control estándar de Smalltalk, incluyendo ramas (si/entonces/else) y bucles (mientras y para), se definen utilizando objetos cuyos métodos aceptan cierres. Los usuarios pueden definir fácilmente sus propias estructuras de control también.
  • En los idiomas que implementan la asignación, se pueden producir múltiples funciones que se acercan al mismo entorno, permitiéndoles comunicarse en privado alterando ese entorno. En el plan:
()definir Foo #f)()definir bar #f)()Deja ()secreto-mensaje "ninguno") ()¡Listo! Foo ()lambda ()msg) ()¡Listo! secreto-mensaje msg)) ()¡Listo! bar ()lambda () secreto-mensaje))()pantalla ()bar) ; impresiones "ninguno"()newline)()Foo "Nos vemos en los muelles a medianoche")()pantalla ()bar) ; impresiones "me recuerdan por los muelles a medianoche"
  • Los cierres se pueden utilizar para implementar sistemas de objetos.

Nota: algunos hablantes llaman clausura a cualquier estructura de datos que une un entorno léxico, pero el término generalmente se refiere específicamente a funciones.

Implementación y teoría

Los cierres normalmente se implementan con una estructura de datos especial que contiene un puntero al código de la función, además de una representación del entorno léxico de la función (es decir, el conjunto de variables disponibles) en el momento en que se creó el cierre.. El entorno de referencia vincula los nombres no locales a las variables correspondientes en el entorno léxico en el momento en que se crea el cierre, además de extender su vida útil al menos tanto como la vida útil del propio cierre. Cuando se ingresa el cierre en un momento posterior, posiblemente con un entorno léxico diferente, la función se ejecuta con sus variables no locales que se refieren a las capturadas por el cierre, no al entorno actual.

La implementación de un lenguaje no puede admitir fácilmente cierres completos si su modelo de memoria en tiempo de ejecución asigna todas las variables automáticas en una pila lineal. En dichos lenguajes, las variables locales automáticas de una función se desasignan cuando la función regresa. Sin embargo, un cierre requiere que las variables libres a las que hace referencia sobrevivan a la ejecución de la función envolvente. Por lo tanto, esas variables deben asignarse para que persistan hasta que ya no se necesiten, generalmente a través de la asignación de montón, en lugar de en la pila, y su vida útil debe administrarse para que sobrevivan hasta que todos los cierres que hacen referencia a ellos ya no estén en uso.

Esto explica por qué, por lo general, los lenguajes que admiten cierres de forma nativa también usan la recolección de elementos no utilizados. Las alternativas son la gestión manual de la memoria de las variables no locales (asignación explícita en el montón y liberación cuando se hace) o, si se usa la asignación de pila, para que el lenguaje acepte que ciertos casos de uso conducirán a un comportamiento indefinido, debido a los punteros colgantes a variables automáticas liberadas, como en expresiones lambda en C++11 o funciones anidadas en GNU C. El problema funarg (o problema de "argumento funcional") describe la dificultad de implementar funciones como objetos de primera clase en una pila. lenguaje de programación basado en C o C++. De manera similar, en la versión 1 de D, se supone que el programador sabe qué hacer con los delegados y las variables locales automáticas, ya que sus referencias no serán válidas después de regresar de su ámbito de definición (las variables locales automáticas están en la pila). patrones funcionales, pero para casos complejos necesita una asignación explícita de almacenamiento dinámico para las variables. La versión 2 de D resolvió esto al detectar qué variables deben almacenarse en el montón y realiza la asignación automática. Debido a que D utiliza la recolección de basura, en ambas versiones, no es necesario realizar un seguimiento del uso de las variables a medida que se pasan.

En lenguajes estrictamente funcionales con datos inmutables (e.g. Erlang), es muy fácil implementar la gestión automática de memoria (recolección de basura), ya que no hay ciclos posibles en las variables' referencias Por ejemplo, en Erlang, todos los argumentos y variables se asignan en el montón, pero las referencias a ellos se almacenan adicionalmente en la pila. Después de que una función regresa, las referencias siguen siendo válidas. La limpieza del montón se realiza mediante un recolector de basura incremental.

En ML, las variables locales tienen un alcance léxico y, por lo tanto, definen un modelo similar a una pila, pero dado que están vinculadas a valores y no a objetos, una implementación es libre de copiar estos valores en la estructura de datos del cierre. de una manera que es invisible para el programador.

Scheme, que tiene un sistema de alcance léxico similar a ALGOL con variables dinámicas y recolección de basura, carece de un modelo de programación de pila y no sufre las limitaciones de los lenguajes basados en pila. Los cierres se expresan naturalmente en Scheme. La forma lambda encierra el código, y las variables libres de su entorno persisten dentro del programa siempre que se pueda acceder a ellas, por lo que se pueden usar con la misma libertad que cualquier otra expresión de Scheme.

Los cierres están estrechamente relacionados con los actores en el modelo Actor de computación concurrente donde los valores en el entorno léxico de la función se denominan conocidos. Una cuestión importante para los cierres en lenguajes de programación concurrentes es si las variables de un cierre se pueden actualizar y, de ser así, cómo se pueden sincronizar estas actualizaciones. Los actores proporcionan una solución.

Los cierres están estrechamente relacionados con los objetos de función; la transformación de la primera a la segunda se conoce como desfuncionalización o levantamiento lambda; véase también conversión de cierre.

Diferencias en semántica

Entorno léxico

Como los diferentes idiomas no siempre tienen una definición común del entorno léxico, sus definiciones de cierre también pueden variar. La definición minimalista comúnmente sostenida del entorno léxico lo define como un conjunto de todos los enlaces de variables en el ámbito, y eso es también lo que los cierres en cualquier idioma deben capturar. Sin embargo, el significado de un enlace variable también difiere. En lenguajes imperativos, las variables se unen a ubicaciones relativas en la memoria que pueden almacenar valores. Aunque la ubicación relativa de un enlace no cambia en tiempo de ejecución, el valor en la ubicación enlazada sí puede. En dichos lenguajes, dado que el cierre captura el enlace, cualquier operación en la variable, ya sea que se realice desde el cierre o no, se realiza en la misma ubicación de memoria relativa. A esto a menudo se le llama capturar la variable "por referencia". Aquí hay un ejemplo que ilustra el concepto en ECMAScript, que es uno de esos lenguajes:

/ JavascriptVar f, g;función Foo() {} Var x; f = función() {} retorno ++x; }; g = función() {} retorno --x; }; x = 1; alerta()"inside foo, call to f(): ' + f());}Foo(); // 2alerta()'call to g(): ' + g()); // 1 (--x)alerta()'call to g(): ' + g()); // 0 (--x)alerta()'call to f(): ' + f()); // 1 (+x)alerta()'call to f(): ' + f()); // 2 (+x)

La función foo y los cierres a los que se refieren las variables f y g usan la misma ubicación de memoria relativa indicada por la variable local x.

En algunos casos, el comportamiento anterior puede ser indeseable y es necesario vincular un cierre léxico diferente. De nuevo en ECMAScript, esto se haría usando Function.bind().

Ejemplo 1: Referencia a una variable independiente

Var módulo = {} x: 42, getX: función() {}retorno esto.x; }}Var sin límites GetX = módulo.getX;consola.log()sin límites GetX()); // La función se invoca en el ámbito global// emite sin definir como 'x' no se especifica en el alcance global.Var boundGetX = sin límites GetX.Bind()módulo); // especificar módulo de objeto como cierreconsola.log()boundGetX()); // emite 42

Ejemplo 2: Referencia accidental a una variable vinculada

Para este ejemplo, el comportamiento esperado sería que cada enlace debería emitir su id cuando se hace clic; sino porque la variable 'e' está vinculado al alcance anterior y se evalúa de forma perezosa al hacer clic, lo que realmente sucede es que cada evento al hacer clic emite la identificación del último elemento en 'elements' enlazado al final del bucle for.

Var elementos= documento.getElementsByTagName()'a');//Incorrecto: e está vinculado a la función que contiene el bucle 'para', no el cierre de la "manija"para ()Var e de elementos){ e.clic=función mango(){ alerta()e.id);} }

Nuevamente aquí, la variable e tendría que estar limitada por el alcance del bloque usando handle.bind(this) o la palabra clave let.

Por otro lado, muchos lenguajes funcionales, como ML, vinculan variables directamente a valores. En este caso, dado que no hay forma de cambiar el valor de la variable una vez que está vinculada, no es necesario compartir el estado entre los cierres, solo usan los mismos valores. A esto a menudo se le llama capturar la variable "por valor". Las clases locales y anónimas de Java también entran en esta categoría: requieren que las variables locales capturadas sean final, lo que también significa que no es necesario compartir el estado.

Algunos idiomas le permiten elegir entre capturar el valor de una variable o su ubicación. Por ejemplo, en C++11, las variables capturadas se declaran con [&], lo que significa capturadas por referencia, o con [=], lo que significa capturadas por valor.

Otro subconjunto más, los lenguajes funcionales perezosos como Haskell, vinculan variables a resultados de cálculos futuros en lugar de valores. Considere este ejemplo en Haskell:

HaskellFoo :: Fraccional a = a - a - ()a - a)Foo x Sí. = ()z - z + r) Donde r = x / Sí.f :: Fraccional a = a - af = Foo 1 0principal = impresión ()f 123)

La vinculación de r capturada por el cierre definido dentro de la función foo es para el cálculo (x / y), que en este caso resulta en la división por cero. Sin embargo, dado que lo que se captura es el cálculo y no el valor, el error solo se manifiesta cuando se invoca el cierre y, de hecho, intenta utilizar el enlace capturado.

Salida de cierre

Aún se manifiestan más diferencias en el comportamiento de otras construcciones de ámbito léxico, como las declaraciones return, break y continue. Tales construcciones pueden, en general, considerarse en términos de invocar una continuación de escape establecida por una declaración de control envolvente (en el caso de break y continue, tal interpretación requiere construcciones en bucle para considerarse en términos de llamadas a funciones recursivas). En algunos lenguajes, como ECMAScript, return se refiere a la continuación establecida por el cierre léxicamente más interno con respecto a la declaración; por lo tanto, un return dentro de un cierre transfiere el control al código que lo llamó. Sin embargo, en Smalltalk, el operador superficialmente similar ^ invoca la continuación de escape establecida para la invocación del método, ignorando las continuaciones de escape de cualquier cierre anidado intermedio. La continuación de escape de un cierre en particular solo se puede invocar implícitamente en Smalltalk al llegar al final del código del cierre. Los siguientes ejemplos en ECMAScript y Smalltalk resaltan la diferencia:

"Smalltalk"Foo Silencio xs Silencio xs := #1 2 3 4). xs hacer: [:x Silencio ^x]. ^0bar Transcripción Mostrar: ()auto Foo printString) "impresión 1"
// ECMAScriptfunción Foo() {} Var xs = [1, 2, 3, 4]; xs.para cada uno()función ()x) {} retorno x; }); retorno 0;}alerta()Foo()); // impresiones 0

Los fragmentos de código anteriores se comportarán de manera diferente porque el operador ^ de Smalltalk y el operador return de JavaScript no son análogos. En el ejemplo de ECMAScript, return x dejará el cierre interno para comenzar una nueva iteración del bucle forEach, mientras que en el ejemplo de Smalltalk, ^x abortará el bucle y volverá desde el método foo.

Common Lisp proporciona una construcción que puede expresar cualquiera de las acciones anteriores: Lisp (return-from foo x) se comporta como Smalltalk ^x, mientras que Lisp (return-from nil x) se comporta como JavaScript return x. Por lo tanto, Smalltalk hace posible que una continuación de escape capturada sobreviva en la medida en que se puede invocar con éxito. Considerar:

"Smalltalk"Foo ^[ :x Silencio ^x ]
bar Silencio f Silencio f := auto Foo. f valor: 123 "¡error!"

Cuando se invoca el cierre devuelto por el método foo, intenta devolver un valor de la invocación de foo que creó el cierre. Dado que esa llamada ya ha regresado y el modelo de invocación del método Smalltalk no sigue la disciplina de la pila de espaguetis para facilitar múltiples retornos, esta operación genera un error.

Algunos lenguajes, como Ruby, permiten al programador elegir la forma en que se captura return. Un ejemplo en Ruby:

Ruby# Cierre usando un Procdef Foo f = Proc.nuevo {} retorno "retorno de foo desde dentro proc" } f.llamada # El control deja foo aquí retorno "retorno de foo"final# Closure using a lambdadef bar f = lambda {} retorno "retorno de lambda" } f.llamada El control no deja la barra aquí retorno "Regresa de la barra"finalputs Foo # prints "regresar de foo desde dentro proc"puts bar # prints "regresa de la barra"

Tanto Proc.new como lambda en este ejemplo son formas de crear un cierre, pero la semántica de los cierres así creados es diferente con respecto al return declaración .

En Scheme, la definición y el alcance de la instrucción de control return son explícitos (y solo se denominan arbitrariamente 'return' por el bien del ejemplo). La siguiente es una traducción directa de la muestra de Ruby.

; Plan()definir call/cc call-with-current-continuation)()definir ()Foo) ()call/cc ()lambda ()retorno) ()definir ()f) ()retorno "retorno de foo desde dentro proc") ()f) ; el control deja foo aquí ()retorno "retorno de foo")))()definir ()bar) ()call/cc ()lambda ()retorno) ()definir ()f) ()call/cc ()lambda ()retorno) ()retorno "retorno de lambda"))) ()f) ; el control no deja la barra aquí ()retorno "Regresa de la barra")))()pantalla ()Foo) ; impresiones "retorno de foo desde el interior proc"()newline)()pantalla ()bar) ; impresiones "retorno de la barra"

Construcciones similares a cierres

Algunos lenguajes tienen características que simulan el comportamiento de los cierres. En lenguajes como Java, C++, Objective-C, C#, VB.NET y D, estas características son el resultado del paradigma orientado a objetos del lenguaje.

Devoluciones de llamada (C)

Algunas bibliotecas C son compatibles devoluciones de llamada Este es a veces se implementa proporcionando dos valores cuando registrar la devolución de llamada con la biblioteca: una función puntero y un puntero void* separado para datos arbitrarios de la elección del usuario. Cuando la biblioteca ejecuta la función de devolución de llamada, pasa los datos puntero. Esto permite que la devolución de llamada mantenga el estado y para referirse a la información capturada en el momento en que fue registrado en la biblioteca. El idioma es similar a cierres en funcionalidad, pero no en sintaxis. Él El puntero void* no es seguro, por lo que este C idiom difiere de los cierres de tipo seguro en C#, Haskell o ML.

Las devoluciones de llamada se utilizan mucho en los kits de herramientas de GUI Widget para implementar la programación impulsada por eventos mediante la asociación de funciones de widgets gráficos (menús, botones, casillas de verificación, controles deslizantes, botones giratorios, etc.) con funciones específicas de la aplicación implementar el comportamiento deseado específico para la aplicación.

Función anidada y puntero de función (C)

Con una extensión gcc, se puede usar una función anidada y un puntero de función puede emular cierres, siempre que la función no salga del ámbito contenedor. El siguiente ejemplo no es válido porque adder es una definición de nivel superior (dependiendo de la versión del compilador, podría producir un resultado correcto si se compila sin optimización, es decir, en -O0):

#include Identificado.hTipodef int ()*fn_int_to_int)int); // tipo de función int-propintfn_int_to_int escalera()int Número) {} int añadir ()int valor) {} retorno valor + Número; } retorno "añadir; // " operador es opcional aquí porque el nombre de una función en C es un puntero apuntando en sí mismo}int principal()vacío) {} fn_int_to_int add10 = escalera()10); printf()"n", add10()1)); retorno 0;}

Pero mover adder (y, opcionalmente, typedef) en main lo hace válido:

#include Identificado.hint principal()vacío) {} Tipodef int ()*fn_int_to_int)int); // tipo de función int-propint  fn_int_to_int escalera()int Número) {} int añadir ()int valor) {} retorno valor + Número; } retorno añadir; }  fn_int_to_int add10 = escalera()10); printf()"n", add10()1)); retorno 0;}

Si se ejecuta, ahora imprime 11 como se esperaba.

Clases locales y funciones lambda (Java)

Java permite definir clases dentro de métodos. Estas se llaman clases locales. Cuando dichas clases no tienen nombre, se conocen como clases anónimas (o clases internas anónimas). Una clase local (ya sea con nombre o anónima) puede hacer referencia a nombres en clases de inclusión léxica, o variables de solo lectura (marcadas como final) en el método de inclusión léxica.

clase CalculationWindow extensiones JFrame {} privado volátil int resultado; //... público vacío calcularInSepararTread()final URI uri) {} // La expresión "nueva Runnable() {... }" es una clase anónima que implementa la interfaz 'Runnable'. nuevo Thread() nuevo Runnable() {} vacío Corre() {} // Puede leer las variables locales finales: calcular()uri); // Puede acceder a campos privados de la clase de cierre: resultado = resultado + 10; } } ).Empieza(); }}

La captura de variables final le permite capturar variables por valor. Incluso si la variable que desea capturar no es final, siempre puede copiarla en una variable temporal final justo antes de la clase.

La captura de variables por referencia se puede emular usando una referencia final a un contenedor mutable, por ejemplo, una matriz de un solo elemento. La clase local no podrá cambiar el valor de la referencia del contenedor en sí, pero podrá cambiar el contenido del contenedor.

Con la llegada de las expresiones lambda de Java 8, el cierre hace que el código anterior se ejecute como:

clase CalculationWindow extensiones JFrame {} privado volátil int resultado; //... público vacío calcularInSepararTread()final URI uri) {} // El código () - Propiedad { /* code */ } es un cierre. nuevo Thread(() - {} calcular()uri); resultado = resultado + 10; }).Empieza(); }}

Las clases locales son uno de los tipos de clases internas que se declaran dentro del cuerpo de un método. Java también admite clases internas que se declaran como miembros no estáticos de una clase envolvente. Normalmente se les conoce simplemente como "clases internas". Estos se definen en el cuerpo de la clase envolvente y tienen acceso total a las variables de instancia de la clase envolvente. Debido a su enlace con estas variables de instancia, una clase interna solo se puede instanciar con un enlace explícito a una instancia de la clase envolvente usando una sintaxis especial.

público clase EnclosingClass {} /* Definir la clase interna */ público clase InnerClass {} público int yReturnCounter() {} retorno contra++; } } privado int contra; {} contra = 0; } público int getCounter() {} retorno contra; } público estática vacío principal()String[] args) {} EnclosingClass enclosingClassInstance = nuevo EnclosingClass(); /* Instantánea la clase interna, con unión a la instancia */ EnclosingClass.InnerClass interior ClassInstance = enclosingClassInstance.nuevo InnerClass(); para ()int i = enclosingClassInstance.getCounter(); ()i = interior ClassInstance.yReturnCounter()) . 10; /* paso de aumento omitido */) {} Sistema.Fuera..println()i); } }}

Al ejecutarse, esto imprimirá los números enteros del 0 al 9. Tenga cuidado de no confundir este tipo de clase con la clase anidada, que se declara de la misma manera con un uso acompañado de la propiedad "static" modificador; esos no tienen el efecto deseado, sino que son solo clases sin un enlace especial definido en una clase adjunta.

A partir de Java 8, Java admite funciones como objetos de primera clase. Las expresiones lambda de esta forma se consideran de tipo Function<T,U>, siendo T el dominio y U el tipo de imagen. La expresión se puede llamar con su método .apply(T t), pero no con una llamada de método estándar.

público estática vacío principal()String[] args) {} Función.String, Integer longitud = s - s.longitud(); Sistema.Fuera..println() longitud.aplicación()"¡Hola, mundo!") ); // Impresión 13.}

Bloques (C, C++, Objective-C 2.0)

Apple introdujo bloques, una forma de cierre, como una extensión no estándar en C, C++, Objective-C 2.0 y en Mac OS X 10.6 "Snow Leopard" y iOS 4.0. Apple puso su implementación a disposición de los compiladores GCC y clang.

Los punteros a bloque y literales de bloque están marcados con ^. Las variables locales normales se capturan por valor cuando se crea el bloque y son de solo lectura dentro del bloque. Las variables a capturar por referencia están marcadas con __block. Es posible que sea necesario copiar los bloques que deben persistir fuera del ámbito en el que se crearon.

Tipodef int ()^IntBlock)();IntBlock downCounter()int Empieza) {} __Block int i = Empieza; retorno [[2] ^int() {} retorno i--; } Copia] autorelease];}IntBlock f = downCounter()5);NSLog()#, f());NSLog()#, f());NSLog()#, f());

Delegadas (C #, VB.NET, D)

(feminine)

Los métodos anónimos de C# y las expresiones lambda admiten el cierre:

Var datos = nuevo[] {}1, 2, 3, 4};Var multiplicador = 2;Var resultado = datos.Seleccione()x = x * multiplicador);

Visual Basic.NET, que tiene muchas funciones de lenguaje similares a las de C#, también admite expresiones lambda con cierres:

Dim datos = {}1, 2, 3, 4}Dim multiplicador = 2Dim resultado = datos.Seleccione()Función()x) x * multiplicador)

En D, los delegados implementan los cierres, un puntero de función emparejado con un puntero de contexto (por ejemplo, una instancia de clase o un marco de pila en el montón en el caso de los cierres).

auto test1() {} int a = 7; retorno delegado() {} retorno a + 3; }; // construcción anónima de delegados}auto test2() {} int a = 20; int Foo() {} retorno a + 5; } // función interna retorno "Foo; // de otra manera construir delegado}vacío bar() {} auto Dg = test1(); Dg(); // =10 // ok, test1.a está en un cierre y todavía existe Dg = test2(); Dg(); // =25 // ok, test2.a está en un cierre y todavía existe}

D versión 1, tiene soporte de cierre limitado. Por ejemplo, el código anterior no funcionará correctamente, porque la variable a está en la pila, y después de regresar de test(), ya no es válido usarlo (lo más probable es que llamar a foo a través de dg() devolverá un & #39;entero aleatorio'). Esto se puede resolver asignando explícitamente la variable 'a' en el montón, o usando estructuras o clases para almacenar todas las variables cerradas necesarias y construir un delegado a partir de un método que implemente el mismo código. Los cierres se pueden pasar a otras funciones, siempre que solo se usen mientras los valores a los que se hace referencia siguen siendo válidos (por ejemplo, llamar a otra función con un cierre como parámetro de devolución de llamada) y son útiles para escribir código de procesamiento de datos genérico, por lo que esta limitación, en la práctica, a menudo no es un problema.

Esta limitación se solucionó en la versión 2 de D: la variable 'a' se asignará automáticamente en el montón porque se usa en la función interna, y un delegado de esa función puede escapar del alcance actual (a través de la asignación a dg o retorno). Cualquier otra variable local (o argumento) a la que no hagan referencia los delegados o a la que solo hagan referencia los delegados que no escapen al ámbito actual, permanecerán en la pila, lo que es más simple y rápido que la asignación de almacenamiento dinámico. Lo mismo ocurre con los métodos de la clase interna que hacen referencia a las variables de una función.

Objetos de función (C++)

C++ permite definir objetos de función al sobrecargar operator(). Estos objetos se comportan como funciones en un lenguaje de programación funcional. Pueden crearse en tiempo de ejecución y pueden contener estado, pero no capturan implícitamente las variables locales como lo hacen los cierres. A partir de la revisión de 2011, el lenguaje C++ también admite cierres, que son un tipo de objeto de función construido automáticamente a partir de una construcción de lenguaje especial llamada expresión lambda. Un cierre de C++ puede capturar su contexto almacenando copias de las variables a las que se accede como miembros del objeto de cierre o por referencia. En el último caso, si el objeto de cierre escapa del alcance de un objeto al que se hace referencia, invocar su operator() provoca un comportamiento indefinido ya que los cierres de C++ no extienden la vida útil de su contexto.

vacío Foo()cuerda mi nombre) {} int Sí.; vector.cuerda n; //... auto i = std::Encontrar_if()n.comenzar(), n.final(), // esta es la expresión lambda: ["]const cuerda" s) {} retorno s ! mi nombre " s.tamaño()  Sí.; } ); // 'i' es ahora 'n.end()' o apunta a la primera cadena en 'n ' // que no es igual a 'mi nombre' y cuya longitud es mayor que 'y '}

Agentes en línea (Eiffel)

Eiffel incluye agentes en línea que definen los cierres. Un agente en línea es un objeto que representa una rutina, definido dando el código de la rutina en línea. por ejemplo, en

Ok_button.click_event.Suscríbete ()Agente ()x, Sí.: INTEGER) domapa.country_at_coordinates ()x, Sí.).pantallafinal)

el argumento para subscribe es un agente, que representa un procedimiento con dos argumentos; el procedimiento encuentra el país en las coordenadas correspondientes y lo muestra. Todo el agente está "suscrito" al tipo de evento click_event para un cierto botón, de modo que cada vez que ocurra una instancia del tipo de evento en ese botón, porque un usuario ha hecho clic en el botón, el procedimiento se ejecutará con las coordenadas del mouse que se pasan como argumentos para x y y.

La principal limitación de los agentes Eiffel, que los distingue de los cierres en otros idiomas, es que no pueden hacer referencia a variables locales desde el ámbito envolvente. Esta decisión de diseño ayuda a evitar la ambigüedad cuando se habla del valor de una variable local en un cierre: ¿debe ser el valor más reciente de la variable o el valor capturado cuando se crea el agente? Solo se puede acceder a Current (una referencia al objeto actual, análoga a this en Java), sus funciones y argumentos del propio agente desde el cuerpo del agente. Los valores de las variables locales externas se pueden pasar proporcionando operandos cerrados adicionales al agente.

C++Builder __cierre palabra reservada

Embarcadero C++Builder proporciona la palabra de reserva __closure para proporcionar un puntero a un método con una sintaxis similar a un puntero de función.

En C estándar, podría escribir un typedef para un puntero a un tipo de función usando la siguiente sintaxis:

Tipodef vacío ()*TMyFunctionPointer) vacío );

De manera similar, puede declarar un typedef para un puntero a un método usando la siguiente sintaxis:

Tipodef vacío ()__closure *TMyMethodPointer)();

Contenido relacionado

Protocolo de árbol de expansión

Control de enlace de datos de alto nivel

Computación de 8 bits

Más resultados...
Tamaño del texto:
Copiar