En programación informática , una función anidada (o procedimiento anidado o subrutina ) es una función con nombre que se define dentro de otro bloque que la encierra y que tiene un alcance léxico dentro del bloque que la encierra, lo que significa que solo se puede llamar por nombre dentro del cuerpo del bloque que la encierra y puede usar identificadores declarados en bloques externos , incluidas funciones externas. El bloque que la encierra es normalmente, pero no siempre, otra función.
La compatibilidad de los lenguajes de programación con funciones anidadas varía. Con respecto a los lenguajes de programación estructurados , se admite en algunos lenguajes obsoletos como ALGOL , Simula 67 y Pascal y en el comúnmente usado JavaScript . Se admite comúnmente en lenguajes dinámicos y funcionales . Sin embargo, no se admite en algunos lenguajes comúnmente usados, incluidos C y C++ estándar .
Otras tecnologías de programación ofrecen beneficios similares. Por ejemplo, una función lambda también permite definir una función dentro de otra función (así como en otros lugares) y permite ocultar y encapsular datos de manera similar. Cabe destacar que una función lambda no tiene nombre (es anónima) y, por lo tanto, no se la puede llamar por su nombre y no tiene ningún aspecto de visibilidad.
El alcance de una función anidada es el bloque que la contiene, ya sea un bloque de función o un bloque dentro del cuerpo de una función. No es visible (no se puede llamar por su nombre) fuera del bloque que la contiene.
Una función anidada puede utilizar identificadores (es decir, el nombre de funciones, variables, tipos, clases) declarados en cualquier bloque circundante, excepto cuando estén enmascarados por declaraciones internas con los mismos nombres.
Una función anidada se puede declarar dentro de otra función anidada, recursivamente, para formar una estructura profundamente anidada. Una función profundamente anidada puede acceder a los identificadores declarados en todos los bloques que la contienen, incluidas las funciones que la contienen.
Las funciones anidadas pueden, en ciertas situaciones, llevar a la creación de un cierre . Si es posible que la función anidada escape de la función que la encierra, por ejemplo, si las funciones son objetos de primera clase y una función anidada se pasa a otra función o se devuelve desde la función que la encierra, entonces se crea un cierre y las llamadas a esta función pueden acceder al entorno de la función original. El marco de la función que encierra inmediatamente debe seguir activo hasta que el último cierre de referencia muera y, por lo tanto, las variables automáticas no locales a las que se hace referencia en los cierres no se pueden asignar en la pila en lenguajes que permiten que el cierre persista más allá de la vida útil del bloque que lo encierra. Esto se conoce como el problema funarg y es una razón clave por la que las funciones anidadas no se implementaron en algunos lenguajes más simples, ya que complica significativamente la generación y el análisis de código, especialmente cuando las funciones están anidadas en varios niveles, compartiendo diferentes partes de su entorno.
La tecnología de funciones anidadas permite a un programador escribir código fuente que incluye atributos beneficiosos como ocultamiento de información , encapsulamiento y descomposición . El programador puede dividir una tarea en subtareas que solo tienen sentido dentro del contexto de la tarea, de modo que las funciones de las subtareas queden ocultas a los usuarios que no están diseñados para usarlas.
El alcance de bloque permite que las funciones compartan el estado de los bloques que los contienen (incluidas las funciones que los contienen) sin pasar parámetros ni utilizar variables globales . [1]
Una función anidada normalmente actúa como una función auxiliar o una función recursiva (como en el ejemplo de ordenación rápida anterior).
Las funciones anidadas se pueden utilizar para el flujo de control no estructurado , utilizando la declaración de retorno para el flujo de control no estructurado general. Esto se puede utilizar para un control más detallado que el que es posible con otras características integradas del lenguaje; por ejemplo, puede permitir la terminación anticipada de un bucle for si break
no está disponible, o la terminación anticipada de un bucle forbreak
anidado si no hay disponibles excepciones o de varios niveles .
En algunos lenguajes, es posible crear una función anidada que acceda a un conjunto de parámetros de la función externa, es decir, un cierre , y que esa función sea el valor de retorno de la función externa. Por lo tanto, es posible devolver una función que está configurada para cumplir una determinada tarea con pocos o ningún parámetro adicional, lo que puede aumentar el rendimiento de manera bastante significativa. [2]
Un ejemplo sencillo en Pascal:
función E ( x : real ) : real ; función F ( y : real ) : real ; inicio F := x + y fin ; inicio E := F ( 3 ) + F ( 4 ) fin ;
La función F
está anidada dentro de E
. Tenga en cuenta que E
el parámetro de x
también es visible en F
(ya que F
es parte de E
) mientras que tanto x
como y
son invisibles fuera de E
y F
respectivamente.
De manera similar, en ML estándar :
fun e ( x : real ) = sea fun f y = x + y en f 3 + f 4 fin ;
En Haskell :
e :: Flotante -> Flotante e x = f 3 + f 4 donde f y = x + y
En PL/I :
e: procedimiento(x) devuelve(float); declarar x float; f: procedimiento(y) devuelve(float); declarar y flotar; devuelve x + y fin; devuelve f(3.0) + f(4.0); fin;
En Python :
def e ( x : float ) -> float : def f ( y : float ) -> float : devuelve x + y devuelve f ( 3.0 ) + f ( 4.0 )
En GNU C [3] – que extiende el C estándar con funciones anidadas:
flotante E ( flotante x ) { flotante F ( flotante y ) { devuelve x + y ; } devuelve F ( 3 ) + F ( 4 ); }
La siguiente es una implementación de quicksort : [4]
void sort ( int * items , int size ) { void quickSort ( int first , int last ) { void swap ( int p , int q ) { int tmp = items [ p ]; items [ p ] = items [ q ]; items [ q ] = tmp ; } int partición () { int pivot = items [ first ], índice = primero ; swap ( índice , último ); for ( int i = primero ; i < último ; i ++ ) if ( items [ i ] < pivot ) swap ( índice ++ , i ); swap ( índice , último ); return índice ; } si ( primero < último ) { int pivotIndex = partición (); quickSort ( primero , pivotIndex - 1 ); quickSort ( pivotIndex + 1 , último ); } } quickSort ( 0 , tamaño - 1 ); }
La siguiente es una implementación del ordenamiento rápido basado en la partición Hoare que utiliza la sintaxis de expresión lambda de C++11 , que es una tecnología alternativa que también permite ocultar una función dentro de una función:
plantilla < typename RandomAccessIterator > auto Sort ( RandomAccessIterator Begin , RandomAccessIterator End ) -> void { auto Partition = [ & ]() { //Esquema de partición de Hoare auto & Pivot = * Begin ; auto ForwardCursor = Begin ; auto BackwardCursor = End - 1 ; auto PartitionPositionFound = false ; auto LocatePartitionPosition = [ & ]() { mientras ( * ForwardCursor < Pivot ) ++ ForwardCursor ; mientras ( Pivot < * BackwardCursor ) -- BackwardCursor ; si ( ForwardCursor >= BackwardCursor ) PartitionPositionFound = true ; de lo contrario Swap ( * ForwardCursor , * BackwardCursor ); }; //Función auxiliar trivial auto MoveOnAndTryAgain = [ & ]() { ++ ForwardCursor ; -- BackwardCursor ; }; //Breve descripción del proceso de partición actual while ( true ) { LocatePartitionPosition (); if ( PartitionPositionFound ) return BackwardCursor + 1 ; else MoveOnAndTryAgain (); } }; //Breve descripción del algoritmo de ordenación rápida if ( Begin < End - 1 ) { auto PartitionPosition = Partition (); Sort ( Begin , PartitionPosition ); Sort ( PartitionPosition , End ); } }
Los lenguajes notables que admiten funciones anidadas incluyen:
En la mayoría de los lenguajes de programación funcional , como Scheme, las funciones anidadas son una forma común de implementar algoritmos con bucles en ellos. Se crea una función interna recursiva simple ( tail ) , que se comporta como el bucle principal del algoritmo, mientras que la función externa realiza acciones de inicio que solo deben realizarse una vez. En casos más complejos, se pueden crear varias funciones recursivas entre sí como funciones internas.
Se pueden utilizar varias técnicas alternativas para lograr resultados de programación similares a los obtenidos con funciones anidadas.
Una alternativa común es aprovechar la tecnología de modularidad de un lenguaje. Algunas funciones están expuestas para su uso fuera del módulo y otras solo son visibles dentro del módulo.
En C, esto se puede implementar declarando funciones y variables como estáticas para ocultarlas del código externo al archivo. [9] Esto permite ocultar, encapsular y descomponer datos, pero a un nivel de granularidad diferente al de las funciones anidadas. Esta modularidad no admite más de un nivel de anidamiento.
En los lenguajes orientados a objetos , una clase normalmente proporciona un ámbito en el que las funciones y el estado pueden ocultarse a los consumidores de la clase, pero pueden accederse a ellos dentro de la clase. Algunos lenguajes permiten que las clases estén anidadas.
Para implementar la ocultación de datos, las funciones pueden pasar datos compartidos como parámetros, pero esto aumenta la complejidad de las llamadas de función. [1]
En C, esto generalmente se implementa pasando un puntero a una estructura que contiene los datos compartidos. [9]
En PHP y otros lenguajes, lambda es una alternativa. Una función se define en una declaración de código en lugar de declararse con la sintaxis de función habitual. No tiene nombre, pero se puede llamar a través de una referencia de función . Estas funciones se pueden definir dentro de una función, así como en otros ámbitos. Para utilizar variables locales en la función anónima, utilice closure .
Los siguientes lenguajes proporcionan características similares a las funciones anidadas:
La implementación de funciones anidadas puede ser más compleja de lo que parece, ya que una referencia a una función anidada que hace referencia a variables no locales crea un cierre . Por este motivo, las funciones anidadas no son compatibles con algunos lenguajes como C, C++ o Java, ya que esto hace que los compiladores sean más difíciles de implementar. [9] [12] Sin embargo, algunos compiladores sí las admiten, como una extensión específica del compilador. Un ejemplo bien conocido de esto es la implementación de C en GNU C , que comparte código con compiladores para lenguajes como Pascal, Ada y Modula.
Hay varias formas de implementar procedimientos anidados en un lenguaje con ámbito léxico, pero la forma clásica es la siguiente:
Este método original es más rápido de lo que parece, pero sin embargo suele optimizarse en compiladores modernos prácticos (utilizando visualizaciones o técnicas similares).
Otra forma de implementar funciones anidadas que utilizan algunos compiladores es convertir ("elevar") funciones anidadas en funciones no anidadas (donde parámetros adicionales ocultos reemplazan los enlaces de acceso) utilizando un proceso conocido como elevación lambda durante una etapa intermedia en la compilación.
Para que las funciones locales con no locales de ámbito léxico se pasen como resultados, el código de ejecución del lenguaje también debe pasar implícitamente el entorno (datos) que la función ve dentro de su función encapsulante, de modo que sea accesible también cuando la activación actual de la función envolvente ya no exista. [13] Esto significa que el entorno debe almacenarse en otra área de memoria que (las partes recuperadas posteriormente de) una pila de ejecución basada cronológicamente, lo que, a su vez, implica algún tipo de asignación de memoria libremente dinámica . Por lo tanto, muchos lenguajes antiguos basados en Algol (o dialectos de los mismos) no permiten que las funciones locales que acceden a no locales se pasen como valores de retorno, o no permiten funciones como valores de retorno en absoluto, aunque el paso de dichas funciones como argumentos todavía puede ser posible.
La implementación de funciones anidadas de GCC provoca una pérdida de pilas de no ejecución (pilas NX). Esta implementación llama a funciones anidadas a través de una instrucción de salto colocada en la pila de la máquina en tiempo de ejecución. Esto requiere que la pila sea ejecutable. Por lo tanto, las pilas de no ejecución y las funciones anidadas son mutuamente excluyentes en GCC. Si se utiliza una función anidada en el desarrollo de un programa, la pila NX se pierde silenciosamente, a menos que se llame a GCC con la opción de alertar sobre la condición. El software diseñado con Secure Development Lifecycle a menudo no permite el uso de funciones anidadas en este compilador en particular debido a la pérdida de pilas NX. [14]‑Wtrampoline