stringtranslate.com

Cierre (programación informática)

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 ámbito léxico en un lenguaje con funciones de primera clase . Operacionalmente , un cierre es un registro que almacena una función [a] junto con un entorno. [1] 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 referencia al que estaba vinculado el nombre cuando se creó el cierre. [b] 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 . [2]

Peter Landin definió el término cierre en 1964 como una parte de entorno y una parte de control utilizadas por su máquina SECD para evaluar expresiones. [3] Joel Moses le da crédito a Landin por haber introducido el término cierre para referirse a una expresión lambda con enlaces abiertos (variables libres) que han sido cerrados (o vinculados) por el entorno léxico, lo que da como resultado una expresión cerrada o cierre. [4] [5] Este uso fue adoptado posteriormente por Sussman y Steele cuando definieron Scheme en 1975, [6] una variante de Lisp con alcance léxico , y se generalizó.

Sussman y Abelson también usaron 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 también estructuras de datos anidadas. Este uso del término proviene del uso de las matemáticas , más que del uso anterior en informática. Los autores consideran que esta superposición de terminología es "desafortunada". [7]

Funciones anónimas

El término cierre se utiliza 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 han sido vinculadas a valores o a ubicaciones de almacenamiento (dependiendo del idioma; consulte la sección sobre entorno léxico a continuación).

Por ejemplo, en el siguiente código Python :

def  f ( x ):  def  g ( y ):  return  x  +  y  return  g  # Devuelve un cierre.def  h ( x ):  return  lambda  y :  x  +  y  # Devuelve un cierre.# Asignar cierres específicos a variables. a  =  f ( 1 ) b  =  h ( 1 )# Usando los cierres almacenados en variables. afirmar  a ( 5 )  ==  6 afirmar  b ( 5 )  ==  6# Usar cierres sin vincularlos primero a variables. afirmar  f ( 1 )( 5 )  ==  6  # f(1) es el cierre. afirmar  h ( 1 )( 5 )  ==  6  # h(1) es el cierre.

los valores de ay bson cierres, en ambos casos se producen al devolver una función anidada con una variable libre de la función adjunta, de modo que la variable libre se une al valor del parámetro xde la función adjunta. Los cierres en ay bson funcionalmente idénticos. La única diferencia en la implementación es que en el primer caso usamos una función anidada con un nombre, gmientras que en el segundo caso usamos una función anidada anónima (usando la palabra clave Python lambdapara crear una función anónima). El nombre original, si lo hubiera, utilizado 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 utilizar 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 cierres en sí mismas: tienen una variable libre que aún no está vinculada. Solo una vez que la función adjunta se evalúa con un valor para el parámetro, se vincula la variable libre de la función anidada, creando un cierre, que luego se devuelve desde la función adjunta.

Por último, un cierre solo se distingue 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 el dinámico no se pueden distinguir porque los nombres se resuelven con los mismos valores). Por ejemplo, en el siguiente programa, las funciones con una variable libre x(vinculada a la variable no local xcon alcance global) se ejecutan en el mismo entorno donde xestán definidas, por lo que es irrelevante si en realidad se trata de cierres:

x  =  1 números  =  [ 1 ,  2 ,  3 ]def  f ( y ):  devolver  x  +  ymapa ( f ,  nums ) mapa ( lambda  y :  x  +  y ,  nums )

Esto generalmente 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 normalmente su propio alcance será menor.

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, fse puede ver que es un cierre porque xel cuerpo de festá vinculado al xespacio de nombres global, no al xlocal g:

x  =  0def  f ( y ):  devolver  x  +  ydef  g ( z ):  x  =  1  # local x sombras global x  retorno  f ( z )g ( 1 )  # se evalúa como 1, no como 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 , y muchos lenguajes modernos de múltiples paradigmas, como Julia , Python y Rust . Los cierres también se usan a menudo con devoluciones de llamada , particularmente 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 continuo para ocultar el estado . Por tanto, construcciones como objetos y estructuras de control se pueden implementar con cierres. En algunos idiomas, puede ocurrir un cierre cuando una función se define dentro de otra función y la función interna se refiere a variables locales de la función externa. En tiempo de ejecución , cuando se ejecuta la función externa, se forma un cierre, que consta del código de la función interna y 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 tipos más simples como cadenas y números enteros. Por ejemplo, considere la siguiente función de esquema :

; Devuelve una lista de todos los libros con al menos UMBRAL de copias vendidas. ( definir ( umbral de libros más vendidos ) ( filtrar ( lambda ( libro ) ( >= ( libro-ventas de libros ) umbral )) lista de libros ))          

En este ejemplo, la expresión lambda (lambda (book) (>= (book-sales book) threshold)) aparece dentro de la función best-selling-books. Cuando se evalúa la expresión lambda, Scheme crea un cierre que consta del código de la expresión lambda y una referencia a la thresholdvariable, que es una variable libre dentro de la expresión lambda.

Luego, el cierre se pasa a la filterfunción, que lo 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 tiene una referencia a threshold, puede usar esa variable cada vez que filterla llama. La función filterpodría definirse en un archivo separado.

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

// Devuelve una lista de todos los libros con al menos "umbral" de copias vendidas. función libros más vendidos ( umbral ) { retorno lista de libros . filtrar ( libro => libro . ventas >= umbral ); }        

El operador de flecha =>se utiliza para definir una expresión de función de flecha y un Array.filtermétodo [8] en lugar de una función global filter, 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 este ejemplo:

// Devuelve una función que se aproxima a la derivada de f // usando un intervalo de dx, que debe ser apropiadamente pequeño. derivada de función ( f , dx ) { return x => ( 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 fy dxsiguen vivas después de que la función derivativeregresa, aunque la ejecución haya abandonado su alcance y ya no sean visibles. En lenguajes sin cierres, la vida útil de una variable local automática coincide con la ejecución del marco de pila donde se declara esa variable. En lenguajes con cierres, las variables deben seguir existiendo mientras los cierres existentes tengan referencias a ellas. Esto se implementa más comúnmente mediante algún tipo de recolección de basura .

Representación estatal

Se puede utilizar un cierre para asociar una función con un conjunto de variables " privadas ", que persisten durante varias invocaciones de la función. El alcance de la variable abarca solo la función cerrada, por lo que no se puede acceder a ella desde otro código de programa. Son análogas a las variables privadas en la programación orientada a objetos y, de hecho, los cierres son similares a los objetos de función con estado (o functores) con un único método de operador de llamada.

En lenguajes con estado, los cierres se pueden utilizar para implementar paradigmas para la representación del estado y el ocultamiento de información , ya que los valores superiores del cierre (sus variables cerradas) son de extensión indefinida , por lo que un valor establecido en una invocación permanece disponible en la siguiente. Los cierres utilizados 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:

( definir foo #f ) ( definir barra #f )    ( let (( mensaje secreto "ninguno" )) ( set! foo ( lambda ( msg ) ( set! mensaje secreto msg ))) ( set! bar ( lambda () mensaje secreto )))              ( pantalla ( barra )) ; imprime "ninguno" ( nueva línea ) ( foo "nos vemos en los muelles a medianoche" ) ( pantalla ( barra )) ; imprime "nos vemos en los muelles a medianoche"     

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

Implementación y teoría.

Los cierres generalmente 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, extendiendo además su vida útil al menos tanto como la vida útil del cierre. Cuando el cierre se ingresa más adelante, posiblemente con un entorno léxico diferente, la función se ejecuta con sus variables no locales refiriéndose a las capturadas por el cierre, no al entorno actual.

Una implementación de lenguaje no puede soportar 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 tales 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 sean necesarias, generalmente mediante 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 ellas ya no estén en uso.

Esto explica por qué, normalmente, los lenguajes que admiten cierres de forma nativa también utilizan la recolección de basura . Las alternativas son la administración manual de memoria de variables no locales (asignar explícitamente en el montón y liberar cuando se hace) o, si se usa la asignación de pila, que el lenguaje acepte que ciertos casos de uso conducirán a un comportamiento indefinido , debido a punteros colgantes a variables automáticas liberadas, como en expresiones lambda en C++ 11 [10] o funciones anidadas en GNU C. [11] 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 D versión 1, 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 alcance de definición (las variables locales automáticas están en la pila); esto aún permite muchas funciones útiles. patrones funcionales, pero para casos complejos se necesita una asignación de montón explícita para las variables. La versión 2 de D resolvió esto detectando qué variables deben almacenarse en el montón y realiza una asignación automática. Debido a que D usa 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 ( por ejemplo, Erlang ), es muy fácil implementar la gestión automática de la memoria (recolección de basura), ya que no hay ciclos posibles en las referencias de las variables. 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 regresa una función, 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 sea 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 de forma natural en Scheme. La forma lambda encierra el código y las variables libres de su entorno persisten dentro del programa siempre que sea posible acceder a ellas, por lo que pueden usarse tan libremente como cualquier otra expresión de Scheme. [ cita necesaria ]

Los cierres están estrechamente relacionados con los actores en el modelo Actor de cálculo 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 ofrecen una solución. [12]

Los cierres están estrechamente relacionados con los objetos funcionales ; la transformación del primero al segundo se conoce como desfuncionalización o levantamiento lambda ; ver también conversión de cierre . [ cita necesaria ]

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 aceptada del entorno léxico lo define como un conjunto de todas las vinculaciones de variables en el alcance, y eso es también lo que deben capturar los cierres en cualquier idioma. Sin embargo, el significado de un enlace variable también difiere. En los lenguajes imperativos, las variables se vinculan 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 del enlace sí puede cambiar. En dichos lenguajes, dado que el cierre captura el enlace, cualquier operación en la variable, ya sea realizada desde el cierre o no, se realiza en la misma ubicación de memoria relativa. A esto se le suele llamar capturar la variable "por referencia". A continuación se muestra un ejemplo que ilustra el concepto en ECMAScript , que es uno de esos lenguajes:

// Javascript var f , g ; función foo () { var x ; f = función () { retorno ++ x ; }; g = función () { retorno - x ; }; x = 1 ; alerta ( 'dentro de foo, llamada a f(): ' + f ()); } foo (); // 2 alerta ( 'llamar a g(): ' + g ()); // 1 (--x) alerta ( 'llamar a g(): ' + g ()); // 0 (--x) alerta ( 'llamar a f(): ' + f ()); // 1 (++x) alerta ( 'llamar a f(): ' + f ()); // 2 (++x)                                       

La función fooy los cierres a los que hacen referencia las variables futilizan gla 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. Nuevamente en ECMAScript, esto se haría usando el archivo Function.bind().

Ejemplo 1: referencia a una variable independiente

[13]

var módulo = { x : 42 , getX : función ( ) { devolver esto . X ; } } var unboundGetX = módulo . obtenerX ; consola . iniciar sesión ( unboundGetX ()); // La función se invoca en el ámbito global // emite undefinido ya que 'x' no está especificada en el ámbito global.              var enlazadoGetX = independienteGetX . enlazar ( módulo ); // especifica el módulo de objeto como consola de cierre . iniciar sesión ( boundGetX ()); // emite 42     

Ejemplo 2: referencia accidental a una variable vinculada

Para este ejemplo, el comportamiento esperado sería que cada enlace emitiera su identificación al hacer clic; pero debido a que la variable 'e' está vinculada al alcance anterior y se evalúa de forma diferida al hacer clic, lo que realmente sucede es que cada evento al hacer clic emite la identificación del último elemento en 'elementos' vinculado al final del bucle for. [14]

elementos var = documento . getElementsByTagName ( 'a' ); // Incorrecto: e está vinculado a la función que contiene el bucle 'for', no al cierre de "handle" for ( var e of elements ) { e . onclick = identificador de función () { alerta ( e . id ); } }                  

Nuevamente aquí la variable edebería estar vinculada al alcance del bloque usando handle.bind(this)o la letpalabra clave.

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 vinculada, no hay necesidad de compartir el estado entre cierres; simplemente usan los mismos valores. A esto se le suele llamar 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 lenguajes permiten elegir entre capturar el valor de una variable o su ubicación. Por ejemplo, en C++11, las variables capturadas se declaran con [&], que significa capturadas por referencia, o con [=], 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:

-- Haskell foo :: Fraccional a => a -> a -> ( a -> a ) foo x y = ( \ z -> z + r ) donde r = x / y                         f :: Fraccional a => a -> a f = foo 1 0           principal = imprimir ( f 123 )    

La vinculación de lo rcapturado por el cierre definido dentro de la función fooes con 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 luego se intenta utilizar el enlace capturado.

Cierre saliendo

returnSin embargo, se manifiestan más diferencias en el comportamiento de otras construcciones de alcance léxico , como breaky continuedeclaraciones. En general, dichas construcciones pueden considerarse en términos de invocar una continuación de escape establecida por una declaración de control adjunta (en el caso de breaky continue, dicha interpretación requiere que las construcciones en bucle se consideren en términos de llamadas a funciones recursivas). En algunos lenguajes, como ECMAScript, returnse refiere a la continuación establecida por el cierre léxicamente más interno con respecto a la declaración; por lo tanto, un cierre returndentro 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 particular sólo se puede invocar en Smalltalk implícitamente al llegar al final del código del cierre. Estos ejemplos en ECMAScript y Smalltalk resaltan la diferencia:

"Smalltalk" foo  | xs |  xs  :=  #( 1  2  3  4 ) .  xs  hacer: [ : x  |  ^ x ] .  ^ 0 barra Mostrar  transcripción  : ( self  foo  printString ) "imprime 1"
// función ECMAScript foo () { var xs = [ 1 , 2 , 3 , 4 ]; xs . para cada ( función ( x ) { retorno x ; }); devolver 0 ; } alerta ( foo ()); // imprime 0                  

Los fragmentos de código anteriores se comportarán de manera diferente porque el ^operador de Smalltalk y el returnoperador de JavaScript no son análogos. En el ejemplo de ECMAScript, return xabandonará el cierre interno para comenzar una nueva iteración del forEachbucle, mientras que en el ejemplo de Smalltalk, ^xabortará el bucle y regresará del 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 hasta el punto en que pueda invocarse con éxito. Considerar:

"Smalltalk" foo  ^ [ : x  |  ^ x ] barra  |  f  |  f  :=  yo  foo .  Valor f :  123 "¡error!"  

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

Algunos lenguajes, como Ruby , permiten al programador elegir la forma en que returnse captura. Un ejemplo en Rubí:

# rubí# Cierre usando un Proc def foo f = Proc . new { return "regreso de foo desde dentro de proc" } f . call # control deja foo aquí return "regresar de foo" fin            # Cierre usando una lambda def bar f = lambda { return "return from lambda" } f . call # control no sale de la barra aquí return "regresar desde la barra" fin            pone foo # imprime "regreso de foo desde dentro del proceso" pone bar # imprime "regreso de la barra"    

Ambos Proc.newy lambdaen este ejemplo son formas de crear un cierre, pero la semántica de los cierres así creados es diferente con respecto a la returndeclaración.

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

; Esquema ( definir llamada/cc llamada-con-continuación-actual )  ( define ( foo ) ( call/cc ( lambda ( return ) ( define ( f ) ( return "regreso de foo desde dentro del proceso " )) ( f ) ; el control deja foo aquí ( return "regreso de foo " ))))            ( define ( bar ) ( call/cc ( lambda ( return ) ( define ( f ) ( call/cc ( lambda ( return ) ( return "regreso de lambda " )))) ( f ) ; el control no sale de la barra aquí ( volver "regresar de la barra" ))))               ( mostrar ( foo )) ; imprime "regreso de foo desde dentro de proc" ( nueva línea ) ( pantalla ( barra )) ; imprime "regreso del bar"    

Construcciones tipo cierre

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

Devoluciones de llamada (C)

Algunas bibliotecas C admiten devoluciones de llamadas . A veces, esto se implementa proporcionando dos valores al registrar la devolución de llamada en la biblioteca: un puntero de función y un void*puntero separado a datos arbitrarios de elección del usuario. Cuando la biblioteca ejecuta la función de devolución de llamada, pasa el puntero de datos. Esto permite que la devolución de llamada mantenga el estado y haga referencia a la información capturada en el momento en que se registró en la biblioteca. El modismo es similar a los cierres en funcionalidad, pero no en sintaxis. El void*puntero no es seguro para tipos , por lo que este modismo de C difiere de los cierres seguros para tipos en C#, Haskell o ML.

Las devoluciones de llamada se utilizan ampliamente en los kits de herramientas de widgets de interfaz gráfica de usuario (GUI) para implementar programación basada en eventos al asociar funciones generales de widgets gráficos (menús, botones, casillas de verificación, controles deslizantes, controles giratorios, etc.) con funciones específicas de la aplicación que implementan el objetivo específico deseado. comportamiento para la aplicación.

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

Con una extensión GNU Compiler Collection (GCC), se puede usar una función anidada [15] y un puntero de función puede emular cierres, siempre que la función no salga del alcance que la contiene. El siguiente ejemplo no es válido porque adderes 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):

#incluir <stdio.h> typedef int ( * fn_int_to_int ) ( int ); // tipo de función int->int   fn_int_to_int sumador ( int número ) { int agregar ( int valor ) { valor de retorno + número ; } devolver y agregar ; // El operador & es opcional aquí porque el nombre de una función en C es un puntero que apunta a sí mismo }                int main ( void ) { fn_int_to_int add10 = sumador ( 10 ); printf ( "%d \n " , add10 ( 1 )); devolver 0 ; }          

Pero mover adder(y, opcionalmente, el typedef) adentro mainlo hace válido:

#incluir <stdio.h> int principal ( vacío ) { typedef int ( * fn_int_to_int ) ( int ); // tipo de función int->int fn_int_to_int sumador ( int número ) { int agregar ( int valor ) { valor de retorno + número ; } devolver agregar ; } fn_int_to_int add10 = sumador ( 10 ); printf ( "%d \n " , add10 ( 1 )); devolver 0 ; }                                 

Si se ejecuta, ahora se imprime 11como se esperaba.

Clases locales y funciones lambda (Java)

Java permite definir clases dentro de métodos . Éstas se denominan clases locales . Cuando dichas clases no tienen nombre, se las conoce 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 que incluyen léxicamente o a variables de solo lectura (marcadas como final) en el método que incluye léxico.

class  CalculationWindow extiende JFrame { resultado int volátil privado ; // ... public void calculaInSeparateThread ( final URI uri ) { // La expresión "new Runnable() { ... }" es una clase anónima que implementa la interfaz 'Runnable'. new Thread ( new Runnable () { void run () { // Puede leer variables locales finales: calcular ( uri ); // Puede acceder a campos privados de la clase adjunta: resultado = resultado + 10 ; } } ). comenzar (); } }                                   

La captura de finalvariables permite capturar variables por valor. Incluso si la variable a capturar no es final, siempre se puede copiar a una finalvariable temporal justo antes de la clase.

La captura de variables por referencia se puede emular utilizando una finalreferencia a un contenedor mutable, por ejemplo, una matriz de un elemento. La clase local no podrá cambiar el valor de la referencia del contenedor, pero podrá cambiar el contenido del contenedor.

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

class  CalculationWindow extiende JFrame { resultado int volátil privado ; // ... public void calculaInSeparateThread ( URI final uri ) { // El código () -> { /* código */ } es un cierre. nuevo hilo (() -> { calcular ( uri ); resultado = resultado + 10 ; }). comenzar (); } }                           

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 adjunta. [17] Normalmente se les denomina simplemente "clases internas". [18] Estos se definen en el cuerpo de la clase adjunta y tienen acceso completo a las variables de instancia de la clase adjunta. Debido a su vinculación a estas variables de instancia, solo se puede crear una instancia de una clase interna con una vinculación explícita a una instancia de la clase adjunta utilizando una sintaxis especial. [19]

clase pública EnclosingClass { /* Definir la clase interna */ clase pública InnerClass { public int incrementAndReturnCounter () { contador de retorno ++ ; } }                 contador de int privado ; { contador = 0 ; }        public int getCounter () { contador de retorno ; }       public static void main ( String [] args ) { EnclosingClass enclosingClassInstance = new EnclosingClass (); /* Crear una instancia de la clase interna, con enlace a la instancia */ EnclosingClass . InnerClass internalClassInstance = instancia de clase adjunta . nueva clase interna ();                 for ( int i = enclosingClassInstance . getCounter (); ( i = internalClassInstance . incrementAndReturnCounter ()) < 10 ; /* paso de incremento omitido */ ) { System . afuera . imprimirln ( yo ); } } }              

Tras la ejecución, 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 el uso acompañado del modificador "estático"; esos no tienen el efecto deseado, sino que son solo clases sin ningún enlace especial definido en una clase adjunta.

As of Java 8, Java supports functions as first class objects. Lambda expressions of this form are considered of type Function<T,U> with T being the domain and U the image type. The expression can be called with its .apply(T t) method, but not with a standard method call.

public static void main(String[] args) { Function<String, Integer> length = s -> s.length(); System.out.println( length.apply("Hello, world!") ); // Will print 13.}

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

Apple introduced blocks, a form of closure, as a nonstandard extension into C, C++, Objective-C 2.0 and in Mac OS X 10.6 "Snow Leopard" and iOS 4.0. Apple made their implementation available for the GCC and clang compilers.

Pointers to block and block literals are marked with ^. Normal local variables are captured by value when the block is created, and are read-only inside the block. Variables to be captured by reference are marked with __block. Blocks that need to persist outside of the scope they are created in may need to be copied.[20][21]

typedef int (^IntBlock)();IntBlock downCounter(int start) { __block int i = start; return [[ ^int() { return i--; } copy] autorelease];}IntBlock f = downCounter(5);NSLog(@"%d", f());NSLog(@"%d", f());NSLog(@"%d", f());

Delegates (C#, VB.NET, D)

C# anonymous methods and lambda expressions support closure:

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

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

Datos tenues = { 1 , 2 , 3 , 4 } Multiplicador tenue = 2 Resultado tenue = datos . Seleccione ( Función ( x ) x * multiplicador )               

En D , los cierres son implementados por delegados, 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 cierres).

prueba automática1 () { int a = 7 ; devolver delegado () { devolver a + 3 ; }; // construcción de delegado anónimo }               prueba automática2 () { int a = 20 ; int foo () { retorna a + 5 ; } // función interna return & foo ; // otra forma de construir delegado }                  barra vacía () { auto dg = prueba1 (); dg (); // =10 // ok, test1.a está en cierre y todavía existe         dg = prueba2 (); dg (); // =25 // ok, test2.a está en 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 usarla (lo más probable es que llamar a foo mediante dg() devolverá un ' 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 y cuando solo se usen mientras los valores referenciados aún sean válidos (por ejemplo, llamar a otra función con un cierre como parámetro de devolución de llamada) y sean útiles para escribir código genérico de procesamiento de datos, por lo que esta limitación En la práctica, no suele ser un problema.

Esta limitación se solucionó en D versión 2: 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 (mediante 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 del alcance actual, permanecen en la pila, lo cual es más simple y rápido que la asignación del montón. Lo mismo ocurre con los métodos de clase internos que hacen referencia a las variables de una función.

Objetos de función (C++)

C++ permite definir objetos de función mediante sobrecarga operator(). Estos objetos se comportan de manera similar a funciones en un lenguaje de programación funcional. Pueden crearse en tiempo de ejecución y pueden contener estado, pero no capturan implícitamente 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 referenciado, invocarlo operator()provoca un comportamiento indefinido ya que los cierres de C++ no extienden la vida útil de su contexto.

void foo ( cadena minombre ) { int y ; vector <cadena> n ;// ... auto i = std :: find_if ( n . comenzar (), n . terminar (), // esta es la expresión lambda: [ & ]( const string & s ) { return s != myname && s .tamaño () > y ; } ) ; // 'i' ahora es 'n.end()' o apunta a la primera cadena en 'n' // que no es igual a 'minombre' y cuya longitud es mayor que 'y' }                              

Agentes en línea (Eiffel)

Eiffel incluye agentes en línea que definen cierres. Un agente en línea es un objeto que representa una rutina, definida proporcionando el código de la rutina en línea. Por ejemplo, en

botón_ok . evento_clic . suscribirse ( agente ( x , y : INTEGER ) hacer mapa . país_en_coordenadas ( x , y ). mostrar fin )       

el argumento to subscribees 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_eventpara un determinado botón, de modo que siempre 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á pasando las coordenadas del mouse como argumentos a favor xy 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 alcance adjunto. Esta decisión de diseño ayuda a evitar ambigüedades cuando se habla del valor de una variable local en un cierre: ¿debería ser el último valor de la variable o el valor capturado cuando se crea el agente? Sólo Current(una referencia al objeto actual, análogo a thisen Java), se puede acceder a sus características y argumentos del agente desde el cuerpo del agente. Los valores de las variables locales externas se pueden pasar proporcionando operandos cerrados adicionales al agente.

C++Builder __palabra reservada de cierre

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. [22]

El estándar C permite escribir una definición de tipo para un puntero a un tipo de función utilizando la siguiente sintaxis:

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

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

typedef void ( __closure * TMyMethodPointer )();   

Ver también

Notas

  1. ^ La función se puede almacenar como referencia a una función, como un puntero de función .
  2. ^ Estos nombres suelen referirse a valores, variables mutables o funciones, pero también pueden ser otras entidades como constantes, tipos, clases o etiquetas.

Referencias

  1. ^ Sussman y Steele. "Esquema: un intérprete para el cálculo lambda extendido". "... una estructura de datos que contiene una expresión lambda y un entorno que se utilizará cuando esa expresión lambda se aplique a los argumentos". (Wikisource)
  2. ^ Turner, David A. (2012). "Un poco de historia de los lenguajes de programación funcionales" (PDF) . Simposio Internacional sobre Tendencias en Programación Funcional . Apuntes de conferencias sobre informática. vol. 7829. Saltador. págs. 1-20 Véase 12 §2, nota 8 para la afirmación sobre expresiones M. doi :10.1007/978-3-642-40447-4_1. ISBN 978-3-642-40447-4.
  3. ^ Landin, PJ (enero de 1964). «La evaluación mecánica de las expresiones» (PDF) . La revista informática . 6 (4): 308–320. doi : 10.1093/comjnl/6.4.308.
  4. ^ Moisés, Joel (junio de 1970). "La función de FUNCIÓN en LISP, o por qué el problema FUNARG debería llamarse problema ambiental". Boletín ACM Sigsam (15): 13–27. doi :10.1145/1093410.1093411. hdl :1721.1/5854. S2CID  17514262. AI Memo 199. Una metáfora útil para la diferencia entre FUNCTION y QUOTE en LISP es pensar en QUOTE como una cubierta porosa o abierta de la función, ya que las variables libres escapan al entorno actual. FUNCIÓN actúa como un revestimiento cerrado o no poroso (de ahí el término "cierre" utilizado por Landin). Por tanto, hablamos de expresiones Lambda "abiertas" (las funciones en LISP suelen ser expresiones Lambda) y expresiones Lambda "cerradas". [...] Mi interés por el problema medioambiental comenzó mientras Landin, que tenía un profundo conocimiento del problema, visitó el MIT durante 1966-1967. Luego me di cuenta de la correspondencia entre las listas FUNARG que son los resultados de la evaluación de expresiones Lambda "cerradas" en LISP y los cierres Lambda de ISWIM .
  5. ^ Wikström, Åke (1987). Programación funcional utilizando ML estándar . Prentice Hall. ISBN 0-13-331968-7. La razón por la que se llama "cierre" es que una expresión que contiene variables libres se llama expresión "abierta", y al asociarle los enlaces de sus variables libres, se cierra.
  6. ^ Sussman, Gerald Jay ; Steele, Guy L. Jr. (diciembre de 1975). Esquema: un intérprete para el cálculo Lambda extendido (Reporte). Memorando AI 349.
  7. ^ Abelson, Harold ; Sussman, Gerald Jay ; Sussman, Julie (1996). Estructura e Interpretación de Programas Informáticos. Prensa del MIT. págs. 98–99. ISBN 0-262-51087-1.
  8. ^ "filtro.matriz". Centro de desarrolladores de Mozilla . 10 de enero de 2010 . Consultado el 9 de febrero de 2010 .
  9. ^ "Re: FP, OO y relaciones. ¿Alguien supera a los demás?". 29 de diciembre de 1999. Archivado desde el original el 26 de diciembre de 2008 . Consultado el 23 de diciembre de 2008 .
  10. ^ Comité de Estándares C++ de Cierres y Expresiones Lambda . 29 de febrero de 2008.
  11. ^ "6.4 Funciones anidadas". Manual del CCG . Si intenta llamar a la función anidada a través de su dirección después de que sale la función que la contiene, se desata el infierno. Si intenta llamarlo después de que sale un nivel de alcance contenedor, y si se refiere a algunas de las variables que ya no están dentro del alcance, puede que tenga suerte, pero no es aconsejable correr el riesgo. Sin embargo, si la función anidada no hace referencia a nada que haya salido del alcance, deberías estar seguro.
  12. ^ Will Clinger: los fundamentos de la semántica del actor . Tesis doctoral en matemáticas del MIT. Junio ​​de 1981.
  13. ^ "Función.prototipo.bind()". Documentos web de MDN . Consultado el 20 de noviembre de 2018 .
  14. ^ "Cierres". Documentos web de MDN . Consultado el 20 de noviembre de 2018 .
  15. ^ "Funciones anidadas".
  16. ^ "Expresiones Lambda". Los tutoriales de Java .
  17. ^ "Clases anidadas, internas, de miembros y de nivel superior". Blog de Oracle de Joseph D. Darcy . Julio de 2007. Archivado desde el original el 31 de agosto de 2016.
  18. ^ "Ejemplo de clase interna". Los tutoriales de Java: aprendizaje del lenguaje Java: clases y objetos .
  19. ^ "Clases anidadas". Los tutoriales de Java: aprendizaje del lenguaje Java: clases y objetos .
  20. ^ "Temas de programación de bloques". Apple Inc. 8 de marzo de 2011 . Consultado el 8 de marzo de 2011 .
  21. ^ Bengtsson, Joachim (7 de julio de 2010). "Programación con bloques C en dispositivos Apple". Archivado desde el original el 25 de octubre de 2010 . Consultado el 18 de septiembre de 2010 .
  22. ^ La documentación completa se puede encontrar en http://docwiki.embarcadero.com/RADStudio/Rio/en/Closure

enlaces externos