En programación de computadoras , el alcance de un enlace de nombre (una asociación de un nombre a una entidad, como una variable ) es la parte de un programa donde el enlace de nombre es válido; es decir, donde se puede utilizar el nombre para hacer referencia a la entidad. En otras partes del programa, el nombre puede referirse a una entidad diferente (puede tener una vinculación diferente) o a nada en absoluto (puede no estar vinculada). El alcance ayuda a evitar colisiones de nombres al permitir que el mismo nombre haga referencia a diferentes objetos, siempre que los nombres tengan ámbitos separados. El alcance de la vinculación de un nombre también se conoce como visibilidad de una entidad, particularmente en literatura más antigua o más técnica; esto es en relación con la entidad a la que se hace referencia, no con el nombre de referencia.
El término "alcance" también se utiliza para referirse al conjunto de todos los enlaces de nombres que son válidos dentro de una parte de un programa o en un punto determinado de un programa, lo que se denomina más correctamente contexto o entorno . [a]
Estrictamente hablando [b] y en la práctica para la mayoría de los lenguajes de programación , "parte de un programa" se refiere a una porción del código fuente (área de texto), y se conoce como alcance léxico . En algunos lenguajes, sin embargo, "parte de un programa" se refiere a una porción del tiempo de ejecución (período durante la ejecución ) y se conoce como alcance dinámico . Ambos términos son algo engañosos (hacen mal uso de términos técnicos, como se analiza en la definición), pero la distinción en sí es exacta y precisa, y estos son los términos estándar respectivos. El alcance léxico es el enfoque principal de este artículo, entendiéndose el alcance dinámico en contraste con el alcance léxico.
En la mayoría de los casos, la resolución de nombres basada en el alcance léxico es relativamente sencilla de usar e implementar, ya que en el uso se puede leer hacia atrás en el código fuente para determinar a qué entidad se refiere un nombre, y en la implementación se puede mantener una lista de nombres y contextos al compilar o interpretar un programa. Surgen dificultades con el enmascaramiento de nombres , las declaraciones adelantadas y la elevación , mientras que surgen dificultades considerablemente más sutiles con variables no locales , particularmente en los cierres .
La definición estricta del "alcance" (léxico) de un nombre ( identificador ) es inequívoca: el alcance léxico es "la parte del código fuente en la que se aplica la vinculación de un nombre con una entidad". Esto prácticamente no ha cambiado desde su definición de 1960 en la especificación de ALGOL 60 . Las especificaciones de idioma representativas son las siguientes:
Más comúnmente, "alcance" se refiere a cuando un nombre determinado puede referirse a una variable determinada (cuando una declaración tiene efecto), pero también puede aplicarse a otras entidades, como funciones, tipos, clases, etiquetas , constantes y enumeraciones.
Una distinción fundamental en cuanto al alcance es lo que significa "parte de un programa". En lenguajes con alcance léxico (también llamado alcance estático ), la resolución de nombres depende de la ubicación en el código fuente y el contexto léxico (también llamado contexto estático ), que se define según el lugar donde se define la variable o función nombrada. Por el contrario, en lenguajes con alcance dinámico, la resolución del nombre depende del estado del programa cuando se encuentra el nombre, que está determinado por el contexto de ejecución (también llamado contexto de ejecución , contexto de llamada o contexto dinámico ). En la práctica, con alcance léxico, un nombre se resuelve buscando en el contexto léxico local, luego, si eso falla, buscando en el contexto léxico externo, y así sucesivamente; mientras que con el alcance dinámico, un nombre se resuelve buscando el contexto de ejecución local, luego, si eso falla, buscando el contexto de ejecución externo, y así sucesivamente, avanzando hacia arriba en la pila de llamadas. [4]
La mayoría de los lenguajes modernos usan alcance léxico para variables y funciones, aunque el alcance dinámico se usa en algunos lenguajes, en particular algunos dialectos de Lisp, algunos lenguajes de "scripting" y algunos lenguajes de plantilla . [c] Perl 5 ofrece alcance tanto léxico como dinámico. Incluso en lenguajes con alcance léxico, el alcance de los cierres puede resultar confuso para los no iniciados, [ cita necesaria ] ya que dependen del contexto léxico donde se define el cierre, no de dónde se llama.
La resolución léxica se puede determinar en tiempo de compilación y también se conoce como enlace temprano , mientras que la resolución dinámica en general sólo se puede determinar en tiempo de ejecución y, por lo tanto, se conoce como enlace tardío .
En la programación orientada a objetos , el envío dinámico selecciona un método de objeto en tiempo de ejecución, aunque si el enlace de nombre real se realiza en tiempo de compilación o en tiempo de ejecución depende del lenguaje. El alcance dinámico de facto es común en los lenguajes de macros , que no resuelven nombres directamente, sino que se expanden en el lugar.
Algunos marcos de programación como AngularJS usan el término "alcance" para significar algo completamente diferente a cómo se usa en este artículo. En esos marcos, el alcance es solo un objeto del lenguaje de programación que usan ( JavaScript en el caso de AngularJS) que el marco usa de ciertas maneras para emular el alcance dinámico en un lenguaje que usa alcance léxico para sus variables. Esos alcances de AngularJS pueden estar en contexto o no en contexto (usando el significado habitual del término) en cualquier parte del programa, siguiendo las reglas habituales de alcance variable del lenguaje como cualquier otro objeto, y usando su propia herencia y reglas de transclusión . En el contexto de AngularJS, a veces se utiliza el término "$scope" (con un signo de dólar) para evitar confusión, pero las guías de estilo a menudo desaconsejan el uso del signo de dólar en los nombres de variables. [5]
El alcance es un componente importante de la resolución de nombres , [d] que a su vez es fundamental para la semántica del lenguaje . La resolución de nombres (incluido el alcance) varía entre lenguajes de programación y, dentro de un lenguaje de programación, varía según el tipo de entidad; las reglas de alcance se denominan reglas de alcance (o reglas de alcance ). Junto con los espacios de nombres , las reglas de alcance son cruciales en la programación modular , por lo que un cambio en una parte del programa no rompe una parte no relacionada.
Cuando se habla de alcance, hay tres conceptos básicos: alcance, extensión y contexto. "Ámbito" y "contexto" en particular se confunden con frecuencia: el alcance es una propiedad de un enlace de nombre, mientras que el contexto es una propiedad de una parte de un programa, es decir, una parte del código fuente ( contexto léxico o contexto estático ) o una parte del tiempo de ejecución ( contexto de ejecución, contexto de tiempo de ejecución, contexto de llamada o contexto dinámico ). El contexto de ejecución consta de un contexto léxico (en el punto de ejecución actual) más un estado de tiempo de ejecución adicional, como la pila de llamadas . [e] Estrictamente hablando, durante la ejecución un programa entra y sale de varios alcances de enlaces de nombres, y en un punto de la ejecución los enlaces de nombres están "en contexto" o "no en contexto", por lo tanto, los enlaces de nombres "entran en contexto" o "van fuera de contexto" cuando la ejecución del programa entra o sale del alcance. [f] Sin embargo, en la práctica el uso es mucho más flexible.
El alcance es un concepto a nivel de código fuente y una propiedad de las vinculaciones de nombres, particularmente las vinculaciones de nombres de variables o funciones (los nombres en el código fuente son referencias a entidades en el programa) y es parte del comportamiento de un compilador o intérprete de un lenguaje. . Como tal, las cuestiones de alcance son similares a los punteros , que son un tipo de referencia utilizado en programas de manera más general. Usar el valor de una variable cuando el nombre está en contexto pero la variable no está inicializada es análogo a desreferenciar (acceder al valor de) un puntero comodín , ya que no está definido. Sin embargo, como las variables no se destruyen hasta que salen de contexto, no existe la analogía de un puntero colgante .
Para entidades como las variables, el alcance es un subconjunto de la duración (también conocido como extensión ): un nombre solo puede referirse a una variable que existe (posiblemente con un valor indefinido), pero las variables que existen no son necesariamente visibles: una variable puede existir pero ser inaccesible (el valor se almacena pero no se hace referencia a él dentro de un contexto determinado), o accesible pero no a través del nombre de pila, en cuyo caso no está en contexto (el programa está "fuera del alcance del nombre"). En otros casos, la "vida útil" es irrelevante: una etiqueta (posición nombrada en el código fuente) tiene una vida útil idéntica a la del programa (para lenguajes compilados estáticamente), pero puede estar en contexto o no en un punto determinado del programa, y lo mismo para Variables estáticas : una variable global estática está en contexto para todo el programa, mientras que una variable local estática solo está en contexto dentro de una función u otro contexto local, pero ambas tienen una vida útil de toda la ejecución del programa.
Determinar a qué entidad se refiere un nombre se conoce como resolución de nombres o vinculación de nombres (particularmente en programación orientada a objetos ) y varía según el lenguaje. Dado un nombre, el lenguaje (correctamente, el compilador o intérprete) verifica todas las entidades que están en contexto en busca de coincidencias; en caso de ambigüedad (dos entidades con el mismo nombre, como una variable global y local con el mismo nombre), se utilizan las reglas de resolución de nombres para distinguirlas. Con mayor frecuencia, la resolución de nombres se basa en una regla de "contexto interno a externo", como la regla LEGB (local, envolvente, global, integrada) de Python: los nombres se resuelven implícitamente en el contexto relevante más limitado. En algunos casos, la resolución de nombres se puede especificar explícitamente, como mediante las palabras clave global
y nonlocal
en Python; en otros casos, las reglas predeterminadas no se pueden anular.
Cuando dos nombres idénticos están en contexto al mismo tiempo, refiriéndose a entidades diferentes, se dice que se está produciendo un enmascaramiento de nombres , donde el nombre de mayor prioridad (generalmente el más interno) está "enmascarando" el nombre de menor prioridad. A nivel de variables esto se conoce como sombreado de variables . Debido a la posibilidad de que se produzcan errores lógicos debido al enmascaramiento, algunos lenguajes no permiten o desaconsejan el enmascaramiento, generando un error o advertencia en tiempo de compilación o ejecución.
Varios lenguajes de programación tienen diferentes reglas de alcance para diferentes tipos de declaraciones y nombres. Estas reglas de alcance tienen un gran efecto en la semántica del lenguaje y, en consecuencia, en el comportamiento y la corrección de los programas. En lenguajes como C++ , acceder a una variable independiente no tiene una semántica bien definida y puede resultar en un comportamiento indefinido , similar a referirse a un puntero colgante ; y las declaraciones o nombres utilizados fuera de su alcance generarán errores de sintaxis .
Los ámbitos suelen estar vinculados a otras construcciones del lenguaje y se determinan implícitamente, pero muchos lenguajes también ofrecen construcciones específicamente para controlar el alcance.
El alcance puede variar desde una sola expresión hasta todo el programa, con muchas gradaciones posibles en el medio. La regla de alcance más simple es el alcance global: todas las entidades son visibles en todo el programa. La regla de alcance modular más básica es el alcance de dos niveles, con un alcance global en cualquier parte del programa y un alcance local dentro de una función. La programación modular más sofisticada permite un alcance de módulo separado, donde los nombres son visibles dentro del módulo (privados del módulo) pero no visibles fuera de él. Dentro de una función, algunos lenguajes, como C, permiten que el alcance del bloque restrinja el alcance a un subconjunto de una función; otros, en particular los lenguajes funcionales, permiten el alcance de la expresión, para restringir el alcance a una sola expresión. Otros ámbitos incluyen el alcance del archivo (especialmente en C), que se comporta de manera similar al alcance del módulo, y el alcance del bloque fuera de las funciones (especialmente en Perl).
Una cuestión sutil es exactamente cuándo comienza y termina un alcance. En algunos lenguajes, como C, el alcance de un nombre comienza en la declaración del nombre y, por lo tanto, diferentes nombres declarados dentro de un bloque determinado pueden tener diferentes alcances. Esto requiere declarar funciones antes de su uso, aunque no necesariamente definirlas, y requiere una declaración directa en algunos casos, especialmente para recursividad mutua. En otros lenguajes, como Python, el alcance de un nombre comienza al inicio del bloque relevante donde se declara el nombre (como el inicio de una función), independientemente de dónde esté definido, por lo que todos los nombres dentro de un bloque determinado tienen la mismo alcance. En JavaScript, el alcance de un nombre declarado con let
o const
comienza en la declaración del nombre, y el alcance de un nombre declarado con var
comienza al inicio de la función donde se declara el nombre, lo que se conoce como elevación de variables . El comportamiento de los nombres en contexto que tienen un valor indefinido difiere: en Python el uso de nombres indefinidos produce un error de tiempo de ejecución, mientras que en JavaScript los nombres indefinidos declarados con var
se pueden utilizar en toda la función porque están vinculados implícitamente al valor undefined
.
El alcance de un enlace de nombre es una expresión , que se conoce como alcance de expresión . El alcance de la expresión está disponible en muchos idiomas, especialmente en los lenguajes funcionales que ofrecen una característica llamada expresiones let que permite que el alcance de una declaración sea una expresión única. Esto es conveniente si, por ejemplo, se necesita un valor intermedio para un cálculo. Por ejemplo, en Standard ML , si f()
devuelve 12
, entonces es una expresión que se evalúa como , utilizando una variable temporal nombrada para evitar llamar dos veces. Algunos lenguajes con alcance de bloque se aproximan a esta funcionalidad al ofrecer sintaxis para que un bloque se incruste en una expresión; por ejemplo, la expresión ML estándar antes mencionada podría escribirse en Perl como o en GNU C como .let val x = f() in x * x end
144
x
f()
do { my $x = f(); $x * $x }
({ int x = f(); x * x; })
En Python, las variables auxiliares en expresiones generadoras y listas por comprensión (en Python 3) tienen alcance de expresión.
En C, los nombres de variables en un prototipo de función tienen un alcance de expresión, conocido en este contexto como alcance del protocolo de función . Como no se hace referencia a los nombres de las variables en el prototipo (pueden ser diferentes en la definición real) (son simplemente ficticios), a menudo se omiten, aunque pueden usarse para generar documentación, por ejemplo.
El alcance de un enlace de nombre es un bloque , lo que se conoce como alcance de bloque . El alcance del bloque está disponible en muchos, pero no en todos, los lenguajes de programación estructurados en bloques. Esto comenzó con ALGOL 60 , donde "[c]ada declaración... es válida sólo para ese bloque", [6] y hoy está particularmente asociado con lenguajes de las familias y tradiciones Pascal y C. La mayoría de las veces, este bloque está contenido dentro de una función, lo que restringe el alcance a una parte de una función, pero en algunos casos, como en Perl, es posible que el bloque no esté dentro de una función.
int sin signo suma_de_cuadrados ( const int sin signo N ) { int sin signo ret = 0 ; for ( unsigned int n = 1 ; n <= N ; n ++ ) { const unsigned int n_squared = n * n ; ret += n_cuadrado ; } devolver retiro ; }
Un ejemplo representativo del uso del alcance del bloque es el código C que se muestra aquí, donde dos variables tienen el alcance del bucle: la variable del bucle n , que se inicializa una vez y se incrementa en cada iteración del bucle, y la variable auxiliar n_squared , que se inicializa en cada iteración. El propósito es evitar agregar variables al alcance de la función que solo son relevantes para un bloque en particular; por ejemplo, esto evita errores en los que la variable de bucle genérica i accidentalmente ya se ha establecido en otro valor. En este ejemplo, la expresión n * n
generalmente no se asignaría a una variable auxiliar y el cuerpo del bucle simplemente se escribiría, ret += n * n
pero en ejemplos más complicados las variables auxiliares son útiles.
Los bloques se utilizan principalmente para controlar el flujo, como con los bucles if, while y for, y en estos casos el alcance del bloque significa que el alcance de la variable depende de la estructura del flujo de ejecución de una función. Sin embargo, los lenguajes con alcance de bloque generalmente también permiten el uso de bloques "desnudos", cuyo único propósito es permitir un control detallado del alcance de la variable. Por ejemplo, una variable auxiliar puede definirse en un bloque, luego usarse (por ejemplo, agregarse a una variable con alcance de función) y descartarse cuando finaliza el bloque, o se puede incluir un bucle while en un bloque que inicializa las variables utilizadas dentro del bucle. eso solo debe inicializarse una vez.
Una sutileza de varios lenguajes de programación, como Algol 68 y C (demostrado en este ejemplo y estandarizado desde C99 ), es que las variables de alcance del bloque se pueden declarar no solo dentro del cuerpo del bloque, sino también dentro de la declaración de control, si cualquier. Esto es análogo a los parámetros de función, que se declaran en la declaración de función (antes de que comience el bloque del cuerpo de la función) y en el alcance de todo el cuerpo de la función. Esto se usa principalmente en bucles for , que tienen una declaración de inicialización separada de la condición del bucle, a diferencia de los bucles while, y es un modismo común.
El alcance del bloque se puede utilizar para el seguimiento. En este ejemplo, dentro del bloque la variable auxiliar también podría haberse llamado n , ocultando el nombre del parámetro, pero esto se considera un estilo deficiente debido a la posibilidad de errores. Además, algunos descendientes de C, como Java y C#, a pesar de tener soporte para alcance de bloque (en el sentido de que se puede hacer que una variable local salga de contexto antes del final de una función), no permiten que una variable local oculte otra. . En tales lenguajes, el intento de declaración de la segunda n resultaría en un error de sintaxis y habría que cambiar el nombre de una de las n variables.
Si se utiliza un bloque para establecer el valor de una variable, el alcance del bloque requiere que la variable se declare fuera del bloque. Esto complica el uso de declaraciones condicionales con asignación única . Por ejemplo, en Python, que no utiliza alcance de bloque, se puede inicializar una variable como tal:
si c : a = "foo" más : a = ""
donde a
es accesible después de la if
declaración.
En Perl, que tiene alcance de bloque, esto requiere declarar la variable antes del bloque:
mi $a ; si ( c ) { $a = 'foo' ; } más { $a = '' ; }
A menudo, esto se reescribe mediante asignación múltiple, inicializando la variable a un valor predeterminado. En Python (donde no es necesario) esto sería:
a = "" si c : a = "foo"
mientras que en Perl esto sería:
mi $a = '' ; si ( c ) { $a = 'foo' ; }
En el caso de una asignación de una sola variable, una alternativa es utilizar el operador ternario para evitar un bloque, pero esto en general no es posible para asignaciones de múltiples variables y es difícil de leer para lógica compleja.
Este es un problema más importante en C, especialmente para la asignación de cadenas, ya que la inicialización de cadenas puede asignar memoria automáticamente, mientras que la asignación de cadenas a una variable ya inicializada requiere asignar memoria, una copia de la cadena y verificar que sean exitosas.
{ mi $contador = 0 ; sub incremento_contador { retorno ++ $contador ; } }
Algunos lenguajes permiten aplicar el concepto de alcance de bloque, en diversos grados, fuera de una función. Por ejemplo, en el fragmento de Perl de la derecha, $counter
hay un nombre de variable con alcance de bloque (debido al uso de la my
palabra clave), mientras que increment_counter
es un nombre de función con alcance global. Cada llamada a increment_counter
aumentará el valor de $counter
en uno y devolverá el nuevo valor. El código fuera de este bloque puede llamar a increment_counter
, pero no puede obtener ni alterar el valor de $counter
. Este modismo permite definir cierres en Perl.
Cuando el alcance de las variables declaradas dentro de una función no se extiende más allá de esa función, esto se conoce como alcance de la función . [7] El alcance de la función está disponible en la mayoría de los lenguajes de programación que ofrecen una forma de crear una variable local en una función o subrutina : una variable cuyo alcance finaliza (que sale de contexto) cuando la función regresa. En la mayoría de los casos, la vida útil de la variable es la duración de la llamada a la función; es una variable automática , creada cuando se inicia la función (o se declara la variable), y se destruye cuando la función regresa, mientras que el alcance de la variable está dentro del función, aunque el significado de "dentro" depende de si el alcance es léxico o dinámico. Sin embargo, algunos lenguajes, como C, también proporcionan variables locales estáticas , donde la vida útil de la variable es toda la vida útil del programa, pero la variable solo está en contexto cuando está dentro de la función. En el caso de las variables locales estáticas, la variable se crea cuando el programa se inicializa y se destruye solo cuando el programa finaliza, como ocurre con una variable global estática , pero solo está en contexto dentro de una función, como una variable local automática.
Es importante destacar que, en el alcance léxico, una variable con alcance de función tiene alcance solo dentro del contexto léxico de la función: sale de contexto cuando se llama a otra función dentro de la función y vuelve al contexto cuando la función regresa; las funciones llamadas no tienen acceso a las variables locales de las funciones que llaman, y las variables locales solo están en contexto dentro del cuerpo de la función en la que se declaran. Por el contrario, en el alcance dinámico, el alcance se extiende al contexto de ejecución de la función: las variables locales permanecen en contexto cuando se llama a otra función, y solo salen de contexto cuando finaliza la función que las define y, por lo tanto, las variables locales están en el contexto de la función. en el que están definidos y todos llamados funciones . En lenguajes con alcance léxico y funciones anidadas , las variables locales están en contexto para funciones anidadas, ya que estas están dentro del mismo contexto léxico, pero no para otras funciones que no están anidadas léxicas. Una variable local de una función adjunta se conoce como variable no local para la función anidada. El alcance de la función también es aplicable a funciones anónimas .
def cuadrado ( n ): devolver n * ndef suma_de_cuadrados ( n ): total = 0 i = 0 mientras que i <= n : total += cuadrado ( i ) i += 1 devolver total
Por ejemplo, en el fragmento de código Python de la derecha, se definen dos funciones: square
y sum_of_squares
. square
calcula el cuadrado de un número; sum_of_squares
Calcula la suma de todos los cuadrados hasta un número. (Por ejemplo, square(4)
es 4 2 = 16
y sum_of_squares(4)
es 0 2 + 1 2 + 2 2 + 3 2 + 4 2 = 30
.)
Cada una de estas funciones tiene una variable llamada n que representa el argumento de la función. Estas dos n variables están completamente separadas y no relacionadas, a pesar de tener el mismo nombre, porque son variables locales de alcance léxico con alcance de función: el alcance de cada una es su propia función léxicamente separada y, por lo tanto, no se superponen. Por lo tanto, sum_of_squares
puede llamar sin que se altere square
su propia n . De manera similar, sum_of_squares
tiene variables denominadas total e i ; estas variables, debido a su alcance limitado, no interferirán con ninguna variable denominada total o i que pueda pertenecer a cualquier otra función. En otras palabras, no hay riesgo de colisión de nombres entre estos nombres y cualquier nombre no relacionado, incluso si son idénticos.
No se produce ningún enmascaramiento de nombres: solo una variable denominada n está en contexto en un momento dado, ya que los ámbitos no se superponen. Por el contrario, si un fragmento similar se escribiera en un lenguaje con alcance dinámico, la n en la función de llamada permanecería en contexto en la función llamada (los alcances se superpondrían) y quedaría enmascarada ("sombreada") por la nueva n. en la función llamada.
El alcance de la función es significativamente más complicado si las funciones son objetos de primera clase y pueden crearse localmente en una función y luego devolverse. En este caso, cualquier variable en la función anidada que no sea local para ella (variables independientes en la definición de la función, que se resuelven en variables en un contexto adjunto) crea un cierre , ya que no solo la función en sí, sino también su contexto (de variables ) debe devolverse y luego, potencialmente, llamarse en un contexto diferente. Esto requiere mucho más soporte por parte del compilador y puede complicar el análisis del programa.
El alcance de un enlace de nombre es un archivo, lo que se conoce como alcance del archivo . El alcance del archivo es en gran medida particular de C (y C++), donde el alcance de las variables y funciones declaradas en el nivel superior de un archivo (no dentro de ninguna función) es para todo el archivo, o más bien para C, desde la declaración hasta el final de el archivo fuente, o más precisamente la unidad de traducción (enlace interno). Esto puede verse como una forma de alcance de módulo, donde los módulos se identifican con archivos y, en lenguajes más modernos, se reemplaza por un alcance de módulo explícito. Debido a la presencia de declaraciones de inclusión, que agregan variables y funciones al contexto interno y pueden llamar a otras declaraciones de inclusión, puede resultar difícil determinar qué está en contexto en el cuerpo de un archivo.
En el fragmento de código C anterior, el nombre de la función sum_of_squares
tiene alcance global (en C, enlace externo). Agregar static
a la firma de la función daría como resultado el alcance del archivo (vínculo interno).
El alcance de un enlace de nombre es un módulo, lo que se conoce como alcance del módulo . El alcance del módulo está disponible en lenguajes de programación modulares donde los módulos (que pueden abarcar varios archivos) son la unidad básica de un programa complejo, ya que permiten ocultar y exponer información en una interfaz limitada. El alcance del módulo fue pionero en la familia de lenguajes Modula , y Python (que fue influenciado por Modula) es un ejemplo contemporáneo representativo.
En algunos lenguajes de programación orientados a objetos que carecen de soporte directo para módulos, como C++ antes de C++ 20, [8] la jerarquía de clases proporciona una estructura similar, donde las clases son la unidad básica del programa y una clase Puede tener métodos privados. Esto se entiende correctamente en el contexto del despacho dinámico en lugar de la resolución de nombres y el alcance, aunque a menudo desempeñan funciones análogas. En algunos casos, ambas funciones están disponibles, como en Python, que tiene módulos y clases, y la organización del código (como una función a nivel de módulo o un método convencionalmente privado) es una elección del programador.
El alcance de un enlace de nombre es un programa completo, lo que se conoce como alcance global . Los nombres de variables con alcance global (llamados variables globales ) con frecuencia se consideran una mala práctica, al menos en algunos lenguajes, debido a la posibilidad de colisiones de nombres y enmascaramiento involuntario, junto con una modularidad deficiente, y el alcance de la función o el alcance del bloque se consideran preferibles. Sin embargo, el alcance global se utiliza normalmente (según el idioma) para otros tipos de nombres, como nombres de funciones, nombres de clases y nombres de otros tipos de datos . En estos casos se utilizan mecanismos como los espacios de nombres para evitar colisiones.
El uso de variables locales (de nombres de variables con alcance limitado, que solo existen dentro de una función específica) ayuda a evitar el riesgo de una colisión de nombres entre dos variables con nombres idénticos. Sin embargo, existen dos enfoques muy diferentes para responder a esta pregunta: ¿Qué significa estar "dentro" de una función?
En el alcance léxico (o alcance léxico ; también llamado alcance estático o alcance estático ), si el alcance de un nombre de variable es una determinada función, entonces su alcance es el texto del programa de la definición de función: dentro de ese texto, el nombre de la variable existe y es vinculado al valor de la variable, pero fuera de ese texto, el nombre de la variable no existe. Por el contrario, en el alcance dinámico (o alcance dinámico ), si el alcance de un nombre de variable es una determinada función, entonces su alcance es el período de tiempo durante el cual se ejecuta la función: mientras la función se está ejecutando, el nombre de la variable existe y es vinculado a su valor, pero después de que la función regresa, el nombre de la variable no existe. Esto significa que si la función f
invoca una función definida por separado , entonces ,g
bajo el alcance léxico, la función g
no tiene acceso a las variables locales (asumiendo que el texto de no está dentro del texto de ), mientras que bajo el alcance dinámico, la función sí tiene acceso a ' s variables locales (ya que se invoca durante la invocación de ).f
g
f
g
f
g
f
$ # lenguaje bash $ x = 1 $ función g () { echo $x ; x = 2 ; } $ función f () { local x = 3 ; gramo ; } $ f # ¿imprime esto 1 o 3? 3 $ echo $x # ¿imprime esto 1 o 2? 1
Consideremos, por ejemplo, el programa de la derecha. La primera línea, crea una variable global y la inicializa en . La segunda línea, define una función que imprime ("hace eco") del valor actual de y luego se establece en (sobrescribiendo el valor anterior). La tercera línea define una función que crea una variable local (ocultando la variable global con el mismo nombre) y la inicializa en y luego llama . La cuarta línea, llama . La quinta línea, imprime el valor actual de .x=1
x
1
function g() { echo $x ; x=2 ; }
g
x
x
2
function f() { local x=3 ; g ; }
f
x
3
g
f
f
echo $x
x
Entonces, ¿qué imprime exactamente este programa? Depende de las reglas de alcance. Si el lenguaje de este programa es uno que usa alcance léxico, entonces g
imprime y modifica la variable global x
(porque g
está definida fuera de f
), por lo que el programa imprime 1
y luego 2
. Por el contrario, si este lenguaje usa alcance dinámico, entonces g
imprime y modifica f
la variable local x
(porque g
se llama desde dentro f
), por lo que el programa imprime 3
y luego 1
. (Da la casualidad de que el lenguaje del programa es Bash , que usa alcance dinámico; por lo que el programa imprime 3
y luego 1
. Si se ejecutara el mismo código con ksh93 que usa alcance léxico, los resultados serían diferentes).
Con alcance léxico , un nombre siempre hace referencia a su contexto léxico. Esta es una propiedad del texto del programa y la implementación del lenguaje la hace independiente de la pila de llamadas en tiempo de ejecución. Debido a que esta coincidencia solo requiere el análisis del texto del programa estático, este tipo de alcance también se denomina alcance estático . El alcance léxico es estándar en todos los lenguajes basados en ALGOL , como Pascal , Modula-2 y Ada , así como en lenguajes funcionales modernos como ML y Haskell . También se utiliza en el lenguaje C y sus parientes sintácticos y semánticos, aunque con distintos tipos de limitaciones. El alcance estático permite al programador razonar sobre referencias de objetos como parámetros, variables, constantes, tipos, funciones, etc. como simples sustituciones de nombres. Esto hace que sea mucho más fácil crear código modular y razonar al respecto, ya que la estructura de nombres local se puede entender de forma aislada. Por el contrario, el alcance dinámico obliga al programador a anticipar todos los contextos de ejecución posibles en los que se puede invocar el código del módulo.
programa A ; var I : número entero ; K : carbón ; procedimiento B ; var K : real ; L : número entero ; procedimiento C ; var M : real ; comenzar (*alcance A+B+C*) finalizar ; (*alcance A+B*) fin ; (*alcance A*) final .
Por ejemplo, Pascal tiene un alcance léxico. Considere el fragmento del programa Pascal a la derecha. La variable I
es visible en todos los puntos, porque nunca está oculta por otra variable con el mismo nombre. La char
variable K
es visible sólo en el programa principal porque está oculta por la real
variable K
visible en el procedimiento B
y C
únicamente. La variable L
también es visible solo en el procedimiento B
y C
no oculta ninguna otra variable. La variable M
solo es visible en el procedimiento C
y, por lo tanto, no se puede acceder a ella ni desde el procedimiento B
ni desde el programa principal. Además, el procedimiento C
sólo es visible en el procedimiento B
y, por lo tanto, no se puede llamar desde el programa principal.
Podría haber habido otro procedimiento C
declarado en el programa fuera de procedimiento B
. El lugar en el programa donde C
se menciona " " determina a cuál de los dos procedimientos nombrados C
representa, es decir, exactamente de forma análoga al alcance de las variables.
La implementación correcta del alcance léxico en lenguajes con funciones anidadas de primera clase no es trivial, ya que requiere que cada valor de función lleve consigo un registro de los valores de las variables de las que depende (el par de la función y este contexto se llama un cierre ). Dependiendo de la implementación y la arquitectura de la computadora , la búsqueda de variables puede volverse ligeramente ineficiente [ cita necesaria ] cuando se utilizan funciones muy profundamente anidadas léxicamente , aunque existen técnicas bien conocidas para mitigar esto. [9] [10] Además, para funciones anidadas que solo hacen referencia a sus propios argumentos y (inmediatamente) a variables locales, todas las ubicaciones relativas se pueden conocer en el momento de la compilación . Por lo tanto, no se incurre en ningún tipo de sobrecarga al utilizar ese tipo de función anidada. Lo mismo se aplica a partes particulares de un programa donde no se utilizan funciones anidadas y, naturalmente, a programas escritos en un lenguaje donde las funciones anidadas no están disponibles (como en el lenguaje C).
El alcance léxico se utilizó por primera vez a principios de la década de 1960 para el lenguaje imperativo ALGOL 60 y desde entonces se ha adoptado en la mayoría de los demás lenguajes imperativos. [4]
Lenguajes como Pascal y C siempre han tenido alcance léxico, ya que ambos están influenciados por las ideas contenidas en ALGOL 60 y ALGOL 68 (aunque C no incluía funciones léxicas anidadas ).
Perl es un lenguaje con alcance dinámico al que luego se le agregó alcance estático.
El intérprete Lisp original (1960) utilizaba alcance dinámico. El enlace profundo , que se aproxima al alcance estático (léxico), se introdujo alrededor de 1962 en LISP 1.5 (a través del dispositivo Funarg desarrollado por Steve Russell , trabajando bajo la dirección de John McCarthy ).
Todos los primeros Lisps usaban alcance dinámico, cuando se basaban en intérpretes. En 1982, Guy L. Steele Jr. y el Common LISP Group publican An Overview of Common LISP , [11] una breve reseña de la historia y las implementaciones divergentes de Lisp hasta ese momento y una revisión de las características que ofrece un Common Lisp. implementación debería tener. En la página 102 leemos:
La mayoría de las implementaciones de LISP son internamente inconsistentes en el sentido de que, de forma predeterminada, el intérprete y el compilador pueden asignar semánticas diferentes a los programas correctos; esto se debe principalmente al hecho de que el intérprete asume que todas las variables tienen un alcance dinámico, mientras que el compilador asume que todas las variables son locales a menos que se vea obligado a asumir lo contrario. Esto se ha hecho por motivos de comodidad y eficiencia, pero puede provocar errores muy sutiles. La definición de Common LISP evita tales anomalías al requerir explícitamente que el intérprete y el compilador impongan una semántica idéntica a los programas correctos.
Por lo tanto, se requería que las implementaciones de Common LISP tuvieran alcance léxico . Nuevamente, de una descripción general de Common LISP :
Además, Common LISP ofrece las siguientes funciones (la mayoría de las cuales están tomadas de MacLisp, InterLisp o Lisp Machines Lisp): (...) Variables de alcance totalmente léxico. El llamado " problema FUNARG " [12] [13] está completamente solucionado, tanto en el caso de bajada como en el de subida.
Para el mismo año en que se publicó una descripción general de Common LISP (1982), se habían publicado diseños iniciales (también de Guy L. Steele Jr.) de un Lisp compilado con alcance léxico, llamado Scheme , y se estaban intentando implementaciones del compilador. En ese momento, se temía comúnmente que el alcance léxico en Lisp fuera ineficaz de implementar. En Una historia de T , [14] Olin Shivers escribe:
Todos los Lisps serios en uso de producción en ese momento tenían un alcance dinámico. Nadie que no hubiera leído detenidamente la tesis de Rabbit [15] (escrita por Guy Lewis Steele Jr. en 1978) creía que el alcance léxico funcionaría; Incluso las pocas personas que lo habían leído estaban dando un poco de fe en que esto iba a funcionar en un uso de producción serio.
El término "alcance léxico" data al menos de 1967, [16] mientras que el término "alcance léxico" data al menos de 1970, donde se utilizó en el Proyecto MAC para describir las reglas de alcance del dialecto Lisp MDL (entonces conocido como " Confusión"). [17]
Con alcance dinámico , un nombre se refiere al contexto de ejecución. En términos técnicos, esto significa que cada nombre tiene una pila global de enlaces. La introducción de una variable local con nombre x
envía un enlace a la x
pila global (que puede haber estado vacía), que se elimina cuando el flujo de control sale del alcance. La evaluación x
en cualquier contexto siempre produce el vínculo superior. Tenga en cuenta que esto no se puede hacer en tiempo de compilación porque la pila de enlace solo existe en tiempo de ejecución , razón por la cual este tipo de alcance se denomina alcance dinámico .
El alcance dinámico es poco común en los lenguajes modernos. [4]
Generalmente, ciertos bloques se definen para crear enlaces cuya vida útil es el tiempo de ejecución del bloque; esto agrega algunas características de alcance estático al proceso de alcance dinámico. Sin embargo, dado que se puede llamar a una sección de código desde muchas ubicaciones y situaciones diferentes, puede resultar difícil determinar desde el principio qué enlaces se aplicarán cuando se utilice una variable (o si existe alguna). Esto puede resultar beneficioso; La aplicación del principio de conocimiento mínimo sugiere que el código evite depender de las razones (o circunstancias) del valor de una variable, sino que simplemente use el valor de acuerdo con la definición de la variable. Esta interpretación estrecha de los datos compartidos puede proporcionar un sistema muy flexible para adaptar el comportamiento de una función al estado (o política) actual del sistema. Sin embargo, este beneficio se basa en una documentación cuidadosa de todas las variables utilizadas de esta manera, así como en evitar cuidadosamente suposiciones sobre el comportamiento de una variable, y no proporciona ningún mecanismo para detectar interferencias entre diferentes partes de un programa. Algunos lenguajes, como Perl y Common Lisp , permiten al programador elegir un alcance estático o dinámico al definir o redefinir una variable. Ejemplos de lenguajes que utilizan alcance dinámico incluyen Logo , Emacs Lisp , LaTeX y los lenguajes shell bash , dash y PowerShell .
El alcance dinámico es bastante fácil de implementar. Para encontrar el valor de un nombre, el programa podría recorrer la pila de tiempo de ejecución, comprobando cada registro de activación (el marco de la pila de cada función) en busca de un valor para el nombre. En la práctica, esto se hace más eficiente mediante el uso de una lista de asociación , que es una pila de pares de nombre/valor. Los pares se colocan en esta pila cada vez que se realizan declaraciones y se extraen cuando las variables salen de contexto. [18] La vinculación superficial es una estrategia alternativa que es considerablemente más rápida, ya que utiliza una tabla de referencia central , que asocia cada nombre con su propia pila de significados. Esto evita una búsqueda lineal durante el tiempo de ejecución para encontrar un nombre en particular, pero se debe tener cuidado de mantener adecuadamente esta tabla. [18] Tenga en cuenta que ambas estrategias asumen un orden de último en entrar, primero en salir ( LIFO ) para los enlaces de cualquier variable; en la práctica, todas las consolidaciones están así ordenadas.
Una implementación aún más simple es la representación de variables dinámicas con variables globales simples. El enlace local se realiza guardando el valor original en una ubicación anónima de la pila que es invisible para el programa. Cuando finaliza ese alcance vinculante, el valor original se restaura desde esta ubicación. De hecho, el alcance dinámico se originó de esta manera. Las primeras implementaciones de Lisp utilizaron esta estrategia obvia para implementar variables locales, y la práctica sobrevive en algunos dialectos que todavía están en uso, como GNU Emacs Lisp. El alcance léxico se introdujo en Lisp más tarde. Esto es equivalente al esquema de vinculación superficial anterior, excepto que la tabla de referencia central es simplemente el contexto de vinculación de la variable global, en el que el significado actual de la variable es su valor global. Mantener variables globales no es complejo. Por ejemplo, un objeto de símbolo puede tener un espacio dedicado para su valor global.
El alcance dinámico proporciona una excelente abstracción para el almacenamiento local de subprocesos , pero si se usa de esa manera no puede basarse en guardar y restaurar una variable global. Una posible estrategia de implementación es que cada variable tenga una clave local de subproceso. Cuando se accede a la variable, la clave local del subproceso se utiliza para acceder a la ubicación de la memoria local del subproceso (mediante el código generado por el compilador, que sabe qué variables son dinámicas y cuáles son léxicas). Si la clave local del subproceso no existe para el subproceso que llama, entonces se utiliza la ubicación global. Cuando una variable está vinculada localmente, el valor anterior se almacena en una ubicación oculta de la pila. El almacenamiento local del subproceso se crea bajo la clave de la variable y el nuevo valor se almacena allí. Otras anulaciones anidadas de la variable dentro de ese subproceso simplemente guardan y restauran esta ubicación local del subproceso. Cuando termina el contexto inicial de anulación más externa, la clave local del subproceso se elimina, exponiendo la versión global de la variable una vez más a ese subproceso.
Con la transparencia referencial, el alcance dinámico se restringe únicamente a la pila de argumentos de la función actual y coincide con el alcance léxico.
En los lenguajes modernos, la expansión de macros en un preprocesador es un ejemplo clave de alcance dinámico de facto. El lenguaje de macros en sí solo transforma el código fuente, sin resolver nombres, pero dado que la expansión se realiza en el lugar, cuando se resuelven los nombres en el texto expandido (en particular, las variables libres), se resuelven en función de dónde se expanden (en términos generales). "llamado"), como si estuviera ocurriendo un alcance dinámico.
El preprocesador de C , utilizado para la expansión de macros , tiene un alcance dinámico de facto, ya que no realiza resolución de nombres por sí solo y es independiente de dónde está definida la macro. Por ejemplo, la macro:
#definir ADD_A(x) x + a
se expandirá para agregar a
a la variable pasada, y este nombre solo lo resolverá más tarde el compilador en función de dónde ADD_A
se "llama" la macro (correctamente, expandida). Correctamente, el preprocesador de C solo realiza análisis léxico , expandiendo la macro durante la etapa de tokenización, pero sin analizar en un árbol de sintaxis ni resolver nombres.
Por ejemplo, en el siguiente código, el nombre a
de la macro se resuelve (después de la expansión) en la variable local en el sitio de expansión:
#definir ADD_A(x) x + avoid add_one ( int * x ) { const int a = 1 ; * x = AGREGAR_A ( * x ); } void add_two ( int * x ) { const int a = 2 ; * x = AGREGAR_A ( * x ); }
Como hemos visto, una de las razones clave del alcance es que ayuda a prevenir colisiones de nombres, al permitir que nombres idénticos se refieran a cosas distintas, con la restricción de que los nombres deben tener alcances separados. A veces esta restricción es inconveniente; Cuando es necesario acceder a muchas cosas diferentes a través de un programa, generalmente todas necesitan nombres con alcance global, por lo que se requieren diferentes técnicas para evitar colisiones de nombres.
Para abordar esto, muchos idiomas ofrecen mecanismos para organizar nombres globales. Los detalles de estos mecanismos y los términos utilizados dependen del idioma; pero la idea general es que a un grupo de nombres se le puede dar un nombre (un prefijo) y, cuando sea necesario, se puede hacer referencia a una entidad mediante un nombre calificado que consista en el nombre más el prefijo. Normalmente, dichos nombres tendrán, en cierto sentido, dos conjuntos de ámbitos: un ámbito (normalmente el ámbito global) en el que el nombre calificado es visible, y uno o más ámbitos más restringidos en los que el nombre no calificado (sin el prefijo) es visible como Bueno. Y normalmente estos grupos pueden organizarse ellos mismos en grupos; es decir, se pueden anidar .
Aunque muchos idiomas apoyan este concepto, los detalles varían mucho. Algunos lenguajes tienen mecanismos, como los espacios de nombres en C++ y C# , que sirven casi exclusivamente para permitir que los nombres globales se organicen en grupos. Otros lenguajes tienen mecanismos, como paquetes en Ada y estructuras en Standard ML , que combinan esto con el propósito adicional de permitir que algunos nombres sean visibles solo para otros miembros de su grupo. Y los lenguajes orientados a objetos a menudo permiten que clases u objetos únicos cumplan este propósito (tengan o no también un mecanismo para el cual este sea el propósito principal). Además, los idiomas a menudo combinan estos enfoques; por ejemplo, los paquetes de Perl son muy similares a los espacios de nombres de C++, pero opcionalmente funcionan como clases para programación orientada a objetos; y Java organiza sus variables y funciones en clases, pero luego organiza esas clases en paquetes tipo Ada.
A continuación se presentan reglas de alcance para idiomas representativos.
En C, el alcance se conoce tradicionalmente como vinculación o visibilidad , particularmente para las variables. C es un lenguaje de alcance léxico con alcance global (conocido como enlace externo ), una forma de alcance de módulo o alcance de archivo (conocido como enlace interno ) y alcance local (dentro de una función); dentro de una función, los alcances se pueden anidar aún más a través del alcance del bloque. Sin embargo, el estándar C no admite funciones anidadas.
La vida útil y la visibilidad de una variable están determinadas por su clase de almacenamiento . Hay tres tipos de vidas útiles en C: estática (ejecución de programa), automática (ejecución de bloque, asignada en la pila) y manual (asignada en el montón). Solo las variables estáticas y automáticas son compatibles y el compilador las maneja, mientras que la memoria asignada manualmente debe rastrearse manualmente en diferentes variables. Hay tres niveles de visibilidad en C: vínculo externo (global), vínculo interno (aproximadamente archivo) y alcance del bloque (que incluye funciones); Los alcances de los bloques se pueden anidar y es posible utilizar diferentes niveles de vinculación interna mediante el uso de inclusiones. El enlace interno en C es la visibilidad a nivel de unidad de traducción , es decir, un archivo fuente después de ser procesado por el preprocesador de C , incluyendo en particular todas las inclusiones relevantes.
Los programas C se compilan como archivos objeto separados , que luego se vinculan a un ejecutable o biblioteca a través de un vinculador . Por lo tanto, la resolución de nombres se divide entre el compilador, que resuelve nombres dentro de una unidad de traducción (más vagamente, "unidad de compilación", pero este es un concepto diferente), y el vinculador, que resuelve nombres entre unidades de traducción; consulte el enlace para obtener más información.
En C, las variables con alcance de bloque entran en contexto cuando se declaran (no en la parte superior del bloque), salen de contexto si se llama a cualquier función (no anidada) dentro del bloque, vuelven al contexto cuando la función regresa, y salir de contexto al final del bloque. En el caso de las variables locales automáticas, también se asignan en la declaración y se desasignan al final del bloque, mientras que para las variables locales estáticas, se asignan en la inicialización del programa y se desasignan al finalizar el programa.
El siguiente programa demuestra una variable con alcance de bloque que entra en contexto a mitad del bloque y luego sale del contexto (y, de hecho, se desasigna) cuando finaliza el bloque:
#incluir <stdio.h> int principal ( vacío ) { char x = 'm' ; printf ( "%c \n " , x ); { printf ( "%c \n " , x ); carácter x = 'b' ; printf ( "%c \n " , x ); } printf ( "%c \n " , x ); }
El programa genera:
metrometrobmetro
Hay otros niveles de alcance en C. [19] Los nombres de variables utilizados en un prototipo de función tienen visibilidad del prototipo de función y un contexto de salida al final del prototipo de función. Dado que no se utiliza el nombre, esto no es útil para la compilación, pero puede resultar útil para la documentación. Los nombres de etiquetas para la declaración GOTO tienen alcance de función.
Todas las variables que pretendemos utilizar en un programa deben haber sido declaradas con su especificador de tipo en un punto anterior del código, como hicimos en el código anterior al inicio del cuerpo de la función main cuando declaramos que a, b, y el resultado fueron de tipo int. Una variable puede ser de alcance global o local. Una variable global es una variable declarada en el cuerpo principal del código fuente, fuera de todas las funciones, mientras que una variable local es una declarada dentro del cuerpo de una función o bloque.
Las versiones modernas permiten un alcance léxico anidado.
Swift tiene una regla similar para ámbitos con C++, pero contiene diferentes modificadores de acceso.
Go tiene un alcance léxico mediante bloques. [3]
Java tiene un alcance léxico.
Una clase Java tiene varios tipos de variables: [20]
En general, un conjunto de corchetes define un alcance particular, pero las variables en el nivel superior dentro de una clase pueden diferir en su comportamiento dependiendo de las palabras clave modificadoras utilizadas en su definición. La siguiente tabla muestra el acceso a los miembros permitido por cada modificador. [21]
JavaScript tiene reglas de alcance simples , [22] pero las reglas de inicialización de variables y resolución de nombres pueden causar problemas, y el uso generalizado de cierres para devoluciones de llamadas significa que el contexto léxico de una función cuando se define (que se usa para la resolución de nombres) puede ser muy diferente de el contexto léxico cuando se llama (lo cual es irrelevante para la resolución de nombres). Los objetos JavaScript tienen resolución de nombres para propiedades, pero este es un tema aparte.
JavaScript tiene un alcance léxico [23] anidado en el nivel de función, siendo el contexto global el contexto más externo. Este alcance se utiliza tanto para variables como para funciones (es decir, declaraciones de funciones, a diferencia de variables de tipo de función ). [24] El alcance del bloque con las palabras clave let
y const
es estándar desde ECMAScript 6. El alcance del bloque se puede producir envolviendo todo el bloque en una función y luego ejecutándola; esto se conoce como patrón de expresión de función inmediatamente invocada (IIFE).
Si bien el alcance de JavaScript es simple (léxico, a nivel de función), las reglas de inicialización y resolución de nombres asociadas son causa de confusión. En primer lugar, la asignación a un nombre que no está dentro del alcance crea de forma predeterminada una nueva variable global, no una local. En segundo lugar, para crear una nueva variable local se debe utilizar la var
palabra clave; Luego, la variable se crea en la parte superior de la función, con valor undefined
y a la variable se le asigna su valor cuando se alcanza la expresión de asignación:
Esto se conoce como elevación de variables [26] : la declaración, pero no la inicialización, se eleva a la parte superior de la función. En tercer lugar, acceder a las variables antes de la inicialización produce undefined
errores de sintaxis, en lugar de errores de sintaxis. En cuarto lugar, para las declaraciones de funciones, la declaración y la inicialización se elevan a la parte superior de la función, a diferencia de la inicialización de variables. Por ejemplo, el siguiente código produce un cuadro de diálogo con salidaindefinido, a medida que se eleva la declaración de la variable local, se oculta la variable global, pero la inicialización no, por lo que la variable no está definida cuando se usa:
un = 1 ; función f () { alerta ( a ); var a = 2 ; } f ();
Además, como las funciones son objetos de primera clase en JavaScript y con frecuencia se asignan como devoluciones de llamada o se devuelven desde funciones, cuando se ejecuta una función, la resolución del nombre depende de dónde se definió originalmente (el contexto léxico de la definición), no del léxico. contexto o contexto de ejecución donde se llama. Los alcances anidados de una función particular (desde la más global hasta la más local) en JavaScript, particularmente de un cierre, utilizado como devolución de llamada, a veces se denominan cadena de alcance , por analogía con la cadena de prototipo de un objeto.
Los cierres se pueden producir en JavaScript mediante el uso de funciones anidadas, ya que las funciones son objetos de primera clase. [27] Devolver una función anidada a partir de una función adjunta incluye las variables locales de la función adjunta como el contexto léxico (no local) de la función devuelta, lo que produce un cierre. Por ejemplo:
function newCounter () { // devuelve un contador que se incrementa al llamar (comenzando en 0) // y que devuelve su nuevo valor var a = 0 ; var b = función () { a ++ ; devolver un ; }; devolver b ; } c = nuevoContador (); alerta ( c () + ' ' + c ()); // salidas "1 2"
Los cierres se usan con frecuencia en JavaScript, debido a que se usan para devoluciones de llamada. De hecho, cualquier enlace de una función en el contexto local como devolución de llamada o devolverla desde una función crea un cierre si hay variables independientes en el cuerpo de la función (con el contexto del cierre basado en los alcances anidados del contexto léxico actual). , o "cadena de alcance"); esto puede ser accidental. Al crear una devolución de llamada basada en parámetros, los parámetros deben almacenarse en un cierre; de lo contrario, se creará accidentalmente un cierre que haga referencia a las variables en el contexto adjunto, que puede cambiar. [28]
La resolución de nombres de propiedades de objetos JavaScript se basa en la herencia en el árbol prototipo (una ruta a la raíz del árbol se llama cadena de prototipos ) y está separada de la resolución de nombres de variables y funciones.
Los dialectos Lisp tienen varias reglas de alcance.
El Lisp original usaba alcance dinámico; Fue Scheme , inspirado en ALGOL , el que introdujo el alcance estático (léxico) a la familia Lisp.
Maclisp usó alcance dinámico por defecto en el intérprete y alcance léxico por defecto en el código compilado, aunque el código compilado podría acceder a enlaces dinámicos mediante el uso de SPECIAL
declaraciones para variables particulares. [29] Sin embargo, Maclisp trató el enlace léxico más como una optimización de lo que uno esperaría en los lenguajes modernos, y no vino con la característica de cierre que uno podría esperar del alcance léxico en los Lisps modernos. Una operación separada, *FUNCTION
, estaba disponible para solucionar con cierta torpeza parte de ese problema. [30]
Common Lisp adoptó el alcance léxico de Scheme , [31] al igual que Clojure .
ISLISP tiene alcance léxico para variables ordinarias. También tiene variables dinámicas, pero en todos los casos están marcadas explícitamente; deben definirse mediante una defdynamic
forma especial, estar vinculados por una dynamic-let
forma especial y acceder a ellos mediante una dynamic
forma especial explícita. [32]
Algunos otros dialectos de Lisp, como Emacs Lisp , todavía usan alcance dinámico de forma predeterminada. Emacs Lisp ahora tiene alcance léxico disponible por búfer. [33]
Para las variables, Python tiene alcance de función, alcance de módulo y alcance global. Los nombres ingresan al contexto al comienzo de un alcance (función, módulo o alcance global) y salen del contexto cuando se llama a una función no anidada o el alcance finaliza. Si se utiliza un nombre antes de la inicialización de la variable, esto genera una excepción de tiempo de ejecución. Si simplemente se accede a una variable (no se le asigna), la resolución de nombres sigue la regla LEGB (Local, Enclosing, Global, Incorporada) que resuelve los nombres en el contexto relevante más limitado. Sin embargo, si se asigna una variable, de forma predeterminada se declara una variable cuyo alcance comienza al inicio del nivel (función, módulo o global), no en la asignación. Ambas reglas se pueden anular con una declaración global
o nonlocal
(en Python 3) antes de su uso, lo que permite acceder a variables globales incluso si hay una variable no local enmascarante y asignar variables globales o no locales.
Como ejemplo simple, una función resuelve una variable al alcance global:
>>> def f (): ... imprimir ( x ) ... >>> x = "global" >>> f () global
Tenga en cuenta que x
se define antes de f
que se llame, por lo que no se genera ningún error, aunque se define después de su referencia en la definición de f
. Léxicamente, esta es una referencia directa , que está permitida en Python.
Aquí la asignación crea una nueva variable local, que no cambia el valor de la variable global:
>>> def f (): ... x = "f" ... imprimir ( x ) ... >>> x = "global" >>> imprimir ( x ) global >>> f () f > >> imprimir ( x ) global
La asignación a una variable dentro de una función hace que se declare local a la función, por lo tanto, su alcance es toda la función y, por lo tanto, usarla antes de esta asignación genera un error. Esto difiere de C, donde el alcance de la variable local comienza en su declaración. Este código genera un error:
>>> def f (): ... print ( x ) ... x = "f" ... >>> x = "global" >>> f () Rastreo (última llamada más reciente): Archivo " <stdin>" , línea 1 , en <módulo> Archivo "<stdin>" , línea 2 , en f UnboundLocalError : variable local 'x' a la que se hace referencia antes de la asignación
Las reglas de resolución de nombres predeterminadas se pueden anular con las palabras clave global
o nonlocal
(en Python 3). En el siguiente código, la global x
declaración en g
significa que x
se resuelve en la variable global. Por lo tanto, se puede acceder a él (como ya se ha definido) y la asignación se asigna a la variable global, en lugar de declarar una nueva variable local. Tenga en cuenta que no global
se necesita ninguna declaración f
; dado que no se asigna a la variable, de forma predeterminada se resuelve en la variable global.
>>> def f (): ... imprimir ( x ) ... >>> def g (): ... global x ... imprimir ( x ) ... x = "g" ... > >> x = "global" >>> f () global >>> g () global >>> f () g
global
También se puede utilizar para funciones anidadas. Además de permitir la asignación a una variable global, como en una función no anidada, esto también se puede utilizar para acceder a la variable global en presencia de una variable no local:
>>> def f (): ... def g (): ... global x ... print ( x ) ... x = "f" ... g () ... >>> x = "global" >>> f () global
Para funciones anidadas, también existe la nonlocal
declaración, para asignar a una variable no local, similar a usar global
en una función no anidada:
>>> def f (): ... def g (): ... no local x # Solo Python 3 ... x = "g" ... x = "f" ... g () ... imprimir ( x ) ... >>> x = "global" >>> f () g >>> imprimir ( x ) global
R es un lenguaje de alcance léxico, a diferencia de otras implementaciones de S donde los valores de las variables libres están determinados por un conjunto de variables globales, mientras que en R están determinados por el contexto en el que se creó la función. [34] Se puede acceder a los contextos de alcance utilizando una variedad de funciones (como parent.frame()
) que pueden simular la experiencia del alcance dinámico si el programador lo desea.
No hay alcance de bloque:
un <- 1 { un <- 2 } mensaje ( un ) ## 2
Las funciones tienen acceso al alcance en el que fueron creadas:
a <- 1 f <- función () { mensaje ( a ) } f () ## 1
Las variables creadas o modificadas dentro de una función permanecen allí:
a <- 1 f <- función () { mensaje ( a ) a <- 2 mensaje ( a ) } f () ## 1 ## 2 mensaje ( a ) ## 1
Las variables creadas o modificadas dentro de una función permanecen allí a menos que se solicite explícitamente la asignación al alcance adjunto:
a <- 1 f <- función () { mensaje ( a ) a <<- 2 mensaje ( a ) } f () ## 1 ## 2 mensaje ( a ) ## 2
Aunque R tiene alcance léxico de forma predeterminada, los alcances de las funciones se pueden cambiar:
a <- 1 f <- function () { mensaje ( a ) } my_env <- new.env () my_env $ a <- 2 f () ## 1 entorno ( f ) <- my_env f () ## 2
No se puede acceder a las variables definidas dentro de una función desde ningún lugar fuera de la función, porque la variable está definida solo en el alcance de la función. Sin embargo, una función puede acceder a todas las variables y funciones definidas dentro del alcance en el que está definida.
Si la variable que se va a vincular se ha declarado como especial, el enlace se compila como código para imitar la forma en que el intérprete vincula las variables.
está destinado a ayudar a resolver el "
problema funarg
", sin embargo, sólo funciona en algunos casos fáciles.
*FUNCTION
*FUNCTION
MacLisp mejoró la noción de variables especiales de Lisp 1.5... Las principales influencias en Common Lisp fueron Lisp Machine Lisp, MacLisp, NIL, S-1 Lisp, Spice Lisp y Scheme.
Los enlaces dinámicos se establecen y se accede a ellos mediante un mecanismo independiente (es decir,
,
y
).
defdynamic
dynamic-let
dynamic
Emacs 24 tiene enlace léxico opcional, que se puede habilitar por búfer.