stringtranslate.com

Alcance (informática)

En programación de computadoras , el alcance de un enlace de nombre (una asociación de un nombre a una entidad, como una variable ) es la parte de un programa donde el enlace de nombre es válido; es decir, donde se puede utilizar el nombre para hacer referencia a la entidad. En otras partes del programa, el nombre puede referirse a una entidad diferente (puede tener una vinculación diferente) o a nada en absoluto (puede no estar vinculada). El alcance ayuda a evitar colisiones de nombres al permitir que el mismo nombre haga referencia a diferentes objetos, siempre que los nombres tengan ámbitos separados. El alcance de la vinculación de un nombre también se conoce como visibilidad de una entidad, particularmente en literatura más antigua o más técnica; esto es en relación con la entidad a la que se hace referencia, no con el nombre de referencia.

El término "alcance" también se utiliza para referirse al conjunto de todos los enlaces de nombres que son válidos dentro de una parte de un programa o en un punto determinado de un programa, lo que se denomina más correctamente contexto o entorno . [a]

Estrictamente hablando [b] y en la práctica para la mayoría de los lenguajes de programación , "parte de un programa" se refiere a una porción del código fuente (área de texto), y se conoce como alcance léxico . Sin embargo, en algunos lenguajes, "parte de un programa" se refiere a una porción del tiempo de ejecución (período de tiempo durante la ejecución ) y se conoce como alcance dinámico . Ambos términos son algo engañosos (hacen mal uso de términos técnicos, como se analiza en la definición), pero la distinción en sí es exacta y precisa, y estos son los términos estándar respectivos. El alcance léxico es el enfoque principal de este artículo, entendiéndose el alcance dinámico en contraste con el alcance léxico.

En la mayoría de los casos, la resolución de nombres basada en el alcance léxico es relativamente sencilla de usar e implementar, ya que en el uso se puede leer hacia atrás en el código fuente para determinar a qué entidad se refiere un nombre, y en la implementación se puede mantener una lista de nombres y contextos al compilar o interpretar un programa. Surgen dificultades con el enmascaramiento de nombres , las declaraciones adelantadas y la elevación , mientras que surgen dificultades considerablemente más sutiles con variables no locales , particularmente en los cierres .

Definición

La definición estricta del "alcance" (léxico) de un nombre ( identificador ) es inequívoca: el alcance léxico es "la parte del código fuente en la que se aplica 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 . A continuación se presentan especificaciones de idioma representativas:

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

Más comúnmente, "alcance" se refiere a cuando un nombre 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 versus alcance dinámico

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

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

La resolución léxica se puede determinar en tiempo de compilación y también se conoce como enlace temprano , mientras que la resolución dinámica en general sólo se puede determinar en tiempo de ejecución y, por lo tanto, se conoce como enlace tardío .

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 lenguaje. El alcance dinámico de facto es común en los lenguajes de macros , que no resuelven nombres directamente, sino que se expanden en el lugar.

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

Usar

El alcance es un componente importante de la resolución de nombres , [d] que a su vez es fundamental para la semántica del lenguaje . La resolución de nombres (incluido el alcance) varía entre lenguajes de programación y, dentro de un lenguaje de programación, varía según el tipo de entidad; las reglas de alcance se denominan reglas de alcance (o reglas de alcance ). Junto con los espacios de nombres , las reglas de alcance son cruciales en la programación modular , por lo que un cambio en una parte del programa no rompe una parte no relacionada.

Descripción general

Cuando se habla de alcance, hay tres conceptos básicos: alcance, extensión y contexto. "Ámbito" y "contexto" en particular se confunden con frecuencia: el alcance es una propiedad de un enlace de nombre, mientras que el contexto es una propiedad de una parte de un programa, es decir, una parte del código fuente ( contexto léxico o contexto estático ) o una parte del tiempo de ejecución ( contexto de ejecución, contexto de tiempo de ejecución, contexto de llamada o contexto dinámico ). El contexto de ejecución consta de un contexto léxico (en el punto de ejecución actual) más un estado de tiempo de ejecución adicional, como la pila de llamadas . [e] Estrictamente hablando, durante la ejecución un programa entra y sale de varios alcances de enlaces de nombres, y en un punto de la ejecución los enlaces de nombres están "en contexto" o "no en contexto", por lo tanto, los enlaces de nombres "entran en contexto" o "van fuera de contexto" cuando la ejecución del programa entra o sale del alcance. [f] Sin embargo, en la práctica el uso es mucho más flexible.

El alcance es un concepto a nivel de código fuente y una propiedad de las vinculaciones de nombres, particularmente las vinculaciones de nombres de variables o funciones (los nombres en el código fuente son referencias a entidades en el programa) y es parte del comportamiento de un compilador o intérprete de un lenguaje. . Como tal, las cuestiones de alcance son similares a los punteros , que son un tipo de referencia utilizado en programas de manera más general. Usar el valor de una variable cuando el nombre está en contexto pero la variable no está inicializada es análogo a desreferenciar (acceder al valor de) un puntero comodín , ya que no está definido. Sin embargo, como las variables no se destruyen hasta que salen de contexto, no existe la analogía de un puntero colgante .

Para entidades como las variables, el alcance es un subconjunto de la vida útil (también conocido como extensión ): un nombre solo puede referirse a una variable que existe (posiblemente con un valor indefinido), pero las variables que existen no son necesariamente visibles: una variable puede existir pero ser inaccesible (el valor se almacena pero no se hace referencia a él dentro de un contexto determinado), o accesible pero no a través del nombre de pila, en cuyo caso no está en contexto (el programa está "fuera del alcance del nombre"). En otros casos, la "vida útil" es irrelevante: una etiqueta (posición nombrada en el código fuente) tiene una vida útil idéntica a la del programa (para lenguajes compilados estáticamente), pero puede estar en contexto o no en un punto determinado del programa, y ​​lo mismo para Variables estáticas : una variable global estática está en contexto para todo el programa, mientras que una variable local estática solo está en contexto dentro de una función u otro contexto local, pero ambas tienen una vida útil de toda la ejecución del programa.

Determinar a qué entidad se refiere un nombre se conoce como resolución de nombres o vinculación de nombres (particularmente en programación orientada a objetos ) y varía según el lenguaje. Dado un nombre, el lenguaje (correctamente, el compilador o intérprete) verifica todas las entidades que están en contexto en busca de coincidencias; en caso de ambigüedad (dos entidades con el mismo nombre, como una variable global y local con el mismo nombre), se utilizan las reglas de resolución de nombres para distinguirlas. Con mayor frecuencia, la resolución de nombres se basa en una regla de "contexto interno a externo", como la regla LEGB (local, envolvente, global, integrada) de Python: los nombres se resuelven implícitamente en el contexto relevante más limitado. En algunos casos, la resolución de nombres se puede especificar explícitamente, como mediante las palabras clave globaly nonlocalen Python; en otros casos, las reglas predeterminadas no se pueden anular.

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

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

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

Niveles de alcance

El alcance puede variar desde una sola expresión hasta todo el programa, con muchas gradaciones posibles en el medio. La regla de alcance más simple es el alcance global: todas las entidades son visibles en todo el programa. La regla de alcance modular más básica es el alcance de dos niveles, con un alcance global en cualquier parte del programa y un alcance local dentro de una función. Una programación modular más sofisticada permite un alcance de módulo separado, donde los nombres son visibles dentro del módulo (privados del módulo) pero no visibles fuera de él. Dentro de una función, algunos lenguajes, como C, permiten que el alcance del bloque restrinja el alcance a un subconjunto de una función; otros, en particular los lenguajes funcionales, permiten el alcance de la expresión, para restringir el alcance a una sola expresión. Otros ámbitos incluyen el alcance del archivo (especialmente en C), que se comporta de manera similar al alcance del módulo, y el alcance del bloque fuera de las funciones (especialmente en Perl).

Una cuestión sutil es exactamente cuándo comienza y termina un alcance. En algunos lenguajes, como C, el alcance de un nombre comienza en la declaración del nombre y, por lo tanto, diferentes nombres declarados dentro de un bloque determinado pueden tener diferentes alcances. Esto requiere declarar funciones antes de su uso, aunque no necesariamente definirlas, y requiere una declaración directa en algunos casos, especialmente para recursividad mutua. En otros lenguajes, como Python, el alcance de un nombre comienza al inicio del bloque relevante donde se declara el nombre (como el inicio de una función), independientemente de dónde esté definido, por lo que todos los nombres dentro de un bloque determinado tienen la mismo alcance. En JavaScript, el alcance de un nombre declarado con leto constcomienza en la declaración del nombre, y el alcance 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 genera un error de tiempo de ejecución, mientras que en JavaScript los nombres indefinidos declarados con varse pueden utilizar en toda la función porque están vinculados implícitamente al valor undefined.

Alcance de la expresión

El alcance de un enlace de nombre es una expresión , que se conoce como alcance de expresión . El alcance de la expresión está disponible en muchos idiomas, especialmente en los lenguajes funcionales que ofrecen una característica llamada let-expressions que permite que el alcance de una declaración sea una expresión única. Esto es conveniente si, por ejemplo, se necesita un valor intermedio para un cálculo. Por ejemplo, en Standard ML , si f()devuelve 12, entonces es una expresión que se evalúa como , utilizando una variable temporal nombrada para evitar llamar dos veces. Algunos lenguajes con alcance de bloque se aproximan a esta funcionalidad al ofrecer sintaxis para que un bloque se incruste en una expresión; por ejemplo, la expresión ML estándar antes mencionada podría escribirse en Perl como o en GNU C como .let val x = f() in x * x end144xf()do { my $x = f(); $x * $x }({ int x = f(); x * x; })

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

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

Alcance del bloque

El alcance de un enlace de nombre es un bloque , lo que se conoce como alcance de bloque . El alcance del bloque está disponible en muchos, pero no en todos, los lenguajes de programación estructurados en bloques. Esto comenzó con ALGOL 60 , donde "[c]ada declaración... es válida sólo para ese bloque", [6] y hoy está particularmente asociado con lenguajes de las familias y tradiciones Pascal y C. La mayoría de las veces, este bloque está contenido dentro de una función, lo que restringe el alcance a una parte de una función, pero en algunos casos, como en Perl, es posible que el bloque no esté dentro de una función.

int sin signo suma_de_cuadrados ( const int sin signo N ) { int sin signo ret = 0 ; for ( unsigned int n = 1 ; n <= N ; n ++ ) { const unsigned int n_squared = n * n ; ret += n_cuadrado ; } devolver retiro ; }                                    

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

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

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

El alcance del bloque se puede utilizar para el sombreado. En este ejemplo, dentro del bloque la variable auxiliar también podría haberse llamado n , ocultando el nombre del parámetro, pero esto se considera un estilo deficiente debido a la posibilidad de errores. Además, algunos descendientes de C, como Java y C#, a pesar de tener soporte para alcance de bloque (en el sentido de que se puede hacer que una variable local salga de contexto antes del final de una función), no permiten que una variable local oculte otra. . En tales lenguajes, el intento de declaración de la segunda n resultaría en un error de sintaxis y habría que cambiar el nombre de una de las n variables.

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

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

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

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

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

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

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

mientras que en Perl esto sería:

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

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

Este es un problema más importante en C, especialmente para la asignación de cadenas, ya que la inicialización de cadenas puede asignar memoria automáticamente, mientras que la asignación de cadenas a una variable ya inicializada requiere asignar memoria, una copia de la cadena y verificar que sean exitosas.

{ mi $contador = 0 ; sub incremento_contador { retorno ++ $contador ; } }          

Algunos lenguajes permiten aplicar el concepto de alcance de bloque, en diversos grados, fuera de una función. Por ejemplo, en el fragmento de Perl de la derecha, $counterhay un nombre de variable con alcance de bloque (debido al uso de la mypalabra clave), mientras que increment_counteres un nombre de función con alcance global. Cada llamada a increment_counteraumentará el valor de $counteren uno y devolverá el nuevo valor. El código fuera de este bloque puede llamar a increment_counter, pero no puede obtener ni alterar el valor de $counter. Este modismo permite definir cierres en Perl.

Alcance de la función

Cuando el alcance de las variables declaradas dentro de una función no se extiende más allá de esa función, esto se conoce como alcance de la función . [7] El alcance de la función está disponible en la mayoría de los lenguajes de programación que ofrecen una forma de crear una variable local en una función o subrutina : una variable cuyo alcance finaliza (que sale de contexto) cuando la función regresa. En la mayoría de los casos, la vida útil de la variable es la duración de la llamada a la función; es una variable automática , creada cuando se inicia la función (o se declara la variable), y se destruye cuando la función regresa, mientras que el alcance de la variable está dentro del función, aunque el significado de "dentro" depende de si el alcance es léxico o dinámico. Sin embargo, algunos lenguajes, como C, también proporcionan variables locales estáticas , donde la vida útil de la variable es toda la vida útil del programa, pero la variable solo está en contexto cuando está dentro de la función. En el caso de las variables locales estáticas, la variable se crea cuando el programa se inicializa y se destruye solo cuando el programa finaliza, como ocurre con una variable global estática , pero solo está en contexto dentro de una función, como una variable local automática.

Es importante destacar que, en el alcance léxico, una variable con alcance de función tiene alcance solo dentro del contexto léxico de la función: sale de contexto cuando se llama a otra función dentro de la función y vuelve al contexto cuando la función regresa; las funciones llamadas no tienen acceso a las variables locales de las funciones que llaman, y las variables locales solo están en contexto dentro del cuerpo de la función en la que se declaran. Por el contrario, en el alcance dinámico, el alcance se extiende al contexto de ejecución de la función: las variables locales permanecen en contexto cuando se llama a otra función, y solo salen de contexto cuando finaliza la función que las define y, por lo tanto, las variables locales están en el contexto de la función. en el que están definidos y todos llamados funciones . En lenguajes con alcance léxico y funciones anidadas , las variables locales están en contexto para funciones anidadas, ya que estas están dentro del mismo contexto léxico, pero no para otras funciones que no están anidadas léxicas. Una variable local de una función adjunta se conoce como variable no local para la función anidada. El alcance de la función también es aplicable a funciones anónimas .

def  cuadrado ( n ):  devolver  n  *  ndef  suma_de_cuadrados ( n ):  total  =  0  i  =  0  mientras que  i  <=  n :  total  +=  cuadrado ( i )  i  +=  1  devolver  total

Por ejemplo, en el fragmento de código Python de la derecha, se definen dos funciones: squarey sum_of_squares. squarecalcula el cuadrado de un número; sum_of_squaresCalcula la suma de todos los cuadrados hasta un número. (Por ejemplo, square(4)es 4 2  =  16y sum_of_squares(4)es 0 2  + 1 2  + 2 2  + 3 2  + 4 2  =  30.)

Cada una de estas funciones tiene una variable llamada n que representa el argumento de la función. Estas dos n variables están completamente separadas y no relacionadas, a pesar de tener el mismo nombre, porque son variables locales de alcance léxico con alcance de función: el alcance de cada una es su propia función léxicamente separada y, por lo tanto, no se superponen. Por lo tanto, sum_of_squarespuede llamar sin que se altere squaresu propia n . De manera similar, sum_of_squarestiene variables denominadas total e i ; estas variables, debido a su alcance limitado, no interferirán con ninguna variable denominada total o i que pueda pertenecer a cualquier otra función. En otras palabras, no hay riesgo de colisión de nombres entre estos nombres y cualquier nombre no relacionado, incluso si son idénticos.

No se produce ningún enmascaramiento de nombres: solo una variable denominada n está en contexto en un momento dado, ya que los ámbitos no se superponen. Por el contrario, si un fragmento similar se escribiera en un lenguaje con alcance dinámico, la n en la función de llamada permanecería en contexto en la función llamada (los alcances se superpondrían) y quedaría enmascarada ("sombreada") por la nueva n. en la función llamada.

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

Alcance del archivo

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

En el fragmento de código C anterior, el nombre de la función sum_of_squarestiene alcance global (en C, enlace externo). Agregar statica la firma de la función daría como resultado el alcance del archivo (vínculo interno).

Alcance del módulo

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

En algunos lenguajes de programación orientados a objetos que carecen de soporte directo para módulos, como C++ antes de C++ 20, [8] la jerarquía de clases proporciona una estructura similar, donde las clases son la unidad básica del programa y una clase Puede tener métodos privados. Esto se entiende correctamente en el contexto del despacho dinámico en lugar de la resolución de nombres y el alcance, aunque a menudo desempeñan funciones análogas. En algunos casos, ambas funciones están disponibles, como en Python, que tiene módulos y clases, y la organización del código (como una función a nivel de módulo o un método convencionalmente privado) es una elección del programador.

Alcance global

El alcance de un enlace de nombre es un programa completo, lo que se conoce como alcance global . Los nombres de variables con alcance global (llamados variables globales ) con frecuencia se consideran una mala práctica, al menos en algunos lenguajes, debido a la posibilidad de colisiones de nombres y enmascaramiento involuntario, junto con una modularidad deficiente, y el alcance de la función o el alcance del bloque se consideran preferibles. Sin embargo, el alcance global se utiliza normalmente (según el idioma) para otros tipos de nombres, como nombres de funciones, nombres de clases y nombres de otros tipos de datos . En estos casos se utilizan mecanismos como los espacios de nombres para evitar colisiones.

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

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

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

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

Alcance léxico

Con alcance léxico , un nombre siempre hace referencia a su contexto léxico. Esta es una propiedad del texto del programa y la implementación del lenguaje la hace independiente de la pila de llamadas en tiempo de ejecución. Debido a que esta coincidencia solo requiere el análisis del texto del programa estático, este tipo de alcance también se denomina alcance estático . El alcance léxico es estándar en todos los lenguajes basados ​​en ALGOL , como Pascal , Modula-2 y Ada , así como en lenguajes funcionales modernos como ML y Haskell . También se utiliza en el lenguaje C y sus parientes sintácticos y semánticos, aunque con distintos tipos de limitaciones. El alcance estático permite al programador razonar sobre referencias de objetos como parámetros, variables, constantes, tipos, funciones, etc. como simples sustituciones de nombres. Esto hace que sea mucho más fácil crear código modular y razonar al respecto, ya que la estructura de nombres local se puede entender de forma aislada. Por el contrario, el alcance dinámico obliga al programador a anticipar todos los contextos de ejecución posibles en los que se puede invocar el código del módulo.

programa A ; var I : número entero ; K : carbón ;    procedimiento B ; var K : real ; L : número entero ;     procedimiento C ; var M : real ; comenzar (*alcance A+B+C*) finalizar ;       (*alcance A+B*) fin ;  (*alcance A*) final .

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

Podría haber habido otro procedimiento Cdeclarado en el programa fuera de procedimiento B. El lugar en el programa en el que Cse menciona " " determina a cuál de los dos procedimientos nombrados Crepresenta, 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 ). Dependiendo de la implementación y la arquitectura de la computadora , la búsqueda de variables puede volverse ligeramente ineficiente [ cita necesaria ] cuando se utilizan funciones muy profundamente anidadas léxicamente , aunque existen técnicas bien conocidas para mitigar esto. [9] [10] Además, para funciones anidadas que solo hacen referencia a sus propios argumentos y (inmediatamente) a variables locales, todas las ubicaciones relativas se pueden conocer en el momento de la compilación . Por lo tanto, no se incurre en ningún tipo de sobrecarga al utilizar ese tipo de función anidada. Lo mismo se aplica a partes particulares de un programa donde no se utilizan funciones anidadas y, naturalmente, a programas escritos en un lenguaje donde las funciones anidadas no están disponibles (como en el lenguaje C).

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 contenidas en ALGOL 60 y ALGOL 68 (aunque C no incluía funciones léxicas anidadas ).

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

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

Todos los primeros Lisps usaban alcance dinámico, cuando se basaban en intérpretes. En 1982, Guy L. Steele Jr. y el Common LISP Group publican An Overview of Common LISP , [11] una breve reseña de la historia y las implementaciones divergentes de Lisp hasta ese momento y una revisión de las características que ofrece un Common Lisp. implementación debería tener. En la página 102 leemos:

La mayoría de las implementaciones de LISP son internamente inconsistentes en el sentido de que, de forma predeterminada, el intérprete y el compilador pueden asignar semánticas diferentes a los programas correctos; esto se debe principalmente al hecho de que el intérprete asume que todas las variables tienen un alcance dinámico, mientras que el compilador asume que todas las variables son locales a menos que se vea obligado a asumir lo contrario. Esto se ha hecho por motivos de comodidad y eficiencia, pero puede provocar errores muy sutiles. La definición de Common LISP evita tales anomalías al requerir explícitamente que el intérprete y el compilador impongan una semántica idéntica a los programas correctos.

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

Además, Common LISP ofrece las siguientes funciones (la mayoría de las cuales están tomadas de MacLisp, InterLisp o Lisp Machines Lisp): (...) Variables de alcance totalmente léxico. El llamado " problema FUNARG " [12] [13] está completamente solucionado, tanto en el caso de bajada como en el de subida.

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

Todos los Lisps serios en uso de producción en ese momento tenían un alcance dinámico. Nadie que no hubiera leído detenidamente la tesis de Rabbit [15] (escrita por Guy Lewis Steele Jr. en 1978) creía que el alcance léxico funcionaría; Incluso las pocas personas que lo habían leído estaban dando un poco de fe en que esto iba a funcionar en un uso de producción serio.

El término "alcance léxico" data al menos de 1967, [16] mientras que el término "alcance léxico" data al menos de 1970, donde se utilizó en el Proyecto MAC para describir las reglas de alcance del dialecto Lisp MDL (entonces conocido como " Confusión"). [17]

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. La introducción de una variable local con nombre xenvía un enlace a la xpila global (que puede haber estado vacía), que se elimina cuando el flujo de control sale del alcance. La evaluación xen cualquier contexto siempre produce el vínculo superior. Tenga en cuenta que esto no se puede hacer en tiempo de compilación porque la pila de enlace solo existe en tiempo de ejecución , razón por la cual este tipo de alcance se denomina alcance dinámico .

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

Generalmente, ciertos bloques se definen para crear enlaces cuya vida útil es el tiempo de ejecución del bloque; esto agrega algunas características de alcance estático al proceso de alcance dinámico. Sin embargo, dado que se puede llamar a una sección de código desde muchas ubicaciones y situaciones diferentes, puede resultar difícil determinar desde el principio qué enlaces se aplicarán cuando se utilice una variable (o si existe alguna). Esto puede resultar beneficioso; La aplicación del principio de conocimiento mínimo sugiere que el código evite depender de las razones (o circunstancias) del valor de una variable, sino que simplemente use el valor de acuerdo con la definición de la variable. Esta interpretación estrecha de los datos compartidos puede proporcionar un sistema muy flexible para adaptar el comportamiento de una función al estado (o política) actual del sistema. Sin embargo, este beneficio se basa en una documentación cuidadosa de todas las variables utilizadas de esta manera, así como en evitar cuidadosamente suposiciones sobre el comportamiento de una variable, y no proporciona ningún mecanismo para detectar interferencias entre diferentes partes de un programa. Algunos lenguajes, como Perl y Common Lisp , permiten al programador elegir un alcance estático o dinámico al definir o redefinir una variable. Ejemplos de lenguajes que utilizan alcance dinámico incluyen Logo , Emacs Lisp , LaTeX y los lenguajes shell bash , dash y PowerShell .

El alcance dinámico es bastante fácil de implementar. Para encontrar el valor de un nombre, el programa podría recorrer la pila de tiempo de ejecución, comprobando cada registro de activación (el marco de la pila de cada función) en busca de un valor para el nombre. En la práctica, esto se hace más eficiente mediante el uso de una lista de asociación , que es una pila de pares de nombre/valor. Los pares se colocan en esta pila cada vez que se realizan declaraciones y se extraen cuando las variables salen de contexto. [18] La vinculación superficial es una estrategia alternativa que es considerablemente más rápida, ya que utiliza una tabla de referencia central , que asocia cada nombre con su propia pila de significados. Esto evita una búsqueda lineal durante el tiempo de ejecución para encontrar un nombre en particular, pero se debe tener cuidado de mantener adecuadamente esta tabla. [18] Tenga en cuenta que ambas estrategias asumen un orden de último en entrar, primero en salir ( LIFO ) para los enlaces de cualquier variable; en la práctica, todas las consolidaciones están así ordenadas.

Una implementación aún más simple es la representación de variables dinámicas con variables globales simples. El enlace local se realiza guardando el valor original en una ubicación anónima de la pila que es invisible para el programa. Cuando finaliza ese alcance vinculante, el valor original se restaura desde esta ubicación. De hecho, el alcance dinámico se originó de esta manera. Las primeras implementaciones de Lisp utilizaron esta estrategia obvia para implementar variables locales, y la práctica sobrevive en algunos dialectos que todavía están en uso, como GNU Emacs Lisp. El alcance léxico se introdujo en Lisp más tarde. Esto es equivalente al esquema de vinculación superficial anterior, excepto que la tabla de referencia central es simplemente el contexto de vinculación de la variable global, en el que el significado actual de la variable es su valor global. Mantener variables globales no es complejo. Por ejemplo, un objeto de símbolo puede tener un espacio dedicado para su valor global.

El alcance dinámico proporciona una excelente abstracción para el almacenamiento local de subprocesos , pero si se usa de esa manera no puede basarse en guardar y restaurar una variable global. Una posible estrategia de implementación es que cada variable tenga una clave local de subproceso. Cuando se accede a la variable, la clave local del subproceso se utiliza para acceder a la ubicación de la memoria local del subproceso (mediante el código generado por el compilador, que sabe qué variables son dinámicas y cuáles son léxicas). Si la clave local del subproceso no existe para el subproceso que llama, entonces se utiliza la ubicación global. Cuando una variable está vinculada localmente, el valor anterior se almacena en una ubicación oculta de la pila. El almacenamiento local del subproceso se crea bajo la clave de la variable y el nuevo valor se almacena allí. Otras anulaciones anidadas de la variable dentro de ese subproceso simplemente guardan y restauran esta ubicación local del subproceso. Cuando termina el contexto inicial de anulación más externa, la clave local del subproceso se elimina, exponiendo la versión global de la variable una vez más a ese subproceso.

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

Macroexpansión

En los lenguajes modernos, la expansión de macros en un preprocesador es un ejemplo clave de alcance dinámico de facto. El lenguaje de macros en sí solo transforma el código fuente, sin resolver nombres, pero dado que la expansión se realiza en el lugar, cuando se resuelven los nombres en el texto expandido (en particular, las variables libres), se resuelven en función de dónde se expanden (en términos generales). "llamado"), como si estuviera ocurriendo un alcance dinámico.

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

#definir ADD_A(x) x + a

se expandirá para agregar aa la variable pasada, y este nombre solo lo resolverá más tarde el compilador en función de dónde ADD_Ase "llama" la macro (correctamente, expandida). Correctamente, el preprocesador de C solo realiza análisis léxico , expandiendo la macro durante la etapa de tokenización, pero sin analizar en un árbol de sintaxis ni resolver nombres.

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

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

Nombres calificados

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

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

Aunque muchos idiomas apoyan este concepto, los detalles varían mucho. Algunos lenguajes tienen mecanismos, como los espacios de nombres en C++ y C# , que sirven casi exclusivamente para permitir que los nombres globales se organicen en grupos. Otros lenguajes tienen mecanismos, como paquetes en Ada y estructuras en Standard ML , que combinan esto con el propósito adicional de permitir que algunos nombres sean visibles solo para otros miembros de su grupo. Y los lenguajes orientados a objetos a menudo permiten que clases u objetos únicos cumplan este propósito (tengan o no también un mecanismo para el cual este sea el propósito principal). Además, los idiomas a menudo combinan estos enfoques; por ejemplo, los paquetes de Perl son muy similares a los espacios de nombres de C++, pero opcionalmente funcionan como clases para programación orientada a objetos; y Java organiza sus variables y funciones en clases, pero luego organiza esas clases en paquetes tipo Ada.

Por idioma

A continuación se presentan reglas de alcance para idiomas representativos.

C

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

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

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

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

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

#incluir <stdio.h> int principal ( vacío ) { char x = 'm' ; printf ( "%c \n " , x ); { printf ( "%c \n " , x ); carácter x = 'b' ; printf ( "%c \n " , x ); } printf ( "%c \n " , x ); }                    

El programa genera:

metrometrobmetro

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

C++

Todas las variables que pretendemos utilizar en un programa deben haber sido declaradas con su especificador de tipo en un punto anterior del código, como hicimos en el código anterior al inicio del cuerpo de la función main cuando declaramos que a, b, y el resultado fueron de tipo int. Una variable puede ser de alcance global o local. Una variable global es una variable declarada en el cuerpo principal del código fuente, fuera de todas las funciones, mientras que una variable local es una declarada dentro del cuerpo de una función o bloque.

Las versiones modernas permiten un alcance léxico anidado.

Rápido

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

Ir

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

Java

Java tiene un alcance léxico.

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

variables locales
se definen dentro de un método o un bloque particular. Estas variables son locales al lugar donde fueron definidas y niveles inferiores. Por ejemplo, un bucle dentro de un método puede utilizar 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 finaliza el bucle.
Variables miembro
También llamados campos son variables declaradas dentro de la clase, fuera de cualquier método. De forma predeterminada, estas variables están disponibles para todos los métodos dentro de esa clase y también para todas las clases del paquete.
Parámetros
son variables en las declaraciones de métodos.

En general, un conjunto de corchetes define un alcance particular, pero las variables en el nivel superior dentro de una clase pueden diferir en su comportamiento dependiendo de las palabras clave modificadoras utilizadas en su definición. La siguiente tabla muestra el acceso a los miembros permitido por cada modificador. [21]

javascript

JavaScript tiene reglas de alcance simples , [22] pero las reglas de inicialización de variables y resolución de nombres pueden causar problemas, y el uso generalizado de cierres para devoluciones de llamadas significa que el contexto léxico de una función cuando se define (que se usa para la resolución de nombres) puede ser muy diferente de el contexto léxico cuando se llama (lo cual es irrelevante para la resolución de nombres). Los objetos JavaScript tienen resolución de nombres para propiedades, pero este es un tema aparte.

JavaScript tiene alcance léxico [23] anidado en el nivel de función, siendo el contexto global el contexto más externo. Este alcance se utiliza tanto para variables como para funciones (es decir, declaraciones de funciones, a diferencia de variables de tipo de función ). [24] El alcance del bloque con las palabras clave lety constes estándar desde ECMAScript 6. El alcance del bloque se puede producir envolviendo todo el bloque en una función y luego ejecutándola; esto se conoce como patrón de expresión de función inmediatamente invocada (IIFE).

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

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

Esto se conoce como elevación de variables [26] : la declaración, pero no la inicialización, se eleva a la parte superior de la función. En tercer lugar, acceder a las variables antes de la inicialización produce errores de sintaxis undefined, en lugar de errores de sintaxis. En cuarto lugar, para las declaraciones de funciones, la declaración y la inicialización se elevan a la parte superior de la función, a diferencia de la inicialización de variables. Por ejemplo, el siguiente código produce un cuadro de diálogo con salidaindefinido, a medida que se eleva la declaración de la variable local, se oculta la variable global, pero la inicialización no, por lo que la variable no está definida cuando se usa:

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

Además, como las funciones son objetos de primera clase en JavaScript y con frecuencia se asignan como devoluciones de llamada o se devuelven desde funciones, cuando se ejecuta una función, la resolución del nombre depende de dónde se definió originalmente (el contexto léxico de la definición), no del léxico. contexto o contexto de ejecución donde se llama. Los alcances anidados de una función particular (desde la más global hasta la más local) en JavaScript, particularmente de un cierre, utilizado como devolución de llamada, a veces se denominan cadena de alcance , por analogía con la cadena de prototipo de un objeto.

Los cierres se pueden producir en JavaScript mediante el uso de funciones anidadas, ya que las funciones son objetos de primera clase. [27] Devolver una función anidada a partir de una función adjunta incluye las variables locales de la función adjunta como el contexto léxico (no local) de la función devuelta, lo que produce un cierre. Por ejemplo:

function newCounter () { // devuelve un contador que se incrementa al llamar (comenzando en 0) // y que devuelve su nuevo valor var a = 0 ; var b = función () { a ++ ; devolver un ; }; devolver b ; } c = nuevoContador (); alerta ( c () + ' ' + c ()); // salidas "1 2"                          

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

La resolución de nombres de propiedades de objetos JavaScript se basa en la herencia en el árbol prototipo (una ruta a la raíz del árbol se llama cadena de prototipos ) y está separada de la resolución de nombres de variables y funciones.

Ceceo

Los dialectos Lisp tienen varias reglas de alcance.

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

Maclisp usó alcance dinámico por defecto en el intérprete y alcance léxico por defecto en el código compilado, aunque el código compilado podría acceder a enlaces dinámicos mediante el uso de SPECIALdeclaraciones para variables particulares. [29] Sin embargo, Maclisp trató el enlace léxico más como una optimización de lo que uno esperaría en los lenguajes modernos, y no vino con la característica de cierre que uno podría esperar del alcance léxico en los Lisps modernos. Una operación separada, *FUNCTION, estaba disponible para solucionar algo de ese problema con cierta torpeza. [30]

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

ISLISP tiene alcance léxico para variables ordinarias. También tiene variables dinámicas, pero en todos los casos están marcadas explícitamente; deben definirse mediante una defdynamicforma especial, estar sujetos a una dynamic-letforma especial y acceder a ellos mediante una dynamicforma especial explícita. [32]

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

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 alcance (función, módulo o alcance global) y salen del contexto cuando se llama a una función no anidada o el alcance finaliza. Si se utiliza un nombre antes de la inicialización de la variable, esto genera una excepción de tiempo de ejecución. Si simplemente se accede a una variable (no se le asigna), la resolución de nombres sigue la regla LEGB (Local, Enclosing, Global, Incorporada) que resuelve los nombres en el contexto relevante más limitado. Sin embargo, si se asigna una variable, de forma predeterminada se declara una variable cuyo alcance comienza al inicio del nivel (función, módulo o global), no en la asignación. Ambas reglas se pueden anular con una declaración globalo nonlocal(en Python 3) antes de su uso, lo que permite acceder a variables globales incluso si hay una variable no local de enmascaramiento y asignar variables globales o no locales.

Como ejemplo simple, una función resuelve una variable al alcance global:

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

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

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

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

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

>>> def  f (): ...  print ( x ) ...  x  =  "f" ... >>> x  =  "global" >>> f () Rastreo (última llamada más reciente): Archivo " <stdin>" , línea 1 , en <módulo> Archivo "<stdin>" , línea 2 , en f UnboundLocalError : variable local 'x' a la que se hace referencia antes de la asignación

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

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

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, esto también se puede utilizar para acceder a la variable global en presencia de una variable no local:

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

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

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

R

R es un lenguaje de alcance léxico, a diferencia de otras implementaciones de S donde los valores de las variables libres están determinados por un conjunto de variables globales, mientras que en R están determinados por el contexto en el que se creó la función. [34] Se puede acceder a los contextos de alcance utilizando una variedad de funciones (como parent.frame()) que pueden simular la experiencia del alcance dinámico si el programador lo desea.

No hay alcance de bloque:

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

Las funciones tienen acceso al alcance en el que fueron creadas:

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

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

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

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

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

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

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

Ver también

Notas

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

Referencias

  1. ^ "Informe sobre el lenguaje algorítmico Algol 60", 2.7. Cantidades, tipos y alcances
  2. ^ WG14 N1256 (versión actualizada de 2007 del estándar C99 ), 6.2.1 Alcances de identificadores, 2007-09-07
  3. ^ ab Especificación del lenguaje de programación Go: declaraciones y alcance, versión del 13 de noviembre de 2013
  4. ^ abc Borning A. CSE 341 - Alcance léxico y dinámico. Universidad de Washington.
  5. ^ Crockford, Douglas. "Convenciones de código para el lenguaje de programación JavaScript" . Consultado el 4 de enero de 2015 .
  6. ^ Backus, JW; Wegstein, JH; Van Wijngaarden, A.; Woodger, M.; Bauer, 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. No se puede acceder a las variables definidas dentro de una función desde ningún lugar fuera de la función, porque la variable está definida solo en el alcance de la función. Sin embargo, una función puede acceder a todas las variables y funciones definidas dentro del alcance en el que está definida.
  8. ^ "N4720: Borrador de trabajo, extensiones a C++ para módulos" (PDF) . Archivado (PDF) desde el original el 30 de abril de 2019 . Consultado 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 descripción general de COMMON LISP". Actas del simposio ACM de 1982 sobre LISP y programación funcional - LFP '82 . págs. 98-107. doi :10.1145/800068.802140. ISBN 0-89791-082-6. S2CID  14517358.
  12. ^ Joel, Moisés (junio de 1970). "La función de FUNCIÓN en LISP". Memorándum 199 de IA del MIT . Laboratorio de Inteligencia Artificial del MIT.
  13. ^ Steele, Guy Lewis hijo; Sussman, Gerald Jay (mayo de 1978). "El arte del intérprete; o el complejo de modularidad (partes cero, uno y dos)". Memorándum 453 de IA del MIT . Laboratorio de Inteligencia Artificial del MIT.
  14. ^ Escalofríos, Olin. "Historia de T". Pablo 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 de Tecnología de Massachusetts. hdl : 1721.1/6913 .
  16. ^ "alcance léxico", Organización de programas y computadoras, Parte 3 , p. 18, en Google Books , Universidad de Michigan. Conferencias de verano de ingeniería, 1967
  17. ^ "alcance léxico", Informe de progreso del proyecto MAC, volumen 8 , p. 80, en Google Books , 1970.
  18. ^ ab Scott 2009, 3.4 Alcance de implementación, p. 143.
  19. ^ "Alcance", XL C/C++ V8.0 para Linux, IBM
  20. ^ "Declaración de variables miembro (Tutoriales de Java™ > Aprendizaje del lenguaje Java > Clases y objetos)". docs.oracle.com . Consultado el 19 de marzo de 2018 .
  21. ^ "Controlar el acceso a los miembros de una clase (Tutoriales de Java™ > Aprender el lenguaje Java > Clases y objetos)". docs.oracle.com . Consultado el 19 de marzo de 2018 .
  22. ^ "Todo lo que necesita saber sobre el alcance de las variables de Javascript", Saurab Parakh, Coding is Cool, 2010-02-08
  23. ^ "ES5 anotado". es5.github.io . Consultado el 19 de marzo de 2018 .
  24. ^ "Funciones". Documentos web de MDN . Consultado el 19 de marzo de 2018 .
  25. ^ "12.2 Declaración variable", ECMAScript 5.1 anotado, última actualización: 28 de mayo de 2012
  26. ^ "Alcance y elevación de JavaScript", Ben Cherry, adecuadamente bueno, 8 de febrero de 2010
  27. ^ Cierres de Javascript, Richard Cornford. marzo de 2004
  28. ^ "Explicación del alcance y los cierres de JavaScript", Robert Nyman, 9 de octubre de 2008
  29. ^ Pitman, Kent (16 de diciembre de 2007). "El manual Maclisp revisado (The Pitmanual), edición del domingo por la mañana". MACLISP.info . Declaraciones de HyperMeta Inc. y el compilador, concepto "Variables" . Consultado el 20 de octubre de 2018 . Si la variable que se va a vincular se ha declarado como especial, el enlace se compila como código para imitar la forma en que el intérprete vincula las variables.
  30. ^ Pitman, Kent (16 de diciembre de 2007). "El manual Maclisp revisado (The Pitmanual), edición del domingo por la mañana". MACLISP.info . HyperMeta Inc. El evaluador, formulario especial . Consultado el 20 de octubre de 2018 . está destinado a ayudar a resolver el " problema funarg ", sin embargo, sólo funciona en algunos casos fáciles.*FUNCTION*FUNCTION
  31. ^ Pitman, Kent; et al. (versión web del estándar ANSI X3.226-1994) (1996). "HyperSpec de Lisp común". Lispworks.com . LispWorks Ltd. 1.1.2 Historia . Consultado el 20 de octubre de 2018 . MacLisp mejoró la noción de variables especiales de Lisp 1.5... Las principales influencias en Common Lisp fueron Lisp Machine Lisp, MacLisp, NIL, S-1 Lisp, Spice Lisp y Scheme.
  32. ^ "Lenguaje de programación ISLISP, Borrador de trabajo 23.0 de ISLISP" (PDF) . ISLISP.info . 11.1 El principio léxico . Consultado el 20 de octubre de 2018 . Los enlaces dinámicos se establecen y se accede a ellos mediante un mecanismo independiente (es decir, , y ).defdynamicdynamic-letdynamic
  33. ^ "Encuadernación léxica". EmacsWiki . Consultado el 20 de octubre de 2018 . Emacs 24 tiene enlace léxico opcional, que se puede habilitar por búfer.
  34. ^ "Preguntas frecuentes sobre R". cran.r-project.org . Consultado el 19 de marzo de 2018 .