Articulo de referencia

Alcance (programación informática)

En programación informática , el ámbito de una vinculación de nombre (la asociación de un nombre con una entidad, como una variable ) es la parte del programa donde dicha vincul...

En programación informática , el ámbito de una vinculación de nombre (la asociación de un nombre con una entidad, como una variable ) es la parte del programa donde dicha vinculación es válida. En otras palabras, un ámbito es donde un nombre puede usarse para referirse a una entidad . En otras partes del programa, el nombre puede referirse a una entidad diferente (puede tener una vinculación distinta) o a nada en absoluto (puede estar sin vincular). El ámbito ayuda a prevenir conflictos de nombres al permitir que el mismo nombre se refiera a diferentes objetos, siempre que los nombres tengan ámbitos separados. El ámbito de una vinculación de nombre también se conoce como la visibilidad de una entidad, especialmente en la literatura más antigua o técnica; esto se refiere a la entidad referenciada, no al nombre que la referencia.

El término "ámbito" también se utiliza para referirse al conjunto de todas las vinculaciones de nombres que son válidas 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 ámbito léxico . Sin embargo, en algunos lenguajes, "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 pueden resultar algo confusos —utilizan incorrectamente términos técnicos, como se explica en la definición— , pero la distinción en sí es precisa y exacta, y estos son los términos estándar respectivos. El ámbito léxico es el tema central 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 ámbito léxico es relativamente sencilla de usar e implementar, ya que durante su uso se puede leer el código fuente hacia atrás para determinar a qué entidad se refiere un nombre, y durante 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 anticipadas y el hoisting , mientras que surgen otras considerablemente más sutiles con las variables no locales , particularmente en los cierres .

Definición

La definición estricta del "ámbito" (léxico) de un nombre ( identificador ) es inequívoca: el ámbito léxico es "la porción del código fuente en la que se aplica una vinculación de un nombre con una entidad". Esta definición permanece prácticamente sin cambios 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 cantidades: variables simples, matrices, etiquetas, interruptores y procedimientos. El ámbito de una cantidad es el conjunto de instrucciones y expresiones en las que la declaración del identificador asociado a dicha cantidad es válida.
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 tipo , un nombre de etiqueta, un nombre de macro o un parámetro de macro. El mismo identificador puede denotar entidades diferentes en distintos puntos del programa. [...] Para cada entidad distinta que designa un identificador, este solo es visible (es decir, se puede usar) dentro de una región del texto del programa denominada su ámbito.
Go (2013) [ 3 ]
Una declaración vincula un identificador no vacío a una constante, tipo, variable, función, etiqueta o paquete. [...] El ámbito de un identificador declarado es la extensión del texto fuente en la que el identificador denota la constante, tipo, variable, función, etiqueta o paquete especificado.

Generalmente, el término "ámbito" se refiere a cuándo 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.

Alcance léxico frente a alcance dinámico

Una distinción fundamental en el alcance radica en el significado de "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 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. En cambio, en lenguajes con alcance dinámico, la resolución de nombres depende del estado del programa cuando se encuentra el nombre, el cual 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; si no se encuentra, se busca en el contexto léxico externo, y así sucesivamente; mientras que con alcance dinámico, un nombre se resuelve buscando en el contexto de ejecución local; si no se encuentra, se busca en el contexto de ejecución externo, y así sucesivamente, ascendiendo en la pila de llamadas. [ 4 ]

La mayoría de los lenguajes modernos utilizan el ámbito léxico para variables y funciones, aunque el ámbito dinámico se utiliza en algunos lenguajes, especialmente en algunos dialectos de Lisp, algunos lenguajes de "scripting" y algunos lenguajes de plantillas . [ c ] Perl 5 ofrece tanto ámbito léxico como dinámico. Las funciones que utilizan variables con ámbito léxico se conocen como cierres .

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 generalmente solo 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 despacho dinámico selecciona un método de objeto en tiempo de ejecución, aunque el hecho de que la vinculación del nombre se realice en tiempo de compilación o en tiempo de ejecución depende del lenguaje. El ámbito dinámico de facto es común en los lenguajes de macros , que no resuelven nombres directamente, sino que los expanden in situ.

Algunos frameworks de programación, como AngularJS, utilizan sintácticamente el término "scope" con un significado —semántico— completamente distinto al que se le da en este artículo. En estos frameworks, el scope es simplemente un objeto del lenguaje de programación que utilizan ( JavaScript en el caso de AngularJS) que el framework emplea de ciertas maneras para emular el ámbito dinámico en un lenguaje que utiliza el ámbito léxico para sus variables. Estos scopes de AngularJS pueden estar o no en contexto (según el significado habitual del término) en cualquier parte del programa, siguiendo las reglas habituales del ámbito de las variables del lenguaje, como cualquier otro objeto, y utilizando sus propias reglas de herencia y transclusión . En el contexto de AngularJS, a veces se utiliza el término "$scope" (con el signo de dólar) para evitar confusiones, pero las guías de estilo suelen desaconsejar el uso del signo de dólar en los nombres de las variables. [ 5 ]

Usar

El ámbito 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 ámbito) varía entre lenguajes de programación y, dentro de un mismo lenguaje, varía según el tipo de entidad; las reglas para el ámbito se denominan reglas de ámbito (o reglas de alcance ). Junto con los espacios de nombres , las reglas de ámbito son cruciales en la programación modular , de modo que un cambio en una parte del programa no afecte a una parte no relacionada.

Descripción general

Al hablar de alcance, hay tres conceptos básicos: alcance, extensión y contexto. "Alcance" y "contexto" en particular se confunden frecuentemente: el alcance es una propiedad de una vinculación de nombre, mientras que el 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 consta del 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 los alcances de varias vinculaciones de nombres, y en un punto de la ejecución las vinculaciones de nombres están "en contexto" o "fuera de contexto", por lo tanto las vinculaciones de nombres "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 flexible.

El ámbito es un concepto propio del código fuente y una propiedad de las vinculaciones de nombres, en particular de las vinculaciones de nombres de variables o funciones (los nombres en el código fuente son referencias a entidades del programa). Forma parte del comportamiento del compilador o intérprete de un lenguaje. Por ello, las cuestiones de ámbito son similares a las de los punteros , un tipo de referencia que se utiliza en programas de forma más general. Acceder al valor de una variable cuando su nombre está en contexto, pero la variable no está inicializada, es análogo a desreferenciar (acceder al valor de) un puntero colgante , ya que este no está definido. Sin embargo, como las variables no se destruyen hasta que salen de contexto, no existe un equivalente a un puntero colgante .

Para entidades como las variables, el ámbito es un subconjunto de la duración (también conocida 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 dado), o accesible pero no a través del nombre dado, en cuyo caso no está en contexto (el programa está "fuera del ámbito del nombre"). En otros casos, la "duración" es irrelevante: una etiqueta (posición con nombre en el código fuente) tiene una duración idéntica a la del programa (para lenguajes compilados estáticamente), pero puede estar en contexto o no en un punto dado del programa, y ​​lo mismo ocurre con 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 una duración 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 (propiamente, el compilador o intérprete) busca coincidencias en todas las entidades que se encuentran en el contexto; en caso de ambigüedad (dos entidades con el mismo nombre, como una variable global y una 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 al contexto relevante más específico. En algunos casos, la resolución de nombres se puede especificar explícitamente, como con las palabras clave `resolve` globaly nonlocal`resolve` en Python; en otros casos, las reglas predeterminadas no se pueden sobrescribir.

Cuando dos nombres idénticos aparecen en contexto simultáneamente, refiriéndose a entidades diferentes, se habla de enmascaramiento de nombres , donde el nombre de mayor prioridad (generalmente el más interno) oculta al de menor prioridad. A nivel de variables, esto se conoce como ocultamiento de variables . Debido al potencial de errores lógicos derivados del enmascaramiento, algunos lenguajes lo prohíben o desaconsejan, generando un error o advertencia en tiempo de compilación o de ejecución.

Los distintos lenguajes de programación tienen diferentes reglas de ámbito para los diferentes tipos de declaraciones y nombres. Estas reglas de ámbito influyen considerablemente en la semántica del lenguaje y, por consiguiente, 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 dar lugar a un comportamiento indefinido , similar a referirse a un puntero colgante ; además, las declaraciones o nombres utilizados fuera de su ámbito 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íficas para controlar el ámbito.

Niveles de alcance

El alcance puede variar desde una sola expresión hasta el programa completo, con muchas gradaciones intermedias. 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 independiente, donde los nombres son visibles dentro del módulo (privados del módulo) pero no 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 la función; otros, especialmente los lenguajes de programación funcional , permiten el alcance de expresión para restringir el alcance a una sola expresión. Otros alcances incluyen el alcance de archivo (especialmente en C), que se comporta de forma similar al alcance de módulo, y el alcance de bloque fuera de las funciones (especialmente 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, por lo que diferentes nombres declarados dentro de un bloque dado pueden tener ámbitos diferentes. Esto requiere declarar funciones antes de usarlas, aunque no necesariamente definirlas, y requiere declaración anticipada en algunos casos, especialmente para la recursión mutua. En otros lenguajes, como Python, el ámbito 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 se defina, por lo que todos los nombres dentro de un bloque dado 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 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 en tiempo de ejecución, mientras que en JavaScript los nombres indefinidos declarados con varson utilizables en toda la función porque están vinculados implícitamente al valor undefined.

Alcance de la expresión

El ámbito de una vinculación de nombre es una expresión , que se conoce como ámbito de expresión . El ámbito de expresión está disponible en muchos lenguajes, especialmente en lenguajes funcionales que ofrecen una característica llamada expresiones let que permite que el ámbito 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 se evalúa a , usando una variable temporal llamada para evitar llamar dos veces. Algunos lenguajes con ámbito de bloque aproximan esta funcionalidad al ofrecer sintaxis para que un bloque se incruste en una expresión; por ejemplo, la expresión 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}({intx=f();x*x;})

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

En C, los nombres de las variables en el prototipo de una función tienen ámbito de expresión, conocido en este contexto como ámbito de protocolo de función . Como los nombres de las variables en el prototipo no se mencionan (pueden ser diferentes en la definición real), son simplemente variables ficticias, por lo que a menudo se omiten, aunque pueden usarse para generar documentación, por ejemplo.

Alcance del bloque

El ámbito de una vinculación de nombre es un bloque , que se conoce como ámbito de bloque . El ámbito de bloque está disponible en muchos, pero no en todos, los lenguajes de programación con estructura de bloques. 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. Por lo general, este bloque está contenido dentro de una función, lo que restringe el ámbito a una parte de la función, pero en algunos casos, como en Perl, el bloque puede no estar dentro de una función.

int suma_de_cuadrados ( int m ) { int resultado = 0 ; for ( int n = 1 ; n <= m ; ++ n ) { const int n_cuadrado = n * n ; resultado += n_cuadrado ; } return resultado ; }

Un ejemplo representativo del uso del ámbito de bloque es el código C que se muestra aquí, donde dos variables tienen ámbito dentro 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 con 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, result += n * npero en ejemplos más complejos las variables auxiliares son útiles.

Los bloques se utilizan principalmente para el control de flujo, como en los bucles if, while y for, y en estos casos el ámbito del 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 también suelen permitir el uso de bloques "desnudos", cuyo único propósito es permitir un control preciso del ámbito de las variables. Por ejemplo, una variable auxiliar puede definirse en un bloque, luego usarse (por ejemplo, sumarse a una variable con ámbito de función) y descartarse cuando finaliza el bloque, o un bucle while puede estar encerrado en un bloque que inicializa las variables utilizadas dentro del bucle que solo deben inicializarse una vez.

Una particularidad de varios lenguajes de programación, como Algol 68 y C (demostrada en este ejemplo y estandarizada desde C99 ), es que las variables de ámbito de bloque pueden declararse no solo dentro del cuerpo del bloque, sino también dentro de la instrucció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 la función (antes de que comience el bloque del cuerpo de la función) y tienen ámbito para todo el cuerpo de la función. Esto se utiliza principalmente en los bucles `for` , que tienen una instrucción de inicialización separada de la condición del bucle, a diferencia de los bucles `while`, y es una práctica común.

El ámbito de bloque se puede utilizar para ocultar variables. 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 una mala práctica 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 (es decir, que una variable local puede quedar fuera de contexto antes de que finalice una función), no permiten que una variable local oculte a otra. En dichos lenguajes, el intento de declarar una segunda n daría como resultado un error de sintaxis, y una de las variables n 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 de bloque, se puede inicializar una variable de la siguiente manera:

si c : a = "foo" sino : a = ""

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

En Perl, que tiene ámbito 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 utilizando asignaciones múltiples, inicializando la variable con un valor predeterminado. En Python (donde no es necesario) sería:

a = "" si c : a = "foo"

mientras que en Perl sería:

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

En el caso de una única asignación de variable, una alternativa es utilizar el operador ternario para evitar un bloque, pero esto no suele ser posible para asignaciones de múltiples variables y resulta difícil de leer para lógica compleja.

Este es un problema más significativo en C, especialmente para la asignación de cadenas, ya que la inicialización de una cadena puede asignar memoria automáticamente, mientras que la asignación de una cadena a una variable ya inicializada requiere asignar memoria, hacer una copia de la cadena y comprobar que estas operaciones se hayan realizado correctamente.

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

Algunos lenguajes permiten aplicar el concepto de ámbito de bloque, en distintos grados, fuera de una función. Por ejemplo, en el fragmento 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_counterincrementará 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 modificar el valor de $counter. Este modismo permite definir cierres en Perl.

Alcance de la función

Cuando el ámbito de las variables declaradas dentro de una función no se extiende más allá de esa función, se conoce como ámbito de función . [ 7 ] El ámbito de 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 ámbito termina (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 la función comienza (o la variable es declarada), destruida cuando la función regresa, mientras que el ámbito de la variable está dentro de la función, aunque el significado de "dentro" depende de si el ámbito 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 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 ámbito 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 finaliza. Las funciones llamadas no tienen acceso a las variables locales de las funciones que las 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 ámbito 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; por lo tanto, las variables locales están en contexto de la función en la que se definen y de 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 se encuentran 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 contenedora se conoce como variable no local para la función anidada. El ámbito de función también es aplicable a las funciones anónimas .

def cuadrado ( n : int ) -> int : return n * ndef suma_de_cuadrados ( n : int ) -> int : total : int = 0 i : int = 0 while i <= n : total += cuadrado ( i ) i += 1 return 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; calcula la suma de todos los sum_of_squarescuadrados hasta un número. (Por ejemplo, essquare(4)=  , y es + 1² + + 3² + = . ) 16sum_of_squares(4)          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 ámbito léxico y ámbito de función: el ámbito de cada una es su propia función, léxicamente separada, y por lo tanto, no se superponen. Por consiguiente, sum_of_squaresse puede llamar a squaresin que su propia n se vea alterada. De manera similar, sum_of_squarestiene variables llamadas total e i ; estas variables, debido a su ámbito 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 colisión de nombres entre estos nombres y cualquier nombre no relacionado, incluso si son idénticos.

No se produce enmascaramiento de nombres: solo una variable llamada 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 variable n de 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 ("sombreada") por la nueva variable n de la función llamada.

El ámbito de las funciones se complica significativamente si son objetos de primera clase y pueden crearse localmente y luego devolverse. En este caso, cualquier variable en la función anidada que no sea local a ella (variables no vinculadas en la definición de la función, que se resuelven en variables en un contexto que la contiene) crea un cierre , ya que no solo la función en sí, sino también su contexto (de variables) debe devolverse y, potencialmente, llamarse en un contexto diferente. Esto requiere un soporte mucho mayor por parte del compilador y puede complicar el análisis del programa .

Ámbito del archivo

El ámbito de una vinculación de nombre es un archivo, lo que se conoce como ámbito de archivo . El ámbito de archivo es en gran medida particular de C (y C++), donde el ámbito 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 ámbito de módulo, donde los módulos se identifican con archivos, y en lenguajes más modernos se reemplaza por un ámbito de módulo explícito. Debido a la presencia de las sentencias include, que agregan variables y funciones al contexto interno y pueden llamar a otras sentencias 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 ámbito global (en C, enlace externo). Si se añadiera statica la firma de la función, tendría ámbito de archivo (enlace interno).

Alcance del módulo

El ámbito de una vinculación de nombres es un módulo, conocido como ámbito de módulo . Este ámbito 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 información y exponer una interfaz limitada. El ámbito de módulo se originó en la familia de lenguajes Modula , y Python (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 ] una estructura similar se proporciona 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 mejor en el contexto de la distribución dinámica que en el de la resolución de nombres y el ámbito, aunque a menudo desempeñan funciones análogas. En algunos casos, ambas funcionalidades están disponibles, como en Python, que cuenta con 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 ámbito de una vinculación de nombre abarca todo un programa, lo que se conoce como ámbito global . Los nombres de variables con ámbito global —llamados variables globales— suelen considerarse 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; por ello, se prefiere el ámbito de función o el ámbito de bloque. Sin embargo, el ámbito global se utiliza habitualmente (según el lenguaje) 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.

Alcance léxico frente a alcance dinámico

El uso de variables locales —variables con nombres de ámbito limitado, que solo existen dentro de una función específica— ayuda a evitar el riesgo de que dos variables con el mismo nombre se confundan. 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 ámbito estático ), si el ámbito de un nombre de variable 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, el nombre de la variable existe y está vinculado a su valor, pero fuera de ese texto, el nombre de la variable no existe. Por el contrario, en el ámbito dinámico (o ámbito dinámico ), si el ámbito de un nombre de variable es una función determinada, entonces su ámbito 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 está 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 invocaf una función definida por separado g, entonces bajo el ámbito léxico, la función gno tiene acceso a las variables locales de (suponiendo que el texto de no está dentro del texto de ), mientras que bajo el ámbito dinámico, la función tiene acceso a las variables locales de (ya que se invoca durante la invocación de ).fgfgfgf

$ # lenguaje bash $ x = 1 $ function g () { echo $x ; x = 2 ; } $ function f () { local x = 3 ; g ; } $ 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 a . La segunda línea, , define una función que imprime ("haz eco") el valor actual de , y luego establece a (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 a , y luego llama a . La cuarta línea, , llama a . La quinta línea, , imprime el valor actual de .x=1x1functiong(){echo$x;x=2;}gxx2functionf(){localx=3;g;}fx3gffecho$xx

Las reglas de ámbito determinan lo que imprime este programa. Si el lenguaje de este programa utiliza ámbito léxico, 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 ámbito dinámico, gimprime y modifica fla variable local de x(porque gse llama desde dentro de f), por lo que el programa imprime 3y luego 1. (Da la casualidad de que el lenguaje del programa es Bash , que utiliza ámbito dinámico; por lo que el programa imprime 3y luego 1. Si el mismo código se ejecutara con ksh93 , que utiliza ámbito léxico, los resultados serían diferentes).

Alcance 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 . Dado que esta correspondencia 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 , y 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 diferentes tipos de limitaciones. El ámbito estático permite al programador razonar sobre las referencias a objetos, como parámetros, variables, constantes, tipos, funciones, etc., como simples sustituciones de nombres. Esto facilita enormemente la creación de código modular y el razonamiento sobre él, ya que la estructura de nombres local se puede comprender 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 P ; var I : número entero ; K : carbón ;procedimiento A ; var K : real ; L : número entero ;procedimiento B ; var M : real ; inicio (*ámbito P+A+B*) fin ;(*alcance P+A*) fin ;(*ámbito P*) fin .

Por ejemplo, Pascal tiene ámbito léxico. Considere el fragmento de programa Pascal de 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 solo en los procedimientos Ay B. La variable Ltambién es visible solo en el procedimiento Ay Bpero no oculta ninguna otra variable. La variable Mes visible solo en el procedimiento By, por lo tanto, no es accesible ni desde el procedimiento Ani desde el programa principal. Además, el procedimiento Bes visible solo en el procedimiento Ay, por lo tanto, no se puede llamar desde el programa principal. Podría haber otro procedimiento llamado Bdeclarado en el programa fuera del procedimiento B. El lugar en el programa donde Bse menciona " " determina entonces cuál de los dos procedimientos llamados Brepresenta, análogo al ámbito de las variables.

La implementación correcta del ámbito 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 formado por la función y este contexto se denomina clausura ). Dependiendo de la implementación y la arquitectura del ordenador , la búsqueda de variables puede volverse ligeramente ineficiente cuando se utilizan funciones anidadas léxicamente muy profundas , aunque existen técnicas bien conocidas para mitigar esto. [ 9 ] [ 10 ] Además, para las funciones anidadas que solo se refieren a sus propios argumentos y variables locales (inmediatamente), todas las ubicaciones relativas pueden conocerse en tiempo de compilación . Por lo tanto, no se incurre en ninguna 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).

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 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 que se incorporaron a ALGOL 60 y ALGOL 68 (aunque C no incluía funciones anidadas léxicamente ).

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

El intérprete original de Lisp (1960) utilizaba un ámbito dinámico. El enlace profundo , que se aproxima al ámbito estático (léxico), se introdujo alrededor de 1962 en LISP 1.5 (a través del dispositivo Funarg desarrollado por Steve Russell , que trabajaba bajo la dirección de John McCarthy ).

Todos los primeros Lisp utilizaban ámbito dinámico cuando se basaban en intérpretes. En 1982, Guy L. Steele Jr. y el Common Lisp Group publicaron Una visión general de Common Lisp [ 11 ] , una breve reseña de la historia y las diversas implementaciones de Lisp hasta ese momento, así como 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 presentan inconsistencias internas, ya que, por defecto, el intérprete y el compilador pueden asignar semánticas diferentes a programas correctos. Esto se debe principalmente a que el intérprete asume que todas las variables tienen un ámbito dinámico, mientras que el compilador las asume como locales, a menos que se le obligue a asumir lo contrario. Si bien esto se ha hecho por conveniencia y eficiencia, puede generar errores muy sutiles. La definición de Common LISP evita estas 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, las implementaciones de Common Lisp debían tener un alcance léxico . Nuevamente, de Una descripción general de Common Lisp :

Además, Common LISP ofrece las siguientes facilidades (la mayoría de las cuales se toman prestadas de MacLisp, InterLisp o Lisp Machines Lisp): (...) Variables con ámbito léxico completo. El llamado " problema FUNARG " [ 12 ] [ 13 ] está completamente resuelto, tanto en el caso descendente como en el ascendente.

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

Todos los lenguajes Lisp serios que se usaban en 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 la habían leído estaban dando un salto de fe al pensar que esto iba 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 ]

Ámbito léxico en la programación moderna

En los lenguajes de programación modernos, el ámbito léxico desempeña un papel crucial en la implementación de paradigmas de programación funcional. Lenguajes como JavaScript [ 18 ] , Python [ 19 ] y Swift [ 20 ] dependen en gran medida del ámbito léxico para garantizar que las funciones puedan acceder a las variables desde su contexto de definición, incluso si la función se ejecuta fuera de su ámbito léxico. Esto es particularmente importante al trabajar con cierres, que son una consecuencia directa del ámbito léxico.

Por ejemplo, en JavaScript, los cierres se utilizan con frecuencia para la programación asíncrona y el manejo de eventos, ya que permiten que las funciones de devolución de llamada mantengan el acceso a las variables del ámbito externo, lo que facilita un código más limpio y modular. De manera similar, Python y Swift utilizan el ámbito léxico para implementar cierres y habilitar patrones potentes como las funciones de orden superior. [ 21 ]

Alcance dinámico

Con el á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 un nombre, xse añade un enlace a la pila global x(que puede estar vacía), la cual se elimina cuando el flujo de control abandona el ámbito. La evaluación xen cualquier contexto siempre devuelve el enlace superior. Cabe destacar que esto no se puede realizar en tiempo de compilación, ya que la pila de enlaces solo existe en tiempo de ejecución ; por ello, este tipo de ámbito se denomina ámbito dinámico .

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

Generalmente, ciertos bloques se definen para crear enlaces cuya duración coincide con 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 diferentes ubicaciones y situaciones, puede resultar difícil determinar de entrada qué enlaces se aplicarán al usar una variable (o si existe alguno). Esto puede ser beneficioso; la aplicación del principio de mínimo conocimiento sugiere que el código evite depender de las razones (o circunstancias) del valor de una variable, y simplemente use el valor según su definición. Esta interpretación estricta 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 depende de una documentación minuciosa de todas las variables utilizadas de esta manera y de 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 entre ámbito estático o dinámico al definir o redefinir una variable. Algunos ejemplos de lenguajes que utilizan ámbito dinámico son Logo , Emacs Lisp , LaTeX y los lenguajes de shell bash , dash y PowerShell .

El ámbito dinámico es bastante fácil de implementar. Para encontrar el valor de un nombre, el programa podría recorrer la pila de ejecución, comprobando cada registro de activación (el marco de pila de cada función) para encontrar 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 cada vez que se hacen declaraciones y se extraen cuando las variables salen de contexto. [ 22 ] El enlace superficial es una estrategia alternativa que es considerablemente más rápida, 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 correctamente esta tabla. [ 22 ] Tenga en cuenta que ambas estrategias asumen un orden último en entrar, primero en salir ( LIFO ) para los enlaces de cualquier variable; en la práctica, todos los enlaces están ordenados de esta manera.

Una implementación aún más sencilla 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 en la pila que es invisible para el programa. Cuando ese ámbito de enlace 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 usaron 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 enlace superficial anterior, excepto que la tabla de referencia central es simplemente el contexto de enlace 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 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 esta manera, no puede basarse en guardar y restaurar una variable global. Una posible estrategia de implementación consiste en que cada variable tenga una clave local de subproceso. Al acceder a la variable, se utiliza la clave local de subproceso para acceder a la ubicación de memoria local de subproceso (mediante código generado por el compilador, que sabe qué variables son dinámicas y cuáles son léxicas). Si no existe una clave local de subproceso para el subproceso que realiza la llamada, se utiliza la ubicación global. Cuando una variable se enlaza localmente, el valor anterior se almacena en una ubicación oculta en la pila. El almacenamiento local de subproceso 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 nuevamente la versión global de la variable a ese subproceso.

Con la transparencia referencial, el ámbito dinámico se restringe únicamente a la pila de argumentos de la función actual y coincide con el ámbito léxico.

Expansión macro

En los lenguajes modernos, la expansión de macros en un preprocesador es un ejemplo clave de ámbito dinámico de facto. El lenguaje de macros en sí solo transforma el código fuente, sin resolver los nombres, pero como la expansión se realiza in situ, cuando se resuelven los nombres en el texto expandido (en particular, las variables libres), se resuelven en función de dónde se expanden (lo que se podría denominar de forma aproximada), como si existiera un ámbito dinámico.

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

#define ADD_A(x) x + a

Se expandirá para agregar aa la variable pasada, y este nombre solo será resuelto posteriormente por el compilador en función de dónde ADD_Ase "llama" (o expande) la macro. En realidad, el preprocesador de C solo realiza un análisis léxico , expandiendo la macro durante la etapa de tokenización, pero no la analiza en un árbol sintáctico ni realiza la resolución de nombres.

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

#define ADD_A(x) (x + a)void add_one ( int * x ) { const int a = 1 ; * x = ADD_A ( * x ); }void add_two ( int * x ) { const int a = 2 ; * x = ADD_A ( * x ); }

Nombres calificados

Como hemos visto, una de las razones clave para el ámbito global es que ayuda a prevenir conflictos de nombres, al permitir que nombres idénticos se refieran a elementos distintos, con la restricción de que los nombres deben tener ámbitos separados. A veces, esta restricción resulta inconveniente; cuando se necesita acceder a muchos elementos diferentes en todo un programa, generalmente todos requieren nombres con ámbito global, por lo que se necesitan técnicas diferentes para evitar conflictos de nombres.

Para abordar esto, 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 asignar un nombre —un prefijo— y, cuando sea necesario, se puede hacer referencia a una entidad mediante un nombre cualificado que consta del nombre más el prefijo. Normalmente, dichos nombres tendrán, en cierto sentido, dos conjuntos de ámbitos: un ámbito (generalmente el ámbito global) en el que el nombre cualificado es visible, y uno o más ámbitos más restringidos en los que el nombre no cualificado (sin el prefijo) también es visible. Y normalmente estos grupos pueden organizarse a su vez en grupos; es decir, pueden estar anidados .

Aunque muchos lenguajes admiten este concepto, los detalles varían enormemente. Algunos lenguajes cuentan con 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 o los objetos singleton cumplan este propósito (independientemente de si también tienen un mecanismo para este propósito principal). Además, los lenguajes a menudo combinan estos enfoques; por ejemplo, los paquetes de Perl son en gran medida similares a los espacios de nombres de C++, pero opcionalmente también funcionan 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 los de 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 , especialmente para las variables. C es un lenguaje con ámbito léxico que incluye ámbito global (conocido como enlace externo ), un tipo de ámbito de módulo o de archivo (conocido como enlace interno ) y ámbito local (dentro de una función); dentro de una función, los ámbitos pueden anidarse aún más mediante el ámbito de 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 . En C existen tres tipos de vida útil: estática (ejecución del programa), automática (ejecución de bloques, asignada en la pila) y manual (asignada en el montón). Solo se admiten las opciones estática y automática para las variables y son gestionadas por el compilador, mientras que la memoria asignada manualmente debe ser rastreada manualmente entre las diferentes variables. En C existen tres niveles de visibilidad: enlace externo (global), enlace interno (aproximadamente a nivel de archivo) y ámbito de bloque (que incluye funciones); los ámbitos de bloque pueden estar anidados, y es posible obtener diferentes niveles de enlace interno 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 notablemente todas las inclusiones relevantes.

Los programas en C se compilan como archivos objeto independientes , que luego se enlazan para formar un ejecutable o una biblioteca mediante 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 (o, de forma más general, "unidad de compilación", aunque este es un concepto distinto), y el enlazador, que resuelve los nombres entre unidades de traducción; consulte la sección sobre enlace para obtener más información.

En C, las variables con ámbito de bloque entran en contexto cuando se declaran (no al inicio 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 finaliza y salen de contexto al final del bloque. En el caso de las variables locales automáticas, también se asignan memoria al declararse y se liberan al final del bloque, mientras que para las variables locales estáticas, se asignan memoria al inicializar el programa y se liberan al finalizar el programa.

El siguiente programa demuestra cómo una variable con ámbito de bloque entra en contexto a mitad del bloque y luego sale de contexto (y de hecho se libera) cuando finaliza el bloque:

#include <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 produce la siguiente salida:

metro metro b metro 

En C existen otros niveles de ámbito. [ 23 ] Los nombres de variables utilizados en un prototipo de función tienen visibilidad dentro del 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 las etiquetas para la instrucción GOTO tienen ámbito de función.

C++

Todas las variables que se utilicen en un programa deben haberse declarado con su especificador de tipo en una parte anterior del código, como se hizo al inicio del cuerpo de la función mainal declarar que a, b y result eran de tipo int. Una variable puede ser de ámbito global o local. Una variable global es aquella que se declara en el cuerpo principal del código fuente, fuera de todas las funciones, mientras que una variable local es aquella que se declara dentro del cuerpo de una función o un bloque.

Las versiones modernas permiten un ámbito léxico anidado.

Rápido

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

Ir

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

Java

Java tiene un ámbito léxico.

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

Variables locales
Las variables se definen dentro de un método o un bloque específico. Estas variables son locales al lugar donde se definieron y a 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 finaliza.
Variables miembro
Los campos son variables declaradas dentro de la clase, fuera de cualquier método. Por defecto, estas variables están disponibles para todos los métodos 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 de 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. [ 25 ]

JavaScript

JavaScript tiene reglas de ámbito simples , [ 26 ] pero las reglas de inicialización de variables y resolución de nombres pueden causar problemas, y el uso generalizado de cierres para las funciones de devolución de llamada implica que el contexto léxico de una función cuando se define (que se utiliza 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 ámbito léxico [ 27 ] anidado a nivel de función, siendo el contexto global el contexto más externo. Este ámbito se utiliza tanto para variables como para funciones (es decir, declaraciones de funciones, a diferencia de variables de tipo función ). [ 28 ] El ámbito de bloque con las letpalabras constclave y es estándar desde ECMAScript 6. El ámbito de bloque se puede producir envolviendo todo el bloque en una función y luego ejecutándola; esto se conoce como el patrón de expresión de función invocada inmediatamente (IIFE).

Si bien el ámbito de JavaScript es simple (léxico, a nivel de función), las reglas de inicialización y resolución de nombres asociadas generan confusión. En primer lugar, la asignación a un nombre que no está dentro del ámbito crea por defecto 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 al inicio de la función con un valor undefinedy se le asigna dicho valor cuando se alcanza la expresión de asignación.

A una variable con un inicializador se le asigna el valor de su expresión de asignación cuando se ejecuta la instrucción de variable , no cuando se crea la variable. [ 29 ]

Esto se conoce como elevación de variables [ 30 ] : la declaración, pero no la inicialización, se eleva al principio de la función. En tercer lugar, acceder a las variables antes de la inicialización produce un error undefined, en lugar de un error de sintaxis. En cuarto lugar, para las declaraciones de funciones, tanto la declaración como la inicialización se elevan al principio de la función, a diferencia de la inicialización de variables. Por ejemplo, el siguiente código produce un diálogo con la siguiente salida:indefinido, dado que la declaración de la variable local se eleva, ocultando la variable global, pero la inicialización no, por lo que la variable no está definida cuando se utiliza:

a = 1 ; función f () { alerta ( a ); var a = 2 ; } f ();

Además, dado que las funciones son objetos de primera clase en JavaScript y se asignan frecuentemente como funciones de devolución de llamada o se devuelven desde otras funciones, cuando se ejecuta una función, la resolución de nombres depende de dónde se definió originalmente (el contexto léxico de la definición), no del contexto léxico o de ejecución donde se llama. Los ámbitos anidados de una función en particular (desde el más global hasta el más local) en JavaScript, especialmente de un cierre utilizado como función de 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 generar cierres mediante el uso de funciones anidadas, ya que las funciones son objetos de primera clase. [ 31 ] Al devolver una función anidada desde una función contenedora, se incluyen las variables locales de la función contenedora como 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 llamarlo (comenzando en 0) // y que devuelve su nuevo valor var a = 0 ; var b = function () { a ++ ; return a ; }; return b ; } c = newCounter (); alert ( c () + ' ' + c ()); // imprime "1 2"

Los cierres se utilizan con frecuencia en JavaScript, debido a su uso para las funciones de devolución de llamada. De hecho, cualquier enganche de una función en el contexto local como una función de devolución de llamada o su retorno desde una función crea un cierre si existen 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 ocurrir accidentalmente. Al crear una función de devolución de llamada basada en parámetros, estos deben almacenarse en un cierre; de ​​lo contrario, se creará accidentalmente un cierre que haga referencia a las variables del contexto que lo contiene, las cuales pueden cambiar. [ 32 ]

La resolución de nombres de las propiedades de los objetos JavaScript se basa en la herencia en el árbol de prototipos (una ruta hasta 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 de Lisp tienen diversas reglas para definir el alcance.

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

Maclisp utilizaba el ámbito dinámico por defecto en el intérprete y el ámbito léxico por defecto en el código compilado, aunque este último podía acceder a las vinculaciones dinámicas mediante SPECIALdeclaraciones para variables específicas. [ 33 ] Sin embargo, Maclisp trataba la vinculación léxica más como una optimización de lo que cabría esperar en los lenguajes modernos, y no incluía la característica de cierre*FUNCTION que se esperaría del ámbito léxico en los Lisp modernos. Existía una operación independiente, , para solucionar de forma algo engorrosa parte de este problema. [ 34 ]

Common Lisp adoptó el alcance léxico de Scheme , [ 35 ] al igual que Clojure .

ISLISP tiene ámbito 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, vincularse mediante una dynamic-letforma especial y accederse mediante una dynamicforma especial explícita. [ 36 ]

Algunos otros dialectos de Lisp, como Emacs Lisp , todavía usan ámbito dinámico por defecto. Emacs Lisp ahora tiene ámbito léxico disponible por búfer. [ 37 ]

Pitón

Python tiene ámbito de función, ámbito de módulo y ámbito global para las variables. Los nombres entran en contexto al inicio de un ámbito (función, módulo o ámbito global) y salen de contexto cuando se llama a una función no anidada o finaliza el ámbito. Si se usa un nombre antes de la inicialización de la variable, se produce una excepción en tiempo de ejecución. Si se accede a una variable (sin asignarle ningún valor), la resolución de nombres sigue la regla LEGB (Local, Enclosing, Global, Built-in), que resuelve los nombres al contexto relevante más específico. Sin embargo, si se le asigna un valor a una variable, por defecto se declara una variable cuyo ámbito 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`or` nonlocal(en Python 3) antes de su uso, lo que permite acceder a variables globales incluso si hay una variable no local que las enmascara, y asignar valores a variables globales o no locales.

Como ejemplo sencillo, una función resuelve una variable al ámbito global:

def f () -> None : print ( x )x : str = "global" f () # imprime: global

Tenga en cuenta que xse define antes de fque se llame, por lo que no se genera ningún error, aunque se defina después de su referencia en la definición de f. Léxicamente, esta es una referencia hacia adelante , 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 () -> None : x : str = "f" print ( x )x : str = "global" print ( x ) # imprime: global f () # imprime: f print ( x ) # imprime: global

La asignación a una variable dentro de una función hace que se declare como local a la función; por lo tanto, su ámbito es toda la función, y usarla antes de esta asignación genera un error. Esto difiere de C, donde el ámbito de la variable local comienza en su declaración. Este código genera un error:

def f () -> None : print ( x ) x : str = "f"x : str = "global" f () # Traceback (última llamada más reciente): # Archivo "<stdin>", línea 1, en <module> # Archivo "<stdin>", línea 2, en f # UnboundLocalError: la variable local 'x' se referencia antes de la asignación

Las reglas de resolución de nombres predeterminadas se pueden anular con las palabras clave global`or` nonlocal(en Python 3). En el código siguiente, la global xdeclaración en `or` gsignifica que xse resuelve a la variable global. Por lo tanto, se puede acceder a ella (ya que se ha definido previamente), y la asignación asigna un valor a la variable global, en lugar de declarar una nueva variable local. Nótese que no globalse necesita ninguna declaración en f`or`, ya que no asigna un valor a la variable, por defecto resuelve a la variable global.

def f () -> None : print ( x )def g () -> None : global x print ( x ) x = "g"x : str = "global" f () # imprime: global g () # imprime: global f () # imprime: 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 () -> None : def g () -> None : global x print ( x ) x : str = "f" g ()x : str = "global" f () # imprime: global

Para funciones anidadas, también existe la nonlocaldeclaración, para asignar a una variable no local, similar a usar globalen una función no anidada:

def f () -> None : def g () -> None : nonlocal x # Solo Python 3 x = "g" x : str = "f" g () print ( x )x : str = "global" f () # imprime: g print ( x ) # imprime: global

R

R es un lenguaje con ámbito 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. [ 38 ] Se puede acceder a los contextos de ámbito utilizando una variedad de características (como parent.frame()) que pueden simular la experiencia de ámbito dinámico si el programador lo desea.

No hay ámbito de bloque:

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

Las funciones tienen acceso al ámbito en el que fueron creadas:

a <- 1 f <- function () { mensaje ( a ) } f () ## 1

Las variables creadas o modificadas dentro de una función permanecen allí:

a <- 1 f <- function () { 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 a un ámbito superior:

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

Aunque R tiene ámbito léxico por defecto, los ámbitos de las funciones se pueden cambiar:

a <- 1 f <- function () { message ( a ) } my_env <- new.env () my_env $ a <- 2 f () ## 1 environment ( f ) <- my_env f () ## 2

Notas

  1. Consulte la definición para conocer el significado de "alcance" frente a "contexto".
  2. El "ámbito dinámico" basa la resolución de nombres en la extensión (tiempo de vida), no en el ámbito , y por lo tanto es formalmente inexacto.
  3. Por ejemplo, el motor de plantillas Jinja para Python utiliza por defecto tanto el ámbito léxico (para importaciones) como el ámbito dinámico (para inclusiones), y permite especificar el comportamiento con palabras clave; véase Comportamiento del contexto de importación .
  4. Los términos «resolución de nombres» y «vinculación de nombres» son prácticamente sinónimos; en sentido estricto, «resolución» determina a qué nombre se refiere un uso particular de un nombre, sin asociarlo a ningún significado, como en la sintaxis abstracta de orden superior , mientras que «vinculación» asocia el nombre a un significado real. En la práctica, ambos términos se usan indistintamente.
  5. En el caso de código automodificable, el contexto léxico en sí puede cambiar durante el tiempo de ejecución.
  6. Por el contrario, *"el contexto de una vinculación de nombre", *"una vinculación de nombre entrando en el ámbito" o *"una vinculación de nombre saliendo del ámbito" son todos incorrectos: una vinculación de nombre tiene ámbito, 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 Ámbitos de los identificadores, 7 de septiembre de 2007
  3. 1 2 Especificación del lenguaje de programación Go : Declaraciones y alcance , Versión del 13 de noviembre de 2013
  4. 1 2 3 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, Florida; Verde, 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. Las variables definidas dentro de una función no pueden ser accedidas desde fuera de ella, ya que solo se definen dentro del á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 se define.
  8. "N4720: Borrador de trabajo, extensiones a C++ para módulos" (PDF) . Archivado (PDF) del original el 30 de abril de 2019. Recuperado el 30 de abril de 2019 .
  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 de la 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. S2CID 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. " alcance léxico ", Organización de computadoras y programas, Parte 3 , pág. 18, en Google Books , Universidad de Michigan. Conferencias de verano de ingeniería, 1967
  17. " lexical scope ", Project MAC Progress Report, Volumen 8 , p. 80, en Google Books , 1970.
  18. "Cierres" . 4 de noviembre de 2025.
  19. Pozo Ramos, Leodanis (16 de julio de 2025). "Ámbito de Python y la regla LEGB: resolución de nombres en su código" .
  20. Hudson, Paul (9 de septiembre de 2020). "Elige una palabra, cualquiera: UIAlertController" .
  21. "Funciones de Python" . 4 de octubre de 2025.
  22. 1 2 Scott 2009 , 3.4 Implementación del alcance, pág. 143.
  23. " Alcance ", XL C/C++ V8.0 para Linux, IBM
  24. "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 .
  25. "Controlar el acceso a los miembros de una clase (Tutoriales de Java > Aprender el lenguaje Java > Clases y objetos)" . docs.oracle.com . Consultado el 19 de marzo de 2018 .
  26. " Todo lo que necesitas saber sobre el alcance de las variables en Javascript ", Saurab Parakh , Coding is Cool , 8 de febrero de 2010
  27. "ES5 anotado" . es5.github.io . Consultado el 19 de marzo de 2018 .
  28. "Funciones" . MDN Web Docs . Consultado el 19 de marzo de 2018 .
  29. " 12.2 Declaración de variable ", ECMAScript 5.1 anotado, Última actualización: 28/05/2012
  30. " Ámbito y elevación de variables en JavaScript ", Ben Cherry , Adequately Good , 8 de febrero de 2010
  31. Cierres de Javascript , Richard Cornford. Marzo de 2004
  32. " Explicando el ámbito y los cierres en JavaScript ", Robert Nyman, 9 de octubre de 2008
  33. 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 enlazar se ha declarado como especial, el enlace se compila como código para imitar la forma en que el intérprete enlaza las variables .
  34. Pitman, Kent (16 de diciembre de 2007). "The Revised Maclisp Manual (The Pitmanual), Sunday Morning Edition" . MACLISP.info . HyperMeta Inc. The Evaluator, Special Form . Recuperado el 20 de octubre de 2018. Está diseñado para ayudar a resolver el " problema funarg ", sin embargo , solo funciona en algunos casos sencillos.*FUNCTION*FUNCTION
  35. 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 Historial . Recuperado 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. 
  36. "Lenguaje de programación ISLISP, ISLISP Working Draft 23.0" (PDF) . ISLISP.info . 11.1 El principio léxico . Recuperado el 20 de octubre de 2018. Los enlaces dinámicos se establecen y se acceden mediante un mecanismo separado (es decir, , , y ) .defdynamicdynamic-letdynamic
  37. "Enlace léxico" . EmacsWiki . Consultado el 20 de octubre de 2018. Emacs 24 tiene enlace léxico opcional, que se puede habilitar para cada búfer.
  38. "Preguntas frecuentes sobre R" . cran.r-project.org . Consultado el 19 de marzo de 2018 .