Alcance (informática)

format_list_bulleted Contenido keyboard_arrow_down
ImprimirCitar
Parte de un programa informático donde un nombre determinado es válido

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 puede usarse para referirse a la entidad. En otras partes del programa, el nombre puede referirse a una entidad diferente (puede tener un enlace diferente) o a nada en absoluto (puede no estar enlazado). El ámbito ayuda a evitar colisiones de nombres al permitir que el mismo nombre se refiera a diferentes objetos, siempre que los nombres tengan ámbitos 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 desde la perspectiva de la entidad a la que se hace referencia, no del nombre de referencia.

El término "alcance" también se utiliza para referirse al conjunto de todos enlaces de nombre que son válidos dentro de una parte de un programa o en un punto dado de un programa, lo que se conoce más correctamente como contexto o entorno.

Estrictamente hablando y en la práctica para la mayoría de los lenguajes de programación, "parte de un programa" se refiere a una parte del código fuente (área de texto) y se conoce como alcance léxico. En algunos idiomas, sin embargo, "parte de un programa" se refiere a una parte del tiempo de ejecución (período de tiempo durante la ejecución) y se conoce como alcance dinámico. Ambos términos son un tanto engañosos (utilizan incorrectamente términos técnicos, como se explica en la definición), pero la distinción en sí es exacta y precisa, y estos son los términos estándar respectivos. El alcance léxico es el enfoque principal de este artículo, con el alcance dinámico entendido en contraste con el alcance léxico.

En la mayoría de los casos, la resolución de nombres basada en el alcance léxico es relativamente sencilla de usar e implementar, ya que en 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 levantamiento, 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) no es ambiguo: el ámbito léxico es "la parte del código fuente en la que se aplica la vinculación de un nombre con una entidad". Esto prácticamente no ha cambiado desde su definición de 1960 en la especificación de ALGOL 60. Las especificaciones de lenguaje representativas son las siguientes:

ALGOL 60 (1960)
Se distinguen los siguientes tipos de cantidades: variables simples, arrays, etiquetas, interruptores y procedimientos. El alcance de una cantidad es el conjunto de declaraciones y expresiones en las que la declaración del identificador asociado con esa cantidad es válida.
C (2007)
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 tipodef; un nombre de etiqueta; un nombre macro; o un parámetro 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 utilizar) sólo dentro de una región de texto del programa llamada su alcance.
Go (2013)
Una declaración vincula un identificador no negro 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 hacer referencia 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 frente a ámbito dinámico

Una distinción fundamental en el alcance es lo que "parte de un programa" significa. 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 el 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 alcance 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 alcance léxico, un nombre se resuelve buscando en el contexto léxico local, luego, si eso falla, buscando en el contexto léxico externo, y así sucesivamente; mientras que con el alcance dinámico, un nombre se resuelve buscando en el contexto de ejecución local, luego, si eso falla, buscando en el contexto de ejecución externo, y así sucesivamente, progresando en la pila de llamadas.

La mayoría de los lenguajes modernos usan alcance léxico para variables y funciones, aunque el alcance dinámico se usa en algunos lenguajes, especialmente en algunos dialectos de Lisp, algunos "scripting" idiomas y algunos idiomas de plantilla. Perl 5 ofrece un alcance tanto léxico como dinámico. Incluso en lenguajes con alcance léxico, el alcance de los cierres puede ser confuso para los no iniciados, ya que estos dependen del contexto léxico donde se define el cierre, no de dónde se llama.

La resolución léxica se puede determinar en tiempo de compilación y también se conoce como enlace temprano, mientras que la resolución dinámica en general 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 si el enlace de nombre real se realiza en tiempo de compilación o en tiempo de ejecución depende del idioma. El alcance dinámico de facto es común en los lenguajes de macros, que no hacen directamente la resolución de nombres, sino que se expanden en su lugar.

Algunos marcos de programación como AngularJS usan el término "alcance" para significar algo completamente diferente de cómo se usa en este artículo. En esos marcos, el alcance es solo un objeto del lenguaje de programación que usan (JavaScript en el caso de AngularJS) que el marco usa de cierta manera para emular el alcance dinámico en un lenguaje que usa el alcance léxico para sus variables. Esos ámbitos de AngularJS pueden estar en contexto o no en contexto (usando el significado habitual del término) en cualquier parte del programa, siguiendo las reglas habituales de ámbito variable del lenguaje como cualquier otro objeto, y utilizando su propia herencia y reglas de transclusión. En el contexto de AngularJS, a veces el término "$scope" (con un signo de dólar) se usa para evitar confusiones, pero las guías de estilo a menudo desaconsejan el uso del signo de dólar en nombres de variables.

Usar

El alcance es un componente importante de la resolución de nombres, que a su vez es fundamental para la semántica del lenguaje. La resolución de nombres (incluido el alcance) varía entre los 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 rompe una parte no relacionada.

Resumen

Al hablar del alcance, hay tres conceptos básicos: alcance, extensión y contexto. "Alcance" y "contexto" en particular, se confunden con frecuencia: el alcance es una propiedad de un enlace de nombre, mientras que el contexto es una propiedad de una parte de un programa, que es una parte del código fuente (contexto léxico o estática contexto) o una parte del tiempo de ejecución (contexto de ejecución, contexto de tiempo de ejecución, contexto de llamada o contexto dinámico). El contexto de ejecución consiste en un contexto léxico (en el punto de ejecución actual) más un estado de tiempo de ejecución adicional, como la pila de llamadas. Estrictamente hablando, durante la ejecución, un programa entra y sale de varios enlaces de nombres. ámbitos, y en un punto de la ejecución, los enlaces de nombres están "en contexto" o "no en contexto", por lo tanto, los enlaces de nombre "entran en contexto" o "salir de contexto" a medida que la ejecución del programa entra o sale del alcance. Sin embargo, en la práctica el uso es mucho más flexible.

El alcance es un concepto a nivel de código fuente y una propiedad de 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 idioma Como tales, los problemas de alcance son similares a los punteros, que son un tipo de referencia que se usa en los 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 variables, el alcance es un subconjunto de la duración (también conocido como extensión): un nombre solo puede referirse a una variable que existe (posiblemente con un valor indefinido), pero las variables que existen no son necesariamente visibles: una variable puede existir pero ser inaccesible (el valor se almacena pero no se hace referencia a él dentro de un contexto dado), o accesible pero no a través del nombre dado, en cuyo caso no está en contexto (el programa está "fuera del alcance de el nombre"). En otros casos, "de por vida" es irrelevante: una etiqueta (posición con nombre en el código fuente) tiene una vida útil idéntica a la del programa (para lenguajes compilados estáticamente), pero puede estar en contexto o no en un punto dado del programa, y lo mismo ocurre con las variables estáticas: un global estático La variable 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 la duración de la ejecución completa 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 (correctamente, el compilador o el 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 una variable 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 un "contexto interno a externo" regla, como la regla Python LEGB (Local, Enclosing, Global, Built-in): los nombres se resuelven implícitamente en el contexto relevante más limitado. En algunos casos, la resolución de nombres puede especificarse explícitamente, como por las palabras clave global y nonlocal en Python; en otros casos, las reglas predeterminadas no se pueden anular.

Cuando dos nombres idénticos están en contexto al mismo tiempo, refiriéndose a diferentes entidades, uno dice que se está produciendo enmascaramiento de nombres, donde el nombre de mayor prioridad (generalmente el más interno) es " enmascaramiento" el nombre de menor prioridad. A nivel de variables, esto se conoce como sombreado de variables. Debido al potencial de errores lógicos del enmascaramiento, algunos lenguajes no permiten o desaconsejan el enmascaramiento, generando un error o advertencia en tiempo de compilación o tiempo de ejecución.

Diversos lenguajes de programación tienen varias reglas de alcance diferentes para diferentes tipos de declaraciones y nombres. Tales reglas de alcance tienen un gran efecto en la semántica del lenguaje y, en consecuencia, en el comportamiento y corrección de los programas. En lenguajes como C++, el acceso a una variable independiente no tiene una semántica bien definida y puede generar un comportamiento indefinido, similar a referirse a un puntero colgante; y declaraciones o nombres usados fuera de su alcance generarán errores de sintaxis.

Los ámbitos suelen estar vinculados a construcciones de otros lenguajes y determinados implícitamente, pero muchos idiomas 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 posibles en el medio. La regla de alcance más simple es el alcance global: todas las entidades son visibles en todo el programa. La regla de alcance modular más básica es el alcance de dos niveles, con un alcance global en cualquier parte del programa y un alcance local dentro de una función. La programación modular más sofisticada permite un alcance de módulo separado, donde los nombres son visibles dentro del módulo (privados para el módulo) pero no visibles fuera de él. Dentro de una función, algunos lenguajes, como C, permiten que el alcance del bloque restrinja el alcance a un subconjunto de una función; otros, en particular los lenguajes funcionales, permiten el alcance de la expresión, para restringir el alcance a una sola expresión. Otros ámbitos incluyen el ámbito del archivo (sobre todo en C), que se comporta de forma similar al ámbito del módulo, y el ámbito del bloque fuera de las funciones (sobre todo en Perl).

Un problema sutil es exactamente cuándo comienza y termina un alcance. En algunos lenguajes, como C, el alcance de un nombre comienza en la declaración del nombre y, por lo tanto, diferentes nombres declarados dentro de un bloque determinado pueden tener diferentes alcances. Esto requiere declarar funciones antes de su uso, aunque no necesariamente definirlas, y requiere una declaración directa en algunos casos, especialmente para la recurrencia mutua. En otros lenguajes, como Python, el alcance 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 dado tienen el mismo alcance. En JavaScript, el alcance de un nombre declarado con let o const comienza en la declaración del nombre, y el alcance de un nombre declarado con var comienza al inicio de la función donde se declara el nombre, lo que se conoce como elevación de variables. El comportamiento de los nombres en contexto que tienen un valor indefinido difiere: en Python, el uso de nombres indefinidos produce un error de tiempo de ejecución, mientras que en JavaScript, los nombres indefinidos declarados con var se pueden usar en toda la función porque están vinculados implícitamente al valor indefinido.

Ámbito de expresión

El ámbito de un enlace de nombre es una expresión, que se conoce como ámbito de expresión. El alcance de la expresión está disponible en muchos idiomas, especialmente en los lenguajes funcionales que ofrecen una característica llamada let-expressions que permite 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 el aprendizaje automático estándar, si f() devuelve 12, entonces let val x = f() in x * x end es una expresión que se evalúa como 144, usando una variable temporal llamada x para evitar llamar f() 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 ML estándar antes mencionada podría escribirse en Perl como do { mi $x = f(); $x * $x }, o en GNU C como ({ int x = f(); x * x; }) .

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

En C, los nombres de las variables en un prototipo de función tienen un alcance de expresión, conocido en este contexto como alcance del protocolo de función. Como no se hace referencia a los nombres de las variables en el prototipo (pueden ser diferentes en la definición real), son solo maniquíes, a menudo se omiten, aunque pueden usarse para generar documentación, por ejemplo.

Ámbito de bloque

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

no firmado int sum_of_squares()const no firmado int N) {} no firmado int Ret = 0; para ()no firmado int n = 1; n . N; n++) {} const no firmado int n_squared = n * n; Ret += n_squared; } retorno Ret;}

Un ejemplo representativo del uso del ámbito de bloque es el código C que se muestra aquí, donde dos variables tienen como ámbito el bucle: la variable de bucle n, que se inicializa una vez y se incrementa en cada iteración de el bucle y la variable auxiliar n_squared, que se inicializa en cada iteración. El propósito es evitar agregar variables al alcance de la función que solo son relevantes para un bloque en particular; por ejemplo, esto evita errores en los que la variable de bucle genérica i ya se ha establecido accidentalmente en otro valor. En este ejemplo, la expresión n * n generalmente no se asignaría a una variable auxiliar, y el cuerpo del ciclo simplemente se escribiría ret += n * n pero en ejemplos más complicados son útiles las variables auxiliares.

Los bloques se utilizan principalmente para controlar el flujo, como los bucles if, while y for, y en estos casos el alcance del bloque significa que el alcance de la variable depende de la estructura del flujo de ejecución de una función. Sin embargo, los lenguajes con ámbito de bloque normalmente también permiten el uso de "naked" bloques, cuyo único propósito es permitir un control detallado del alcance variable. Por ejemplo, una variable auxiliar puede definirse en un bloque, luego usarse (digamos, agregarse a una variable con alcance de función) y descartarse cuando finaliza el bloque, o un bucle while puede encerrarse en un bloque que inicializa las variables utilizadas dentro del bucle. que solo debe inicializarse una vez.

Una sutileza de varios lenguajes de programación, como Algol 68 y C (demostrado en este ejemplo y estandarizado desde C99), es que las variables de ámbito de bloque se pueden declarar no solo dentro del cuerpo del bloque, sino también dentro del control. declaración, si la hubiere. Esto es análogo a los parámetros de la 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 en el ámbito de todo el cuerpo de la función. Esto se usa principalmente en bucles for, que tienen una declaración de inicialización separada de la condición del bucle, a diferencia de los bucles while, y es un idioma común.

El alcance del bloque se puede usar para sombrear. 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 de estilo deficiente debido a la posibilidad de errores. Además, algunos descendientes de C, como Java y C#, a pesar de tener soporte para el alcance del bloque (en el sentido de que se puede hacer que una variable local salga de contexto antes del final de una función), no permiten que una variable local oculte a otra.. En tales lenguajes, el intento de declaración del segundo n daría como resultado un error de sintaxis, y una de las variables n tendría que ser renombrada.

Si se usa un bloque para establecer el valor de una variable, el alcance del bloque requiere que la variable se declare fuera del bloque. Esto complica el uso de sentencias condicionales con asignación única. Por ejemplo, en Python, que no usa alcance de bloque, uno puede inicializar una variable como tal:

si c: a = "foo"más: a = "

donde se puede acceder a a después de la instrucción if.

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

# $a;si ()c) {} $a = 'foo ';} más {} $a = ' ';}

A menudo, esto se reescribe mediante una asignación múltiple, inicializando la variable a un valor predeterminado. En Python (donde no es necesario) esto sería:

a = "si c: a = "foo"

mientras que en Perl esto sería:

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

En el caso de una asignación de una sola variable, una alternativa es usar el operador ternario para evitar un bloque, pero esto no es posible en general 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 cadenas y verificar que se realicen correctamente.

{} # Cuenta de $ = 0; sub increase_counter {} retorno ++Cuenta de $; }}

Algunos lenguajes permiten aplicar el concepto de ámbito de bloque, en mayor o menor medida, fuera de una función. Por ejemplo, en el fragmento de Perl de la derecha, $counter es un nombre de variable con alcance de bloque (debido al uso de la palabra clave my), mientras que increment_counter es un nombre de función con alcance global. Cada llamada a increment_counter aumentará el valor de $counter en uno y devolverá el nuevo valor. El código fuera de este bloque puede llamar a increment_counter, pero de otro modo no puede obtener ni alterar el valor de $counter. Esta expresión 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. El alcance de la función está disponible en la mayoría de los lenguajes de programación que ofrecen una forma de crear una variable local en una función o subrutina: una variable cuyo alcance finaliza (que se sale de contexto) cuando la función regresa. En la mayoría de los casos, la vida útil de la variable es la duración de la llamada a la función: es una variable automática, creada cuando se inicia la función (o se declara la variable), se destruye cuando la función regresa, mientras que el alcance de la variable está dentro del función, aunque el significado de "dentro" depende de si el á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 la vida útil completa del programa, pero la variable solo está en contexto cuando está dentro de la función. En el caso de las variables locales estáticas, la variable se crea cuando el programa se inicializa y se destruye solo cuando el programa finaliza, como 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 sólo tiene ámbito dentro del contexto léxico de la función: se 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 regresa, las funciones llamadas no tienen acceso a las variables locales de las funciones llamantes, y las variables locales solo están en contexto dentro del cuerpo de la función en la que se declaran. Por el contrario, en el alcance dinámico, el alcance se extiende al contexto de ejecución de la función: las variables locales permanecen en contexto cuando se llama a otra función, solo salen de contexto cuando el la función de definición termina 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 alcance léxico y funciones anidadas, las variables locales están en contexto para funciones anidadas, ya que estas están dentro del mismo contexto léxico, pero no para otras funciones que no están anidadas léxicamente. Una variable local de una función envolvente se conoce como variable no local para la función anidada. El alcance de la función también se aplica a las funciones anónimas.

def cuadrado()n): retorno n * ndef sum_of_squares()n): total = 0 i = 0 mientras i . n: total += cuadrado()i) i += 1 retorno total

Por ejemplo, en el fragmento de código Python de la derecha, se definen dos funciones: square y sum_of_squares. square calcula el cuadrado de un número; sum_of_squares calcula la suma de todos los cuadrados hasta un número. (Por ejemplo, square(4) es 42 = 16, y sum_of_squares(4) es 02 + 12 + 22 + 32 + 42 = 30.)

Cada una de estas funciones tiene una variable llamada n que representa el argumento de la función. Estas dos variables n están completamente separadas y no relacionadas, a pesar de tener el mismo nombre, porque son variables locales con alcance léxico con alcance de función: el alcance de cada una es su propia función separada léxicamente y, por lo tanto,, no se superponen. Por lo tanto, sum_of_squares puede llamar a square sin que se altere su propio n. De manera similar, sum_of_squares tiene variables llamadas total y 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 está enmascarando el nombre: 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 alcance dinámico, n en la función de llamada permanecería en contexto en la función llamada (los alcances se superpondrían) y estarían enmascarados (& #34;sombreado") por el nuevo 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 devolverlos. 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) crean un cierre, ya que no solo la función en sí, sino también su contexto (de variables).) debe devolverse y luego llamarse potencialmente en un contexto diferente. Esto requiere mucho más apoyo del compilador y puede complicar el análisis del programa.

Alcance del archivo

El ámbito de un enlace de nombre es un archivo, que se conoce como ámbito de archivo. El alcance del archivo es en gran parte particular de C (y C++), donde el alcance de las variables y funciones declaradas en el nivel superior de un archivo (no dentro de ninguna función) es para todo el archivo, o más bien para C, desde la declaración hasta el final de el archivo fuente, o más precisamente la unidad de traducción (enlace interno). Esto puede verse como una forma de alcance de módulo, donde los módulos se identifican con archivos y, en lenguajes más modernos, se reemplaza por un alcance de módulo explícito. Debido a la presencia de declaraciones de inclusión, que agregan variables y funciones al contexto interno y pueden llamar a más declaraciones de inclusión, puede ser difícil determinar qué hay en contexto en el cuerpo de un archivo.

En el fragmento de código C anterior, el nombre de la función sum_of_squares tiene ámbito de archivo.

Alcance del módulo

El ámbito de un enlace de nombre es un módulo, que se conoce como ámbito de módulo. El alcance del módulo está disponible en lenguajes de programación modulares donde los módulos (que pueden abarcar varios archivos) son la unidad básica de un programa complejo, ya que permiten ocultar y exponer información en una interfaz limitada. El alcance del módulo fue pionero en la familia de lenguajes Modula, y Python (que fue influenciado por Modula) es un ejemplo contemporáneo representativo.

En algunos lenguajes de programación orientados a objetos que carecen de soporte directo para módulos, como C++, la jerarquía de clases proporciona una estructura similar, donde las clases son la unidad básica del programa y una clase puede tener métodos privados. Esto se entiende correctamente en el contexto del envío dinámico en lugar de la resolución de nombres y el alcance, aunque a menudo desempeñan funciones análogas. En algunos casos, ambas funciones están disponibles, como en Python, que tiene tanto módulos como 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 mundial

El ámbito de un enlace de nombre es un programa completo, que se conoce como ámbito 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 no intencional, junto con una modularidad deficiente y un alcance o bloqueo de funciones. alcance se consideran preferibles. Sin embargo, el alcance global se usa normalmente (según el idioma) para otros tipos de nombres, como nombres de funciones, nombres de clases y nombres de otros tipos de datos. En estos casos se utilizan mecanismos como los espacios de nombres para evitar colisiones.

Ámbito léxico frente a ámbito dinámico

El uso de variables locales, de nombres de variables con alcance limitado, que solo existen dentro de una función específica, ayuda a evitar el riesgo de una colisión de nombres entre dos variables con nombres idénticos. Sin embargo, existen dos enfoques muy diferentes para responder a esta pregunta: ¿Qué significa estar "dentro de" ¿Una función?

En ámbito léxico (o ámbito léxico; también llamado ámbito estático o ámbito estático), si un el alcance del nombre de la variable es una determinada función, entonces su alcance 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 al valor de la variable, pero fuera de ese texto, el nombre de la variable no existe. Por el contrario, en alcance dinámico (o alcance dinámico), si el alcance de un nombre de variable es una determinada función, entonces su alcance es el período de tiempo durante el cual la función se está ejecutando: 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 f invoca una función definida por separado g, entonces bajo el alcance léxico, la función g no tener acceso a las variables locales de f (asumiendo que el texto de g no está dentro del texto de f), mientras que bajo dinámica alcance, la función g no tiene acceso a las variables locales de f (ya que g se invoca durante la invocación de f).

$ # Bash language$ x=1$ función g() {} eco $x ; x=2 ; }$ función f() {} local x=3 ; g ; }$ f # ¿Tiene esta impresión 1, o 3?3$ eco $x # ¿Tiene esta impresión 1, o 2?1

Considere, por ejemplo, el programa de la derecha. La primera línea, x=1, crea una variable global x y la inicializa en 1. La segunda línea, función g() { echo $x ; x=2 ; }, define una función g que imprime ("hace eco") del valor actual de x, y luego establece x en 2 (sobrescribiendo el valor anterior). La tercera línea, función f() { local x=3 ; g ; } define una función f que crea una variable local x (oculta la variable global con el mismo nombre) y lo inicializa a 3, y luego llama a g. La cuarta línea, f, llama a f. La quinta línea, echo $x, imprime el valor actual de x.

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

Ámbito léxico

Con alcance 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 del programa estático, este tipo de ámbito también se denomina ámbito estático. El alcance léxico es estándar en todos los lenguajes basados en ALGOL, como Pascal, Modula-2 y Ada, así como en 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 crear código modular y razonar sobre él, ya que la estructura de nomenclatura local se puede entender de forma aislada. Por el contrario, el alcance dinámico obliga al programador a anticipar todos los contextos de ejecución posibles en los que se puede invocar el código del módulo.

programa A;Var I:entero; K:char; procedimiento B; Var K:real; L:entero; procedimiento C; Var M:real; comenzar (*scope A+B+C*) final; (*scopio A+B*) final; (*scopio A*)final.

Por ejemplo, Pascal tiene un alcance léxico. Considere el fragmento del programa Pascal a la derecha. La variable I es visible en todos los puntos, porque nunca está oculta por otra variable del mismo nombre. La variable char K solo es visible en el programa principal porque está oculta por la variable real K visible en procedimiento B y C únicamente. La variable L también es visible solo en el procedimiento B y C pero no oculta ninguna otra variable. La variable M solo es visible en el procedimiento C y, por lo tanto, no se puede acceder a ella ni desde el procedimiento B ni desde el programa principal. Además, el procedimiento C solo es visible en el procedimiento B y, por lo tanto, no se puede llamar desde el programa principal.

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

La implementación correcta del alcance léxico en lenguajes con funciones anidadas de primera clase no es trivial, ya que requiere que cada valor de función lleve consigo un registro de los valores de las variables de las que depende (el par de la función y este contexto se llama un cierre). Según la implementación y la arquitectura de la computadora, la búsqueda de variables puede volverse ligeramente ineficaz cuando se utilizan funciones anidadas léxicamente muy profundas, aunque existen técnicas bien conocidas para mitigar esto. Además, para las funciones anidadas que solo se refieren a sus propios argumentos y (inmediatamente) a las variables locales, todas las ubicaciones relativas se pueden conocer en el momento de la compilación. Por lo tanto, no se incurre en gastos generales cuando se utiliza 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 idioma imperativo ALGOL 60 y se ha adoptado en la mayoría de los otros idiomas imperativos desde entonces.

Los lenguajes como Pascal y C siempre han tenido un alcance léxico, ya que ambos están influenciados por las ideas que se incluyeron en ALGOL 60 y ALGOL 68 (aunque C no incluía funciones léxicamente anidadas).

Perl es un lenguaje con alcance dinámico que luego agregó alcance estático.

El intérprete Lisp original (1960) usaba alcance dinámico. Enlace profundo, que se aproxima al alcance estático (léxico), se introdujo alrededor de 1962 en LISP 1.5 (a través del dispositivo Funarg desarrollado por Steve Russell, trabajando con John McCarthy).

Todos los primeros Lisps usaban alcance dinámico, cuando se basaban en intérpretes. En 1982, Guy L. Steele Jr. y Common LISP Group publican Una descripción general de Common LISP, una breve revisión de la historia y las implementaciones divergentes 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 en que por defecto el intérprete y compilador pueden asignar diferentes semántica a los programas correctos; esto se deriva principalmente del hecho de que el intérprete asume que todas las variables tienen alcance dinámico, mientras que el compilador asume que todas las variables son locales a menos que se vea obligado a asumir lo contrario. Esto se ha hecho por conveniencia y eficiencia, pero puede llevar a errores muy sutiles. La definición de Common LISP evita tales anomalías exigiendo explícitamente al intérprete y compilador que impongan semántica idéntica a los programas correctos.

Por lo tanto, se requería que las implementaciones de Common LISP tuvieran alcance léxico. Nuevamente, de Una descripción general de Common LISP:

Además, Common LISP ofrece las siguientes instalaciones (la mayoría de las cuales son prestadas desde MacLisp, InterLisp o Lisp Machines Lisp): (...) Variables de alcance completo. El llamado "problema de FUNARG" está completamente resuelto, tanto en los casos hacia abajo como hacia arriba.

Para el mismo año en que se publicó Una descripción general de Common LISP (1982), los diseños iniciales (también por Guy L. Steele Jr.) de un Lisp compilado con alcance léxico, llamado Scheme y se estaban intentando implementaciones del compilador. En ese momento, se temía comúnmente que el alcance léxico en Lisp fuera ineficiente de implementar. En A History of T, Olin Shivers escribe:

Todos Lisps serios en el uso de la producción en ese momento tenían un alcance dinámico. Nadie que no hubiera leído cuidadosamente la tesis del conejo (escrita por Guy Lewis Steele Jr. en 1978) creía que el alcance léxico volaría; incluso las pocas personas que había leía que estaba tomando un poco de fe que esto iba a funcionar en serio uso de la producción.

El término "alcance léxico" data al menos de 1967, mientras que el término "alcance léxico" data al menos de 1970, cuando se usó en Project MAC para describir las reglas de alcance del dialecto Lisp MDL (entonces conocido como "Muddle").

Alcance dinámico

Con alcance dinámico, un nombre se refiere al contexto de ejecución. En términos técnicos, esto significa que cada nombre tiene una pila global de enlaces. Al introducir una variable local con el nombre x, se inserta un enlace en la pila global x (que puede haber estado vacía), que se elimina cuando el flujo de control abandona el alcance. Evaluar x en cualquier contexto siempre produce el enlace superior. Tenga en cuenta que esto no se puede hacer en tiempo de compilación porque la pila de enlace solo existe en tiempo de ejecución, razón por la cual este tipo de alcance se llama alcance dinámico.

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

Generalmente, ciertos bloques se definen para crear enlaces cuyo tiempo de vida es el tiempo de ejecución del bloque; esto agrega algunas características de alcance estático al proceso de alcance dinámico. Sin embargo, dado que se puede llamar a una sección de código desde muchas ubicaciones y situaciones diferentes, puede 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 de mínimo conocimiento sugiere que el código evite depender de las razones para (o circunstancias de) el valor de una variable, sino que simplemente use el valor de acuerdo con la definición de la variable.. Esta interpretación restringida de los datos compartidos puede proporcionar un sistema muy flexible para adaptar el comportamiento de una función al estado (o política) actual del sistema. Sin embargo, este beneficio se basa en la documentación cuidadosa de todas las variables utilizadas de esta manera, así como en la evitación cuidadosa de suposiciones sobre el comportamiento de una variable, y no proporciona ningún mecanismo para detectar interferencias entre diferentes partes de un programa. Algunos lenguajes, como Perl y Common Lisp, permiten al programador elegir un alcance estático o dinámico al definir o redefinir una variable. Los ejemplos de lenguajes que usan el alcance dinámico incluyen 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 atravesar la pila de tiempo de ejecución, comprobando cada registro de activación (marco de pila de cada función) para obtener un valor para el nombre. En la práctica, esto se hace más eficiente mediante el uso de una lista de asociaciones, que es una pila de pares de nombre/valor. Los pares se colocan en esta pila cada vez que se realizan declaraciones y se abren cada vez que las variables se salen de contexto. El enlace superficial es una estrategia alternativa que es considerablemente más rápida y 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 para mantener correctamente esta tabla. Tenga en cuenta que ambas estrategias asumen un orden de último en entrar, primero en salir (LIFO) para los enlaces para cualquier variable; en la práctica todas las encuadernaciones se ordenan así.

Una implementación aún más simple es la representación de variables dinámicas con variables globales simples. El enlace local se realiza guardando el valor original en una ubicación anónima en la pila que es invisible para el programa. Cuando finaliza ese ámbito de vinculación, el valor original se restaura desde esta ubicación. De hecho, el alcance dinámico se originó de esta manera. Las primeras implementaciones de Lisp usaron esta estrategia obvia para implementar variables locales, y la práctica sobrevive en algunos dialectos que todavía están en uso, como GNU Emacs Lisp. El alcance léxico se introdujo en Lisp más tarde. Esto es equivalente al esquema de 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 de símbolo puede tener una ranura dedicada para su valor global.

El alcance dinámico proporciona una excelente abstracción para el almacenamiento local de subprocesos, pero si se usa de esa manera, no se puede basar en guardar y restaurar una variable global. Una posible estrategia de implementación es que cada variable tenga una clave local de subproceso. Cuando se accede a la variable, la clave local del subproceso se usa para acceder a la ubicación de la memoria local del subproceso (mediante el código generado por el compilador, que sabe qué variables son dinámicas y cuáles son léxicas). Si la clave local del subproceso no existe para el subproceso que llama, se usa la ubicación global. Cuando una variable está vinculada 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 adicionales anidadas de la variable dentro de ese subproceso simplemente guardan y restauran esta ubicación local del subproceso. Cuando finaliza el contexto inicial de anulación más externo, la clave local del subproceso se elimina, exponiendo la versión global de la variable una vez más a ese subproceso.

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

Expansión de macros

En los lenguajes modernos, la expansión de macros en un preprocesador es un ejemplo clave del alcance dinámico de facto. El lenguaje de macros en sí solo transforma el código fuente, sin resolver los nombres, pero dado que la expansión se realiza en el lugar, cuando los nombres en el texto expandido se resuelven (en particular, las variables libres), se resuelven en función de dónde se expanden (vagamente "llamado"), 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 hace 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 a a la variable pasada, con este nombre solo más tarde resuelto por el compilador en función de dónde se "llame&#34 a la macro ADD_A; (correctamente, ampliado). Correctamente, el preprocesador C solo realiza análisis léxico, expandiendo la macro durante la etapa de tokenización, pero no analizando en un árbol de sintaxis ni haciendo resolución de nombres.

Por ejemplo, en el siguiente código, el nombre a en 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 add_one()int *x) {} const int a = 1; *x = ADD_A()*x);}vacío add_two()int *x) {} const int a = 2; *x = ADD_A()*x);}

Nombres calificados

Como hemos visto, una de las razones clave del alcance es que ayuda a evitar colisiones de nombres, al permitir que nombres idénticos se refieran a cosas distintas, con la restricción de que los nombres deben tener alcances separados. A veces esta restricción es inconveniente; cuando se necesita acceder a muchas cosas diferentes a través de un programa, generalmente todas necesitan nombres con alcance global, por lo que se requieren diferentes técnicas para evitar colisiones de nombres.

Para abordar esto, muchos idiomas ofrecen mecanismos para organizar nombres globales. Los detalles de estos mecanismos y los términos utilizados dependen del idioma; pero la idea general es que a un grupo de nombres se le puede dar un nombre (un prefijo) y, cuando sea necesario, se puede hacer referencia a una entidad mediante un nombre calificado que consiste en el nombre más el prefijo. Normalmente, tales nombres tendrán, en cierto sentido, dos conjuntos de ámbitos: un ámbito (generalmente el ámbito global) en el que el nombre calificado es visible, y uno o más ámbitos más limitados en los que el nombre no calificado (sin el prefijo) también es visible. Y normalmente estos grupos se pueden organizar en grupos; es decir, se pueden anidar.

Aunque muchos idiomas admiten este concepto, los detalles varían mucho. Algunos lenguajes tienen mecanismos, como espacios de nombres en C++ y C#, que sirven casi exclusivamente para permitir que los nombres globales se organicen en grupos. Otros lenguajes tienen mecanismos, como paquetes en Ada y estructuras en Standard ML, que combinan esto con el propósito adicional de permitir que algunos nombres sean visibles solo para otros miembros de su grupo. Y los lenguajes orientados a objetos a menudo permiten que las clases o los 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 idiomas a menudo fusionan estos enfoques; por ejemplo, los paquetes de Perl son muy similares a los espacios de nombres de C++, pero opcionalmente 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 tipo Ada.

Por idioma

Siguen las reglas de alcance para los idiomas representativos.

C

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

La vida útil y la visibilidad de una variable están determinadas por su clase de almacenamiento. Hay tres tipos de tiempos de vida en C: estático (ejecución de programa), automático (ejecución de bloque, asignado en la pila) y manual (asignado en el montón). Solo las variables estáticas y automáticas son compatibles 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 (más o menos archivo) y alcance de bloque (que incluye funciones); los ámbitos de bloque se pueden anidar, y es posible diferentes niveles de vinculación interna 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, que incluye en particular todas las inclusiones relevantes.

Los programas C se compilan como archivos de objetos separados, que luego se vinculan a un ejecutable o biblioteca a través de un vinculador. Por lo tanto, la resolución de nombres se divide entre el compilador, que resuelve nombres dentro de una unidad de traducción (más vagamente, "unidad de compilación", pero este es un concepto diferente), y el enlazador, que resuelve nombres entre unidades de traducción; ver enlace para más discusión.

En C, las variables con ámbito de bloque entran en contexto cuando se declaran (no en la parte superior del bloque), salen de contexto si se llama a alguna función (no anidada) dentro del bloque, vuelven al contexto cuando la función regresa y sale de contexto al final del bloque. En el caso de las variables locales automáticas, también se asignan en la declaración y se desasignan al final del bloque, mientras que para las variables locales estáticas, se asignan en la inicialización del programa y se desasignan al finalizar el programa.

El siguiente programa muestra una variable con alcance de bloque que entra en contexto a la mitad del bloque, luego sale del contexto (y, de hecho, se desasigna) cuando finaliza el bloque:

#include Identificado.hint principal()vacío) {} char x = 'm '; printf()"%cn", x); {} printf()"%cn", x); char x = 'b '; printf()"%cn", x); } printf()"%cn", x);}

Las salidas del programa:

m
m
b
m

Hay otros niveles de alcance en C. Los nombres de variables utilizados en un prototipo de función tienen visibilidad de prototipo de función y contexto de salida al final del prototipo de función. Dado que no se utiliza el nombre, esto no es útil para la compilación, pero puede ser útil para la documentación. Los nombres de las etiquetas para la declaración GOTO tienen un alcance de función, mientras que los nombres de las etiquetas de casos para las declaraciones de cambio tienen un alcance de bloque (el bloque del cambio).

C++

Todas las variables que pretendemos utilizar en un programa deben haber sido declaradas con su especificador de tipo en un punto en el código, como hicimos en el código anterior al principio del cuerpo de la función principal cuando declaró 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 ámbitos con C++, pero contiene diferentes modificadores de acceso.

ModificadorAlcance inmediatoArchivoMódulo/paquete que contieneEl resto del mundo
abiertoSí.Sí.Sí.Sí, permite subclase
públicoSí.Sí.Sí.Sí, disallows subclass
internaSí.Sí.Sí.No
archivo privadoSí.Sí.NoNo
privadoSí.NoNoNo

Ir

Go tiene un alcance léxico usando bloques.

Java

Java tiene un alcance léxico.

Una clase Java puede contener tres tipos de variables:

Variables locales
se definen dentro de un método, o un bloque particular. Estas variables son locales a donde fueron definidas y menores niveles. Por ejemplo, un bucle dentro de un método puede utilizar las variables locales de ese método, pero no del otro modo. Las variables del bucle (local a ese bucle) se destruyen tan pronto como termine el bucle.
Variables de los miembros
también llamado 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 dentro de esa clase y también para todas las clases en el paquete.
Parámetros
son variables en declaraciones de método.

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 según las palabras clave modificadoras utilizadas en su definición. La siguiente tabla muestra el acceso a los miembros permitido por cada modificador.

ModificadorClasePaqueteSubclaseMundo
públicoSí.Sí.Sí.Sí.
protegidaSí.Sí.Sí.No
(sin modificador)Sí.Sí.NoNo
privadoSí.NoNoNo

Javascript

JavaScript tiene reglas de alcance simples, 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 llamada significa que el contexto léxico de una función cuando se define (que se usa para el nombre resolución) 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 anidado en el nivel de función, siendo el contexto global el contexto más externo. Este alcance se usa tanto para variables como para funciones (es decir, declaraciones de funciones, a diferencia de las variables de tipo función). El alcance del bloque con las palabras clave let y const es estándar desde ECMAScript 6. El alcance del bloque se puede producir envolviendo el bloque completo en una función y luego ejecutándolo; esto se conoce como patrón de expresión de función inmediatamente invocada (IIFE).

Si bien el alcance de JavaScript es simple (léxico, a nivel de función), las reglas de resolución de nombres y inicialización asociadas son causa de confusión. En primer lugar, la asignación a un nombre que no está en el alcance por defecto es crear una nueva variable global, no una local. En segundo lugar, para crear una nueva variable local se debe utilizar la palabra clave var; la variable se crea luego en la parte superior de la función, con valor undefined y se le asigna su valor a la variable cuando se alcanza la expresión de asignación:

Una variable con una Inicial se asigna el valor de su AsignaciónExpresión cuando Estado variable se ejecuta, no cuando se crea la variable.

Esto se conoce como elevación de variables: 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 salida indefinido, ya 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 usa:

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 con frecuencia se asignan como devoluciones de llamada o se devuelven desde funciones, cuando se ejecuta una función, la resolución del nombre depende de dónde se definió originalmente (el contexto léxico de la definición), no el contexto léxico o el contexto de ejecución donde se llama. Los ámbitos anidados de una función particular (de la más global a la más local) en JavaScript, en particular de un cierre, que se utiliza como devolución de llamada, a veces se denominan cadena de ámbito, por analogía con la cadena prototipo. de un objeto

Los cierres se pueden producir en JavaScript usando funciones anidadas, ya que las funciones son objetos de primera clase. Devolver 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:

función newCounter() {} // devolver un contador que se aumenta en la llamada (a partir de 0) // y que devuelve su nuevo valor Var a = 0; Var b = función() {} a++; retorno a; }; retorno b;}c = newCounter();alerta()c() + ' + c()); // salidas "1 2"

Los cierres se usan con frecuencia en JavaScript, debido a que se usan para devoluciones de llamada. De hecho, cualquier enganche de una función en el contexto local como devolución de llamada o devolución desde una función crea un cierre si hay variables independientes en el cuerpo de la función (con el contexto del cierre basado en los ámbitos anidados del contexto léxico actual, o "cadena de alcance"); esto puede ser accidental. Al crear una devolución de llamada basada en parámetros, los parámetros deben almacenarse en un cierre; de lo contrario, se creará accidentalmente un cierre que se refiera a las variables en el contexto adjunto, que puede cambiar.

La resolución de nombres de las propiedades de los objetos de 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 usaba alcance dinámico; fue Scheme, inspirado en ALGOL, el que introdujo el alcance estático (léxico) a la familia Lisp.

Maclisp usó 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 declaraciones SPECIAL para variables particulares. Sin embargo, Maclisp trató el enlace léxico más como una optimización de lo que uno esperaría en los lenguajes modernos, y no vino con la función de cierre que uno podría esperar del alcance léxico en los Lisps modernos. Una operación separada, *FUNCTION, estaba disponible para solucionar de forma un tanto torpe algunos de esos problemas.

Common Lisp adoptó el alcance léxico de Scheme, 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 explícitamente marcadas; deben estar definidos por un formulario especial defdynamic, enlazados por un formulario especial dynamic-let y accedidos por un formulario especial dynamic explícito.

Algunos otros dialectos de Lisp, como Emacs Lisp, todavía usan el alcance dinámico de forma predeterminada. Emacs Lisp ahora tiene alcance léxico disponible por búfer.

Pitón

Para las variables, Python tiene alcance de función, alcance de módulo y alcance global. Los nombres ingresan al contexto al comienzo de un ámbito (función, módulo o ámbito global) y salen del contexto cuando se llama a una función no anidada o el ámbito finaliza. Si se usa un nombre antes de la inicialización de la variable, se genera una excepción de tiempo de ejecución. Si simplemente se accede a una variable (no se le asigna), la resolución de nombres sigue la regla LEGB (local, envolvente, global, incorporada) que resuelve los nombres en el contexto relevante más limitado. Sin embargo, si se asigna una variable, se declara de manera predeterminada una variable cuyo alcance 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 global o nonlocal (en Python 3) antes de su uso, lo que permite acceder a las variables globales incluso si hay una variable no local de enmascaramiento, y asignación a variables globales o no locales.

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

, titulado def f():...  impresión()x)..., titulado x = "global", titulado f()mundial

Tenga en cuenta que x se define antes de llamar a f, por lo que no se genera ningún error, aunque se define después de su referencia en la definición de f. Léxicamente, esta es una referencia directa, que está permitida en Python.

Aquí la asignación crea una nueva variable local, que no cambia el valor de la variable global:

, titulado def f():...  x = "f"...  impresión()x)..., titulado x = "global", titulado impresión()x)mundial, titulado f()f, titulado impresión()x)mundial

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

, titulado def f():...  impresión()x)...  x = "f"..., titulado x = "global", titulado f()Traceback (la última llamada más reciente):Archivo "Seguido", línea 1, dentro .Archivo "Seguido", línea 2, dentro fUnboundLocalError: variable local 'x' referenciado antes de la asignación

Las reglas de resolución de nombres predeterminadas se pueden anular con las palabras clave global o nonlocal (en Python 3). En el siguiente código, la declaración global x en g significa que x se resuelve en la variable global. Por lo tanto, se puede acceder (como ya se ha definido) y la asignación se asigna a la variable global, en lugar de declarar una nueva variable local. Tenga en cuenta que no se necesita una declaración global en f, ya que no se asigna a la variable, por defecto se resuelve en la variable global.

, titulado def f():...  impresión()x)..., titulado def g():...  mundial x...  impresión()x)...  x = "g"..., titulado x = "global", titulado f()mundial, titulado g()mundial, titulado f()g

global también se puede usar para funciones anidadas. Además de permitir la asignación a una variable global, como en una función no anidada, esto también se puede usar para acceder a la variable global en presencia de una variable no local:

, titulado def f():...  def g():...  mundial x...  impresión()x)...  x = "f"...  g()..., titulado x = "global", titulado f()mundial

Para funciones anidadas, también existe la declaración nonlocal, para asignar a una variable no local, similar al uso de global en una función no anidada:

, titulado def f():...  def g():...  no local x # Python 3 only...  x = "g"...  x = "f"...  g()...  impresión()x)..., titulado x = "global", titulado f()g, titulado impresión()x)mundial

R

R es un lenguaje de alcance léxico, a diferencia de otras implementaciones de S donde los valores de las variables libres están determinados por un conjunto de variables globales, mientras que en R están determinados por el contexto en el que se creó la función. Se puede acceder a los contextos de alcance utilizando una variedad de funciones (como parent.frame()) que pueden simular la experiencia de 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 se crearon:

a . 1f . función() {} Mensaje()a)}f()## 1

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

a . 1f . función() {} Mensaje()a) a . 2 Mensaje()a)}f()## 1## 2Mensaje()a)## 1

Las variables creadas o modificadas dentro de una función permanecen allí a menos que se solicite explícitamente la asignación al ámbito adjunto:

a . 1f . función() {} Mensaje()a) a " 2 Mensaje()a)}f()## 1## 2Mensaje()a)## 2

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

a . 1f . función() {} Mensaje()a)}my_env . new.env()my_env$a . 2f()## 1medio ambiente()f) . my_envf()## 2

Contenido relacionado

Forma normal disyuntiva

ML (lenguaje de programación)

Hexadecimal

Más resultados...
Tamaño del texto:
  • Copiar
  • Editar
  • Resumir
undoredo
format_boldformat_italicformat_underlinedstrikethrough_ssuperscriptsubscriptlink
save