stringtranslate.com

Alcance (informática)

En programación informática , 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 el nombre se puede usar para hacer referencia a la entidad. En otras partes del programa, el nombre puede hacer referencia a una entidad diferente (puede tener un enlace diferente) o a nada en absoluto (puede no estar enlazado). El alcance ayuda a prevenir colisiones de nombres al permitir que el mismo nombre haga referencia a diferentes objetos, siempre que los nombres tengan alcances separados. El alcance de un enlace de nombre también se conoce como la visibilidad de una entidad, particularmente en la literatura más antigua o más técnica; esto es en relación con la entidad referenciada, 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 dado de un programa, al 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 ámbito 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 ámbito dinámico . Ambos términos son algo engañosos (utilizan incorrectamente términos técnicos, como se analiza en la definición), pero la distinción en sí es precisa y precisa, y estos son los términos estándar respectivos. El ámbito léxico es el foco principal de este artículo, y el ámbito dinámico se entiende por contraste con el ámbito 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 en el enmascaramiento de nombres , las declaraciones hacia adelante y el hoisting , mientras que surgen otras considerablemente más sutiles con variables no locales , particularmente en los cierres .

Definición

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 una 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. A continuación, se presentan especificaciones representativas del lenguaje:

ALGOL 60 (1960) [1]
Se distinguen los siguientes tipos de magnitudes: variables simples, matrices, etiquetas, conmutadores y procedimientos. El ámbito de una magnitud es el conjunto de instrucciones y expresiones en las que es válida la declaración del identificador asociado a esa magnitud.
C (2007) [2]
Un identificador puede denotar un objeto, una función, una etiqueta o un miembro de una estructura, unión o enumeración, un nombre de definición de tipo , un nombre de etiqueta, un nombre de macro o un parámetro de macro. El mismo identificador puede denotar diferentes entidades en diferentes puntos del programa. [...] Para cada entidad diferente que designa un identificador, el identificador es visible (es decir, se puede usar) solo dentro de una región del texto del programa llamada su alcance.
Vete (2013) [3]
Una declaración vincula un identificador que no está en blanco a una constante, tipo, variable, función, etiqueta o paquete. [...] El alcance de un identificador declarado es la extensión del texto fuente en el que el identificador denota la constante, tipo, variable, función, etiqueta o paquete especificado.

Más comúnmente, "alcance" se refiere a cuando un nombre dado puede referirse a una variable dada (cuando una declaración tiene efecto), pero también puede aplicarse a otras entidades, como funciones, tipos, clases, etiquetas , constantes y enumeraciones.

Ámbito léxico vs. ámbito dinámico

Una distinción fundamental en el ámbito es lo que significa "parte de un programa". En lenguajes con ámbito léxico (también llamado ámbito estático ), la resolución de nombres depende de la ubicación en el código fuente y del contexto léxico (también llamado contexto estático ), que se define por el lugar donde se define la variable o función nombrada. Por el contrario, en lenguajes con ámbito dinámico, la resolución de nombres depende del estado del programa cuando se encuentra el nombre, que está determinado por el contexto de ejecución (también llamado contexto de tiempo de ejecución , contexto de llamada o contexto dinámico ). En la práctica, con ámbito 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 ámbito dinámico, un nombre se resuelve buscando en el contexto de ejecución local, luego, si eso falla, buscando en el contexto de ejecución externo, y así sucesivamente, progresando hacia arriba en la pila de llamadas. [4]

La mayoría de los lenguajes modernos utilizan un ámbito léxico para las variables y funciones, aunque en algunos lenguajes se utiliza un ámbito dinámico, en particular en algunos dialectos de Lisp, algunos lenguajes de "scripting" y algunos lenguajes de plantillas . [c] Perl 5 ofrece tanto un ámbito léxico como dinámico. Incluso en lenguajes con ámbito léxico, el ámbito para los cierres puede resultar confuso para los no iniciados, [ cita requerida ] ya que estos dependen del contexto léxico donde se define el cierre, no donde se lo 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, solo se puede determinar en tiempo de ejecución y, por lo tanto, se conoce como enlace tardío .

Conceptos relacionados

En la programación orientada a objetos , el envío dinámico selecciona un método de objeto en tiempo de ejecución, aunque el hecho de que la vinculación de nombres real se realice en tiempo de compilación o de ejecución depende del lenguaje. El alcance dinámico de facto es común en los lenguajes de macros , que no realizan directamente la resolución de nombres, sino que se expanden en el lugar.

Algunos frameworks de programación como AngularJS usan el término "ámbito" para significar algo completamente diferente de cómo se usa en este artículo. En esos frameworks, el ámbito es simplemente un objeto del lenguaje de programación que usan ( JavaScript en el caso de AngularJS) que el framework usa de ciertas maneras para emular el ámbito dinámico en un lenguaje que usa ámbito léxico para sus variables. Esos ámbitos de AngularJS pueden estar en contexto o no (usando el significado habitual del término) en cualquier parte dada del programa, siguiendo las reglas habituales de ámbito de variable del lenguaje como cualquier otro objeto, y usando sus propias reglas de herencia y transclusión . En el contexto de AngularJS, a veces se usa el término "$ámbito" (con un signo de dólar) para evitar confusiones, pero las guías de estilo a menudo desaconsejan usar el signo de dólar en los nombres de las variables. [5]

Usar

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 para el 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 interrumpe una parte no relacionada.

Descripción general

Cuando se habla de alcance, hay tres conceptos básicos: alcance, extensión y contexto. "Alcance" y "contexto" en particular se confunden con frecuencia: alcance es una propiedad de un enlace de nombre, mientras que contexto es una propiedad de una parte de un programa, que es una porción de código fuente ( contexto léxico o contexto estático ) o una porción de 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 consiste en contexto léxico (en el punto de ejecución actual) más 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 nombre, y en un punto en la ejecución los enlaces de nombre están "en contexto" o "no en contexto", por lo tanto, los enlaces de nombre "entran en contexto" o "salen de contexto" a medida que la ejecución del programa entra o sale del alcance. [f] Sin embargo, en la práctica el uso es mucho más laxo.

El alcance es un concepto de nivel de código fuente y una propiedad de los enlaces de nombres, en particular los enlaces 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 salvaje , ya que no está definido. Sin embargo, como las variables no se destruyen hasta que salen de contexto, el análogo de un puntero colgante no existe.

Para entidades como las variables, el ámbito es un subconjunto del tiempo de vida (también conocido como extensión ): un nombre solo puede hacer referencia 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 dado, en cuyo caso no está en contexto (el programa está "fuera del alcance del nombre"). En otros casos, el "tiempo de vida" es irrelevante: una etiqueta (posición nombrada en el código fuente) tiene un tiempo de vida idéntico al programa (para lenguajes compilados estáticamente), pero puede estar en contexto o no en un punto dado en el programa, y ​​lo mismo para las 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 un tiempo de vida 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 entre lenguajes. Dado un nombre, el lenguaje (adecuadamente, el compilador o intérprete) verifica todas las entidades que están en contexto para encontrar 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, Enclosing, Global, Built-in) de Python: los nombres se resuelven implícitamente en el contexto relevante más estrecho. En algunos casos, la resolución de nombres se puede especificar explícitamente, como por las palabras clave globaland nonlocalen Python; en otros casos, las reglas predeterminadas no se pueden anular.

Cuando dos nombres idénticos se encuentran en contexto al mismo tiempo, haciendo referencia a entidades diferentes, se dice que se está produciendo un enmascaramiento de nombres , en el que el nombre de mayor prioridad (normalmente el más interno) está "enmascarando" al nombre de menor prioridad. A nivel de variables, esto se conoce como " enmascaramiento de variables " . Debido a la posibilidad de que se produzcan errores lógicos a causa del enmascaramiento, algunos lenguajes lo desautorizan o desalientan, lo que genera un error o una advertencia en tiempo de compilación o de ejecución.

Varios lenguajes de programación tienen distintas reglas de alcance para distintos tipos de declaraciones y nombres. Dichas 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 no vinculada no tiene una semántica bien definida y puede resultar en un comportamiento indefinido , similar a hacer referencia a un puntero colgante ; y las declaraciones o nombres utilizados fuera de su alcance generarán errores de sintaxis .

Los alcances frecuentemente están vinculados a otras construcciones del lenguaje y se determinan implícitamente, pero muchos lenguajes también ofrecen construcciones específicamente para controlar el alcance.

Niveles de alcance

El alcance puede variar desde una sola expresión hasta todo el programa, con muchas gradaciones posibles entre ambas. 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 para el módulo) pero no visibles fuera de él. Dentro de una función, algunos lenguajes, como C, permiten el alcance de bloque para restringir el alcance a un subconjunto de una función; otros, en particular los lenguajes funcionales, permiten el alcance de expresión, para restringir el alcance a una sola expresión. Otros ámbitos incluyen el alcance de archivo (en particular en C), que se comporta de manera similar al alcance de módulo, y el alcance de bloque fuera de las funciones (en particular en Perl).

Un problema sutil es exactamente cuándo comienza y termina un ámbito. En algunos lenguajes, como C, el ámbito de un nombre comienza en la declaración del nombre y, por lo tanto, los diferentes nombres declarados dentro de un bloque determinado pueden tener diferentes ámbitos. Esto requiere declarar funciones antes de su uso, aunque no necesariamente definirlas, y requiere una declaración hacia adelante en algunos casos, en particular para la recursión mutua. En otros lenguajes, como Python, el ámbito de un nombre comienza al comienzo del bloque relevante donde se declara el nombre (como el comienzo de una función), independientemente de dónde se defina, por lo que todos los nombres dentro de un bloque determinado tienen el mismo ámbito. En JavaScript, el ámbito de un nombre declarado con leto constcomienza en la declaración del nombre, y el ámbito de un nombre declarado con varcomienza al comienzo de la función donde se declara el nombre, lo que se conoce como elevación de variable . 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 varse pueden usar en toda la función porque están implícitamente vinculados al valor undefined.

Ámbito de expresión

El alcance de un enlace de nombre es una expresión , que se conoce como alcance de expresión . El alcance de expresión está disponible en muchos lenguajes, especialmente lenguajes funcionales que ofrecen una característica llamada expresiones let que permiten que el alcance de una declaración sea una sola expresión. 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 evalúa a , utilizando una variable temporal nombrada para evitar llamar dos veces. Algunos lenguajes con alcance de bloque se aproximan a esta funcionalidad al ofrecer una sintaxis para que un bloque se incruste en una expresión; por ejemplo, la expresión de Standard ML mencionada anteriormente podría escribirse en Perl como , o en GNU C como .let val x = f() in x * x end144xf()do { my $x = f(); $x * $x }({ int x = f(); x * x; })

En Python, las variables auxiliares en expresiones generadoras y comprensiones de listas (en Python 3) tienen alcance de expresión.

En C, los nombres de las variables en un prototipo de función tienen un ámbito de expresión, conocido en este contexto como ámbito de 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 solo ficticios, por lo que a menudo se omiten, aunque se pueden usar para generar documentación, por ejemplo.

Ámbito de bloque

El alcance de un enlace de nombre es un bloque , que se conoce como alcance de bloque . El alcance de bloque está disponible en muchos lenguajes de programación estructurados en bloques, pero no en todos. Esto comenzó con ALGOL 60 , donde "[c]ada declaración... es válida solo para ese bloque", [6] y hoy en día se asocia particularmente 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 Perl, el bloque puede no estar dentro de una función.

entero sin signo suma_de_cuadrados ( const entero sin signo N ) { entero sin signo ret = 0 ; para ( entero sin signo n = 1 ; n <= N ; n ++ ) { constante entero sin signo n_cuadrado = n * n ; ret += n_cuadrado ; } return ret ; }                                    

Un ejemplo representativo del uso del ámbito de bloque es el código C que se muestra aquí, donde dos variables están limitadas al ámbito del bucle: la variable de 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 ámbito de la función que solo son relevantes para un bloque en particular; por ejemplo, esto evita errores donde la variable genérica de bucle i ya se ha establecido accidentalmente en otro valor. En este ejemplo, la expresión n * ngeneralmente no se asignaría a una variable auxiliar, y el cuerpo del bucle simplemente se escribiría, ret += n * npero en ejemplos más complicados, las variables auxiliares son útiles.

Los bloques se utilizan principalmente para el control del flujo, como en los bucles if, while y for, y en estos casos el ámbito de bloque significa que el ámbito de la variable depende de la estructura del flujo de ejecución de una función. Sin embargo, los lenguajes con ámbito de bloque normalmente también permiten el uso de bloques "desnudos", cuyo único propósito es permitir un control detallado del ámbito de la variable. Por ejemplo, se puede definir una variable auxiliar en un bloque, luego usarla (por ejemplo, agregarla a una variable con ámbito de función) y descartarla cuando finaliza el bloque, o se puede incluir un bucle while en un bloque que inicialice las variables utilizadas dentro del bucle que solo se deben inicializar 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 ámbito de bloque se pueden declarar no solo dentro del cuerpo del bloque, sino también dentro de la declaración de control, si la hay. 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 ámbito de todo el cuerpo de la función. Esto se utiliza 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 ámbito de bloque se puede utilizar para hacer sombra. En este ejemplo, dentro del bloque la variable auxiliar también podría haberse llamado n , haciendo sombra al nombre del parámetro, pero esto se considera un estilo deficiente debido al potencial de errores. Además, algunos descendientes de C, como Java y C#, a pesar de tener soporte para el ámbito de bloque (en el que una variable local puede quedar fuera 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 daría como resultado un error de sintaxis y una de las n variables tendría que ser renombrada.

Si se utiliza un bloque para establecer el valor de una variable, el ámbito del bloque requiere que la variable se declare fuera del bloque. Esto complica el uso de sentencias condicionales con asignación simple . Por ejemplo, en Python, que no utiliza el ámbito del bloque, se puede inicializar una variable de la siguiente manera:

si  c :  a  =  "foo" de lo contrario :  a  =  ""

donde aes accesible después de la ifdeclaración.

En Perl, que tiene alcance de bloque, esto requiere declarar la variable antes del bloque:

mi $a ; si ( c ) { $a = 'foo' ; } de lo contrario { $a = '' ; }           

A menudo, esto se reescribe mediante una asignación múltiple, inicializando la variable con 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 bloqueo, pero esto en general no es posible para asignaciones de múltiples variables y es difícil de leer para una 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 estas operaciones sean exitosas.

{ mi $contador = 0 ; sub increment_counter { return ++ $contador ; } }          

Algunos lenguajes permiten que el concepto de ámbito de bloque se aplique, en distintos grados, fuera de una función. Por ejemplo, en el fragmento de código de Perl de la derecha, $counteres un nombre de variable con ámbito de bloque (debido al uso de la mypalabra clave), mientras que increment_counteres un nombre de función con ámbito global. Cada llamada a increment_counteraumentará el valor de $counteren 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.

Alcance de la función

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 termina (que sale de contexto) cuando la función regresa. En la mayoría de los casos, el tiempo de vida de la variable es la duración de la llamada a la función: es una variable automática , creada cuando la función comienza (o se declara la variable), destruida cuando la función regresa, mientras que el alcance de la variable está dentro de la 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 el tiempo de vida de la variable es todo el tiempo de vida 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 termina, como 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 ámbito léxico, una variable con ámbito 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 a entrar en contexto cuando la función retorna; 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 ámbito 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, solo salen de contexto cuando termina la función que las define y, por lo tanto, las variables locales están en el contexto de la función en la que están definidas y todas las funciones llamadas . En lenguajes con ámbito léxico y funciones anidadas , las variables locales están en contexto para las funciones anidadas, ya que estas están dentro del mismo contexto léxico, pero no para otras funciones que no están anidadas léxicamente. Una variable local de una función envolvente se conoce como una variable no local para la función anidada. El ámbito de función también se aplica a funciones anónimas .

def  cuadrado ( n ):  devuelve  n  *  ndef  suma_de_cuadrados ( n ):  total  =  0  i  =  0  mientras  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: squarey sum_of_squares. squarecalcula el cuadrado de un número; sum_of_squarescalcula la suma de todos los cuadrados hasta un número. (Por ejemplo, square(4)is 4 2  =  16, y sum_of_squares(4)is 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 variables n son completamente independientes y no están relacionadas, a pesar de tener el mismo nombre, porque son variables locales con alcance léxico y alcance de función: el alcance de cada una es su propia función léxicamente independiente y, por lo tanto, no se superponen. Por lo tanto, sum_of_squaresse puede llamar sin que se altere squaresu propia nsum_of_squares . De manera similar, tiene variables llamadas total e i ; estas variables, debido a su alcance limitado, no interferirán con ninguna variable llamada total o i que pueda pertenecer a cualquier otra función. En otras palabras, no hay riesgo de una 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 se escribiera un fragmento similar en un lenguaje con ámbito dinámico, la n en la función que realiza la llamada permanecería en contexto en la función llamada (los ámbitos se superpondrían) y quedaría enmascarada ("ensombrecida") 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 se pueden crear 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 no vinculadas en la definición de la función, que se resuelven en variables en un contexto envolvente) crea un cierre , ya que no solo se debe devolver la función en sí, sino también su contexto (de variables) y luego potencialmente llamarla en un contexto diferente. Esto requiere mucho más soporte del compilador y puede complicar el análisis del programa.

Ámbito del archivo

El alcance de un enlace de nombre es un archivo, que se conoce como alcance de archivo . El alcance de 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 del 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 include, que agregan variables y funciones al contexto interno y pueden llamar a otras declaraciones include, puede ser 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_squarestiene alcance global (en C, enlace externo). Si se agrega statica la firma de la función, se obtendrá un alcance de archivo (enlace interno).

Alcance del módulo

El alcance de un enlace de nombre es un módulo, que se conoce como alcance de módulo . El alcance de módulo está disponible en lenguajes de programación modular donde los módulos (que pueden abarcar varios archivos) son la unidad básica de un programa complejo, ya que permiten ocultar información y exponer una interfaz limitada. El alcance de 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] se proporciona una estructura similar mediante la jerarquía de clases, 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 de distribución dinámica en lugar de resolución de nombres y alcance, aunque a menudo desempeñan papeles análogos. 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 privado convencional) es una elección del programador.

Alcance global

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 , se consideran con frecuencia 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 se considera preferible el alcance de función o el alcance de bloque. Sin embargo, el alcance global se usa típicamente (dependiendo del lenguaje) para varios otros tipos de nombres, como nombres de funciones, nombres de clases y nombres de otros tipos de datos . En estos casos, se usan mecanismos como los espacios de nombres para evitar colisiones.

Ámbito léxico vs. ámbito dinámico

El uso de variables locales (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 ámbito léxico (o ámbito léxico ; también llamado ámbito estático o alcance estático ), si el ámbito de una variable nombre es una función determinada, entonces su ámbito es el texto del programa de la definición de la función: dentro de ese texto, la variable nombre existe y está vinculada al valor de la variable, pero fuera de ese texto, la variable nombre no existe. Por el contrario, en el ámbito dinámico (o ámbito dinámico ), si el ámbito de una variable nombre es una función determinada, entonces su ámbito es el período de tiempo durante el cual la función se está ejecutando: mientras la función se está ejecutando, la variable nombre existe y está vinculada a su valor, pero después de que la función retorna, la variable nombre no existe. Esto significa que si función invocaf una función definida por separado g, entonces bajo el ámbito léxico, función gno tiene acceso a las variables locales de (asumiendo que el texto de no está dentro del texto de ), mientras que bajo el ámbito dinámico, función tiene acceso a las variables locales de (ya que se invoca durante la invocación de ).fgfg fgf

$ # lenguaje bash $ x = 1 $ function  g () { echo $x ; x = 2 ; } $ function f () { local x = 3 ; g ; } $ f # ¿esto imprime 1 o 3? 3 $ echo $x # ¿esto imprime 1 o 2? 1                  

Considere, 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 lo 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 a . La cuarta línea, , llama a . La quinta línea, , imprime el valor actual de .x=1x1function g() { echo $x ; x=2 ; }gxx2function f() { local x=3 ; g ; }fx3gffecho $xx

Entonces, ¿qué imprime exactamente este programa? Depende de las reglas de alcance. Si el lenguaje de este programa es uno que utiliza alcance léxico, entonces gimprime y modifica la variable global x(porque gse define fuera de f), por lo que el programa imprime 1y luego 2. Por el contrario, si este lenguaje utiliza alcance dinámico, entonces gimprime y modifica fla variable local de x(porque gse llama desde dentro fde ), por lo que el programa imprime 3y luego 1. (De hecho, el lenguaje del programa es Bash , que utiliza alcance dinámico; por lo que el programa imprime 3y luego 1. Si el mismo código se ejecutara con ksh93 que utiliza alcance léxico, los resultados serían diferentes).

Ámbito léxico

Con el ámbito léxico , un nombre siempre se refiere 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 estático del programa, este tipo de ámbito también se denomina ámbito estático . El ámbito léxico es estándar en todos los lenguajes basados ​​en ALGOL, como Pascal , Modula-2 y Ada , así como en los 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 diferentes tipos de limitaciones. El ámbito estático permite al programador razonar sobre referencias a objetos como parámetros, variables, constantes, tipos, funciones, etc. como simples sustituciones de nombres. Esto hace que sea mucho más fácil hacer código modular y razonar sobre él, ya que la estructura de nombres local se puede entender de forma aislada. Por el contrario, el ámbito dinámico obliga al programador a anticipar todos los posibles contextos de ejecución 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 ; inicio (*ámbito A+B+C*) fin ;       (*alcance A+B*) fin ;  (*alcance A*) fin .

Por ejemplo, Pascal tiene un alcance léxico. Considere el fragmento del programa Pascal a la derecha. La variable Ies visible en todos los puntos, porque nunca está oculta por otra variable del mismo nombre. La charvariable Kes visible solo en el programa principal porque está oculta por la realvariable Kvisible en procedimiento By Csolo. Variable Ltambién es visible solo en procedimiento By Cpero no oculta ninguna otra variable. Variable Msolo es visible en procedimiento Cy, por lo tanto, no es accesible ni desde procedimiento Bni desde el programa principal. Además, procedimiento Ces visible solo en procedimiento By, por lo tanto, no se puede llamar desde el programa principal.

Podría haber otro procedimiento Cdeclarado en el programa fuera del procedimiento B. El lugar en el programa donde Cse menciona " " determina entonces cuál de los dos procedimientos nombrados Crepresenta, por lo que es exactamente análogo al alcance de las variables.

La correcta implementación 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 cierre ). Dependiendo de la implementación y la arquitectura de la computadora , la búsqueda de variables puede volverse ligeramente ineficiente [ cita requerida ] cuando se usan funciones anidadas léxicamente muy profundas , 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 variables locales (inmediatamente), todas las ubicaciones relativas se pueden conocer en tiempo de compilación . Por lo tanto, no se incurre en ninguna sobrecarga al usar ese tipo de función anidada. Lo mismo se aplica a partes particulares de un programa donde no se usan funciones anidadas y, naturalmente, a programas escritos en un lenguaje donde las funciones anidadas no están disponibles (como en el lenguaje C).

Historia

El alcance léxico se utilizó por primera vez a principios de la década de 1960 para el lenguaje imperativo ALGOL 60 y ha sido adoptado por la mayoría de los demás lenguajes imperativos desde entonces. [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 anidadas léxicamente ).

Perl es un lenguaje con alcance dinámico al que luego se le añadió un alcance estático.

El intérprete original de Lisp (1960) utilizaba un 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 , bajo la dirección de John McCarthy ).

Todos los primeros Lisp utilizaban un 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 revisión de la historia y las diferentes implementaciones de Lisp hasta ese momento y una revisión de las características que debería tener una implementación de Common Lisp . En la página 102, leemos:

La mayoría de las implementaciones de LISP son internamente inconsistentes, ya que, por defecto, 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 supone que todas las variables tienen un alcance dinámico, mientras que el compilador supone que todas las variables son locales a menos que se le obligue a suponer lo contrario. Esto se ha hecho por conveniencia y eficiencia, pero puede conducir a errores muy sutiles. La definición de Common LISP evita tales anomalías al exigir explícitamente que el intérprete y el compilador impongan semánticas idénticas a los programas correctos.

Por lo tanto, se requería que las implementaciones de Common LISP tuvieran un 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 se han tomado prestadas de MacLisp, InterLisp o Lisp Machines Lisp): (...) Variables con alcance léxico completo. El llamado " problema FUNARG " [12] [13] se resuelve por completo, tanto en el caso descendente como en el ascendente.

En el mismo año en que se publicó An overview of Common LISP (1982), se habían publicado los diseños iniciales (también de Guy L. Steele Jr.) de un Lisp compilado y con alcance léxico, llamado Scheme , y se estaban intentando implementaciones de compiladores. En ese momento, se temía comúnmente que el alcance léxico en Lisp fuera ineficiente de implementar. En A History of T , [14] Olin Shivers escribe:

Todos los lenguajes Lisp serios que se utilizaban en producción en ese momento tenían un alcance dinámico. Nadie que no hubiera leído con atención la tesis Rabbit [15] (escrita por Guy Lewis Steele Jr. en 1978) creía que el alcance léxico funcionaría; incluso las pocas personas que la habían leído estaban un poco a la expectativa de que esto fuera a funcionar en un uso serio en producción.

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 "Muddle"). [17]

Alcance dinámico

Con un ámbito dinámico , un nombre hace referencia al contexto de ejecución. En términos técnicos, esto significa que cada nombre tiene una pila global de enlaces. Al introducir una variable local con nombre, xse inserta un enlace en la xpila global (que puede haber estado vacía), que se elimina cuando el flujo de control abandona el ámbito. La evaluación xen cualquier contexto siempre da como resultado el enlace superior. Tenga en cuenta que esto no se puede hacer en tiempo de compilación porque la pila de enlaces solo existe en tiempo de ejecución , por lo que este tipo de ámbito se denomina ámbito dinámico .

El alcance dinámico es poco común en los lenguajes modernos. [4]

Generalmente, ciertos bloques se definen para crear enlaces cuya duración es el tiempo de ejecución del bloque; esto añade algunas características de ámbito estático al proceso de ámbito dinámico. Sin embargo, dado que una sección de código puede ser llamada desde muchas ubicaciones y situaciones diferentes, puede ser difícil determinar desde el principio qué enlaces se aplicarán cuando se use una variable (o si existe alguna). Esto puede ser beneficioso; la aplicación del principio del mínimo conocimiento 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 actual (o política) 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 las 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 el ámbito estático o dinámico al definir o redefinir una variable. Algunos ejemplos de lenguajes que utilizan el alcance dinámico son Logo , Emacs Lisp , LaTeX y los lenguajes de 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, verificando cada registro de activación (el marco de 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 nombre/valor. Los pares se insertan en esta pila siempre que se realizan declaraciones y se extraen cuando las variables quedan fuera de contexto. [18] La vinculación superficial es una estrategia alternativa que es considerablemente más rápida, haciendo uso de 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 suponen un orden de último en entrar, primero en salir ( LIFO ) para las vinculaciones para cualquier variable; en la práctica, todas las vinculaciones están ordenadas de esa manera.

Una implementación aún más simple es la representación de variables dinámicas con variables globales simples. La vinculación local se realiza guardando el valor original en una ubicación anónima en la pila que es invisible para el programa. Cuando ese ámbito de vinculación termina, el valor original se restaura desde esta ubicación. De hecho, el ámbito dinámico se originó de esta manera. Las primeras implementaciones de Lisp usaban esta estrategia obvia para implementar variables locales, y la práctica sobrevive en algunos dialectos que todavía se usan, como GNU Emacs Lisp. El ámbito 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 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 una ranura dedicada para su valor global.

El ámbito dinámico proporciona una excelente abstracción para el almacenamiento local de subprocesos , pero si se utiliza 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 de subproceso se utiliza para acceder a la ubicación de memoria local de subproceso (por código generado por el compilador, que sabe qué variables son dinámicas y cuáles son léxicas). Si la clave local de subproceso no existe para el subproceso que realiza la llamada, se utiliza la ubicación global. Cuando una variable está enlazada localmente, el valor anterior se almacena en una ubicación oculta en la pila. El almacenamiento local de subprocesos se crea bajo la clave de la variable y el nuevo valor se almacena allí. Las anulaciones anidadas posteriores de la variable dentro de ese subproceso simplemente guardan y restauran esta ubicación local de subproceso. Cuando finaliza el contexto de la anulación inicial más externa, se elimina la clave local de subproceso, exponiendo la versión global de la variable una vez más a ese subproceso.

Con 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.

Expansión macro

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 como 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 del lugar donde se expanden (lo que se denomina "llamado" de manera general), como si se estuviera produciendo un alcance dinámico.

El preprocesador C , utilizado para la expansión de macros , tiene un alcance dinámico de facto, ya que no realiza la resolución de nombres por sí mismo y es independiente de dónde se define la macro. Por ejemplo, la macro:

#define ADD_A(x) x + a

se expandirá para agregar aa la variable pasada, y este nombre será resuelto más tarde por el compilador en función de dónde ADD_Ase "llame" a la macro (adecuadamente, expandida). Correctamente, el preprocesador de C solo realiza análisis léxico , expandiendo la macro durante la etapa de tokenización, pero no la analiza en un árbol de sintaxis ni realiza resolución de nombres.

Por ejemplo, en el siguiente código, el nombre aen la macro se resuelve (después de la expansión) en la variable local en el sitio de expansión:

#define ADD_A(x) x + avacío agregar_uno ( int * x ) { constante int a = 1 ; * x = ADD_A ( * x ); }           vacío añadir_dos ( int * x ) { constante int a = 2 ; * x = ADD_A ( * x ); }           

Nombres calificados

Como hemos visto, una de las razones principales para el ámbito es que ayuda a evitar colisiones de nombres, al permitir que nombres idénticos hagan referencia a cosas distintas, con la restricción de que los nombres deben tener ámbitos separados. A veces, esta restricción es incómoda; cuando se necesita acceder a muchas cosas diferentes a través de un programa, generalmente todas necesitan nombres con ámbito global, por lo que se requieren diferentes técnicas para evitar colisiones de nombres.

Para solucionar este problema, muchos lenguajes ofrecen mecanismos para organizar nombres globales. Los detalles de estos mecanismos y los términos utilizados dependen del lenguaje, 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 consiste en el nombre más el prefijo. Normalmente, estos 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 estrechos en los que el nombre no calificado (sin el prefijo) también es visible. Y normalmente estos grupos se pueden organizar a su vez en grupos; es decir, se pueden anidar .

Aunque muchos lenguajes admiten este concepto, los detalles varían enormemente. 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 los paquetes en Ada y las 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 las clases u objetos singleton cumplan con este propósito (ya sea que también tengan o no un mecanismo para el cual este sea el propósito principal). Además, los lenguajes a menudo fusionan estos enfoques; por ejemplo, los paquetes de Perl son en gran medida similares a los espacios de nombres de C++, pero opcionalmente se duplican como clases para la programación orientada a objetos; y Java organiza sus variables y funciones en clases, pero luego organiza esas clases en paquetes similares a Ada.

Por idioma

A continuación se detallan las reglas de alcance para los idiomas representativos.

do

En C, el ámbito se conoce tradicionalmente como enlace o visibilidad , en particular para las variables. C es un lenguaje con ámbito léxico con alcance global (conocido como enlace externo ), una forma de ámbito de módulo o ámbito de archivo (conocido como enlace interno ) y ámbito local (dentro de una función); dentro de una función, los ámbitos se pueden anidar a través del ámbito de bloque. Sin embargo, el C estándar no admite funciones anidadas.

La duración de vida y la visibilidad de una variable están determinadas por su clase de almacenamiento . Hay tres tipos de duración de vida en C: estática (ejecución del programa), automática (ejecución de bloques, asignada en la pila) y manual (asignada en el montón). Solo la estática y la automática son compatibles con las variables y las maneja el compilador, mientras que la memoria asignada manualmente debe rastrearse manualmente en diferentes variables. Hay tres niveles de visibilidad en C: enlace externo (global), enlace interno (aproximadamente archivo) y ámbito de bloque (que incluye funciones); los ámbitos de bloque se pueden anidar y es posible tener diferentes niveles de enlace interno mediante el uso de inclusiones. El enlace interno en C es la visibilidad a nivel de la unidad de traducción , es decir, un archivo fuente después de ser procesado por el preprocesador de C , en particular incluyendo todas las inclusiones relevantes.

Los programas C se compilan como archivos de objeto separados , que luego se vinculan a un ejecutable o biblioteca a través de un enlazador . Por lo tanto, la resolución de nombres se divide entre el compilador, que resuelve los nombres dentro de una unidad de traducción (más libremente, "unidad de compilación", pero este es un concepto diferente), y el enlazador, que resuelve los nombres en las distintas 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 al principio del bloque), salen de contexto si se llama a alguna función (no anidada) dentro del bloque, vuelven a entrar en contexto cuando la función retorna y salen de contexto al final del bloque. En el caso de las variables locales automáticas, también se asignan en el momento de la declaración y se desasignan al final del bloque, mientras que en el caso de 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 el bloque finaliza:

#incluir <stdio.h> int main ( void ) { char x = 'm' ; printf ( "%c \n " , x ); { printf ( "%c \n " , x ); char x = 'b' ; printf ( "%c \n " , x ); } printf ( "%c \n " , x ); }                    

El programa genera:

metrometrobmetro

Existen otros niveles de alcance en C. [19] Los nombres de variables utilizados en un prototipo de función tienen visibilidad de prototipo de función y salen del contexto al final del prototipo de función. Dado que el nombre no se utiliza, esto no es útil para la compilación, pero puede ser útil para la documentación. Los nombres de etiqueta para la declaración GOTO tienen alcance de función.

C++

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 principio del cuerpo de la función main cuando declaramos que a, b y result eran 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 un bloque.

Las versiones modernas permiten un alcance léxico anidado.

Rápido

Swift tiene una regla similar para los ámbitos con C++, pero contiene diferentes modificadores de acceso.

Ir

Go tiene un alcance léxico mediante bloques. [3]

Java

Java tiene alcance léxico.

Una clase Java tiene varios tipos de variables: [20]

Variables locales
Se definen dentro de un método o de un bloque en particular. Estas variables son locales en el lugar donde se definieron y en niveles inferiores. Por ejemplo, un bucle dentro de un método puede usar las variables locales de ese método, pero no al revés. Las variables del bucle (locales a ese bucle) se destruyen tan pronto como el bucle termina.
Variables miembro
También llamados campos, son variables declaradas dentro de la clase, fuera de cualquier método. De manera predeterminada, estas variables están disponibles para todos los métodos dentro de esa clase y también para todas las clases del paquete.
Parámetros
son variables en las declaraciones de métodos.

En general, un conjunto de corchetes define un ámbito 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

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 del contexto léxico cuando se llama (que es irrelevante para la resolución de nombres). Los objetos de JavaScript tienen resolución de nombres para las 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 las variables del tipo de función ). [24] El alcance de bloque con las letpalabras constclave y es estándar desde ECMAScript 6. El alcance de bloque se puede producir envolviendo el bloque completo en una función y luego ejecutándolo; esto se conoce como el patrón de expresión de función invocada inmediatamente (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 manera predeterminada una nueva variable global, no una local. En segundo lugar, para crear una nueva variable local se debe usar la varpalabra clave; la variable se crea entonces en la parte superior de la función, con valor undefinedy se le asigna su valor a la variable cuando se alcanza la expresión de asignación:

A una variable con un Inicializador se le asigna el valor de su AssignmentExpression cuando se ejecuta VariableStatement , no cuando se crea la variable. [25]

Esto se conoce como elevación de variable [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, en lugar de un error 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, ya que se eleva la declaración de la variable local, ensombreciendo la variable global, pero no la inicialización, por lo que la variable no está definida cuando se usa:

a = 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 llamadas 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 contexto léxico o el contexto de ejecución donde se llama. Los ámbitos anidados de una función particular (desde el más global hasta el más local) en JavaScript, particularmente de un cierre, utilizado como devolución de llamada, a veces se denominan cadena de ámbitos , por analogía con la cadena de prototipos de un objeto.

En JavaScript, se pueden producir cierres mediante el uso de funciones anidadas, ya que las funciones son objetos de primera clase. [27] La ​​devolución de una función anidada desde una función envolvente incluye las variables locales de la función envolvente 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 en la llamada (empezando en 0) // y que devuelve su nuevo valor var a = 0 ; var b = function () { a ++ ; return a ; }; return b ; } c = newCounter (); alert ( c () + ' ' + c ()); // genera "1 2"                          

Los cierres se utilizan con frecuencia en JavaScript, debido a que se utilizan para devoluciones de llamadas. De hecho, cualquier conexión de una función en el contexto local como una devolución de llamada o su devolución desde una función crea un cierre si hay variables no vinculadas en el cuerpo de la función (con el contexto del cierre basado en los ámbitos anidados del contexto léxico actual, o "cadena de ámbitos"); 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 hace referencia a las variables en el contexto envolvente, que puede cambiar. [28]

La resolución de nombres de las propiedades de los objetos JavaScript se basa en la herencia en el árbol de prototipos (una ruta a la raíz en el árbol se denomina cadena de prototipos ) y es independiente de la resolución de nombres de variables y funciones.

Ceceo

Los dialectos Lisp tienen varias reglas para el alcance.

El Lisp original utilizaba un alcance dinámico; fue Scheme , inspirado en ALGOL , el que introdujo el alcance estático (léxico) a la familia Lisp.

Maclisp utilizaba el ámbito dinámico de forma predeterminada en el intérprete y el ámbito léxico de forma predeterminada en el código compilado, aunque el código compilado podía acceder a enlaces dinámicos mediante el uso de SPECIALdeclaraciones para variables particulares. [29] Sin embargo, Maclisp trataba el enlace léxico más como una optimización de lo que uno esperaría en los lenguajes modernos, y no venía con la característica de cierre que uno podría esperar del ámbito léxico en los Lisp modernos. Una operación separada, *FUNCTION, estaba disponible para solucionar de manera algo torpe 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 defdynamicforma especial, estar limitadas por una dynamic-letforma especial y se debe acceder a ellas mediante una dynamicforma especial explícita. [32]

Algunos otros dialectos de Lisp, como Emacs Lisp , todavía utilizan el ámbito dinámico de forma predeterminada. Emacs Lisp ahora tiene un ámbito léxico disponible por búfer. [33]

Pitón

Para las variables, Python tiene un ámbito de función, un ámbito de módulo y un ámbito global. Los nombres entran en el contexto al comienzo de un ámbito (función, módulo o ámbito global) y salen del contexto cuando se llama a una función no anidada o finaliza el ámbito. Si se utiliza un nombre antes de la inicialización de la variable, se genera una excepción en 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, envolvente, global, incorporada) que resuelve los nombres en el contexto relevante más estrecho. Sin embargo, si se asigna una variable, se declara de forma predeterminada una variable cuyo ámbito comienza al comienzo del nivel (función, módulo o global), no en la asignación. Ambas reglas se pueden anular con una declaración globalo nonlocal(en Python 3) antes del uso, lo que permite acceder a las variables globales incluso si hay una variable no local de enmascaramiento y asignar a variables globales o no locales.

Como ejemplo simple, una función resuelve una variable en el ámbito global:

>>> def  f (): ...  imprimir ( x ) ... >>> x  =  "global" >>> f () global

Tenga en cuenta que xse define antes de fque se llame, por lo que no se produce ningún error, aunque se defina después de su referencia en la definición de f. Léxicamente, se trata de una referencia hacia delante , lo cual está permitido 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 que su alcance es toda la función y, por lo tanto, su uso 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 () Traceback (última llamada más reciente): Archivo "<stdin>" , línea 1 , en <módulo> Archivo "<stdin>" , línea 2 , en f UnboundLocalError : variable local 'x' referenciada antes de la asignación

Las reglas de resolución de nombres predeterminadas se pueden anular con las palabras clave globalor nonlocal(en Python 3). En el código siguiente, la global xdeclaración in gsignifica que xse resuelve en la variable global. Por lo tanto, se puede acceder a ella (ya que ya se ha definido) y la asignación asigna a la variable global, en lugar de declarar una nueva variable local. Tenga en cuenta que no globalse necesita ninguna declaración in f; dado que no asigna a la variable, se resuelve de forma predeterminada en la variable global.

>>> def  f (): ...  imprimir ( x ) ... >>> def  g (): ...  global  x ...  imprimir ( x ) ...  x  =  "g" ... >>> x  =  "global" >>> f () global >>> g () global >>> f () g

globalTambié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, 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 las funciones anidadas, también existe la nonlocaldeclaración para asignar a una variable no local, similar al uso globalen una función no anidada:

>>> def  f (): ...  def  g (): ...  no local  x  # Solo Python 3 ...  x  =  "g" ...  x  =  "f" ...  g () ...  print ( x ) ... >>> x  =  "global" >>> f () g >>> print ( x ) global

R

R es un lenguaje con 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 características (como parent.frame()) que pueden simular la experiencia del alcance dinámico si el programador lo desea.

No hay alcance de bloque:

a <- 1 { a <- 2 } mensaje ( a ) ## 2     

Las funciones tienen acceso al ámbito 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 su asignación al ámbito que las incluye:

a <- 1 f <- función () { mensaje ( a ) a <<- 2 mensaje ( a ) } f () ## 1 ## 2 mensaje ( a ) ## 2          

Aunque R tiene alcance léxico por defecto, los alcances de las funciones se pueden cambiar:

a <- 1 f <- función () { mensaje ( a ) } mi_env <- nuevo.env () mi_env $ a <- 2 f () ## 1 entorno ( f ) <- mi_env f () ## 2            

Notas

  1. ^ Véase la definición del significado de "alcance" versus "contexto".
  2. ^ El "alcance dinámico" basa la resolución de nombres en la extensión (duración), no en el alcance , y por lo tanto es formalmente inexacto.
  3. ^ Por ejemplo, el motor de plantillas Jinja para Python utiliza de manera predeterminada tanto el ámbito léxico (para importaciones) como el ámbito dinámico (para inclusiones), y permite especificar el comportamiento con palabras clave; consulte Comportamiento del contexto de importación.
  4. ^ "Resolución de nombres" y "vinculación de nombres" son en gran medida sinónimos; en sentido estricto, "resolución" determina a qué nombre se refiere un uso particular de un nombre, sin asociarlo con ningún significado, como en la sintaxis abstracta de orden superior , mientras que "vinculación" asocia el nombre con un significado real. En la práctica, los términos se usan indistintamente.
  5. ^ En el caso del código automodificable, el contexto léxico en sí puede cambiar durante el tiempo de ejecución.
  6. ^ Por el contrario, *"el contexto de un enlace de nombre", *"un enlace de nombre que entra dentro del ámbito" o *"un enlace de nombre que sale fuera del ámbito" son todos incorrectos: un enlace de nombre tiene alcance, mientras que una parte de un programa tiene contexto.

Referencias

  1. ^ "Informe sobre el lenguaje algorítmico Algol 60", 2.7. Cantidades, tipos y alcances
  2. ^ WG14 N1256 (versión actualizada de 2007 del estándar C99 ), 6.2.1 Alcances de los identificadores, 2007-09-07
  3. ^ ab La especificación del lenguaje de programación Go: declaraciones y alcance, versión del 13 de noviembre de 2013
  4. ^ abc Borning A. CSE 341 -- Alcance léxico y dinámico. Universidad de Washington.
  5. ^ Crockford, Douglas. "Convenciones de código para el lenguaje de programación JavaScript" . Consultado el 4 de enero de 2015 .
  6. ^ Backus, JW; Wegstein, JH; Van Wijngaarden, A.; Woodger, M.; Bauer, FL; Green, J.; Katz, C.; McCarthy, J.; Perlis, AJ; Rutishauser, H.; Samelson, K.; Vauquois, B. (1960). "Informe sobre el lenguaje algorítmico ALGOL 60". Comunicaciones de la ACM . 3 (5): 299. doi : 10.1145/367236.367262 . S2CID  278290.
  7. ^ "Funciones - Javascript:MDN". 23 de abril de 2023. 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 ámbito de la función. Sin embargo, una función puede acceder a todas las variables y funciones definidas dentro del ámbito en el que está definida.
  8. ^ "N4720: Borrador de trabajo, extensiones a C++ para módulos" (PDF) . Archivado (PDF) desde el original el 2019-04-30 . Consultado el 2019-04-30 .
  9. ^ "Pragmática del lenguaje de programación", tabla de símbolos de LeBlank-Cook
  10. ^ "Una abstracción de tabla de símbolos para implementar lenguajes con control de alcance explícito", LeBlank-Cook, 1983
  11. ^ Louis Steele, Guy (agosto de 1982). "Una visión general de COMMON LISP". Actas del simposio ACM de 1982 sobre LISP y programación funcional - LFP '82 . págs. 98-107. doi :10.1145/800068.802140. ISBN 0-89791-082-6. Número de identificación del sujeto  14517358.
  12. ^ Joel, Moses (junio de 1970). "La función de FUNCTION en LISP". MIT AI Memo 199. Laboratorio de Inteligencia Artificial del MIT.
  13. ^ Steele, Guy Lewis Jr.; Sussman, Gerald Jay (mayo de 1978). "El arte del intérprete; o, el complejo de modularidad (partes cero, uno y dos)". MIT AI Memo 453. Laboratorio de Inteligencia Artificial del MIT.
  14. ^ Shivers, Olin. "Historia de T". Paul Graham . Consultado el 5 de febrero de 2020 .
  15. ^ Steele, Guy Lewis Jr. (mayo de 1978). RABBIT: un compilador para SCHEME (informe técnico). Instituto Tecnológico de Massachusetts. hdl : 1721.1/6913 .
  16. ^ "lexical scope", Computer and Program Organization, Parte 3 , p. 18, en Google Books , Universidad de Michigan. Conferencias de verano de ingeniería, 1967
  17. ^ "alcance léxico", Informe de progreso del Proyecto MAC, Volumen 8 , pág. 80, en Google Books , 1970.
  18. ^ ab Scott 2009, 3.4 Implementación del alcance, pág. 143.
  19. ^ "Ámbito de aplicación", XL C/C++ V8.0 para Linux, IBM
  20. ^ "Declaración de variables miembro (Tutoriales de Java > Aprendizaje del lenguaje Java > Clases y objetos)". docs.oracle.com . Consultado el 19 de marzo de 2018 .
  21. ^ "Controlar el acceso a los miembros de una clase (Tutoriales de Java > Aprendizaje del lenguaje Java > Clases y objetos)". docs.oracle.com . Consultado el 19 de marzo de 2018 .
  22. ^ "Todo lo que necesita saber sobre el alcance de las variables de Javascript", Saurab Parakh, Coding is Cool, 8 de febrero de 2010
  23. ^ "ES5 anotado". es5.github.io . Consultado el 19 de marzo de 2018 .
  24. ^ "Funciones". MDN Web Docs . Consultado el 19 de marzo de 2018 .
  25. ^ "12.2 Declaración de variable", ECMAScript 5.1 anotado, última actualización: 28/05/2012
  26. ^ "Alcance y elevación de JavaScript", Ben Cherry, Adequately Good, 8 de febrero de 2010
  27. ^ Cierres de Javascript, Richard Cornford. Marzo de 2004
  28. ^ "Explicación del alcance y los cierres de JavaScript", Robert Nyman, 9 de octubre de 2008
  29. ^ Pitman, Kent (16 de diciembre de 2007). "The Revised Maclisp Manual (The Pitmanual), Sunday Morning Edition". MACLISP.info . HyperMeta Inc. Declaraciones y el compilador, concepto "Variables" . Consultado el 20 de octubre de 2018 . Si la variable que se va a vincular se ha declarado como especial, la vinculación se compila como código para imitar la forma en que el intérprete vincula las variables
  30. ^ Pitman, Kent (16 de diciembre de 2007). "The Revised Maclisp Manual (The Pitmanual), Sunday Morning Edition". MACLISP.info . HyperMeta Inc. The Evaluator, Special Form . Consultado el 20 de octubre de 2018 . tiene como objetivo ayudar a resolver el " problema funarg ", sin embargo, solo funciona en algunos casos fáciles.*FUNCTION*FUNCTION
  31. ^ Pitman, Kent; et al. (versión web del estándar ANSI X3.226-1994) (1996). "Common Lisp HyperSpec". Lispworks.com . LispWorks Ltd. 1.1.2 History . Consultado el 20 de octubre de 2018 . 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.
  32. ^ "Lenguaje de programación ISLISP, borrador de trabajo ISLISP 23.0" (PDF) . ISLISP.info . 11.1 El principio léxico . Consultado el 20 de octubre de 2018 . Los enlaces dinámicos se establecen y se accede a ellos mediante un mecanismo independiente (es decir, , , y ).defdynamicdynamic-letdynamic
  33. ^ "Enlace léxico". EmacsWiki . Consultado el 20 de octubre de 2018. Emacs 24 tiene un enlace léxico opcional, que se puede habilitar por búfer.
  34. ^ "Preguntas frecuentes sobre R". cran.r-project.org . Consultado el 19 de marzo de 2018 .