En programmation informatique , la portée d’une liaison de nom – une association d’un nom à une entité, telle qu’une variable – est la partie d’un programme où la liaison de nom est valide, c’est-à-dire où le nom peut être utilisé pour faire référence à la entité. Dans d’autres parties du programme, le nom peut faire référence à une entité différente (il peut avoir une liaison différente) ou à rien du tout (il peut être non lié). La portée d’une liaison de nom est également connue sous le nom de visibilité d’une entité, en particulier dans la littérature plus ancienne ou plus technique – c’est du point de vue de l’entité référencée, pas du nom de référence.
Le terme “scope” est également utilisé pour désigner l’ensemble de toutes les liaisons de noms valides dans une partie d’un programme ou à un point donné d’un programme, qui est plus correctement appelé context ou environment . [un]
Strictement parlant [b] et en pratique pour la plupart des langages de programmation, “partie d’un programme” fait référence à une portion de code source (zone de texte), et est connue sous le nom de portée lexicale . Dans certains langages, cependant, “partie d’un programme” fait référence à une partie du temps d’exécution (période de temps pendant l’exécution) et est connue sous le nom de portée dynamique . Ces deux termes sont quelque peu trompeurs – ils utilisent à mauvais escient des termes techniques, comme indiqué dans la définition – mais la distinction elle-même est exacte et précise, et ce sont les termes respectifs standard. La portée lexicale est l’objet principal de cet article, la portée dynamique étant comprise par opposition à la portée lexicale.
Dans la plupart des cas, la résolution de noms basée sur la portée lexicale est relativement simple à utiliser et à mettre en œuvre, car lors de l’utilisation, on peut lire en arrière dans le code source pour déterminer à quelle entité un nom se réfère, et dans la mise en œuvre, on peut maintenir une liste de noms et contextes lors de la compilation ou de l’interprétation d’un programme. Des difficultés surgissent dans le masquage de noms , les déclarations en avant et le hissage , tandis que des difficultés considérablement plus subtiles surviennent avec des variables non locales , en particulier dans les fermetures .
Définition
La définition stricte de la “portée” (lexicale) d’un nom ( identifiant ) est sans ambiguïté : la portée lexicale est “la partie du code source dans laquelle s’applique une liaison d’un nom avec une entité”. Ceci est pratiquement inchangé par rapport à sa définition de 1960 dans la spécification d ‘ ALGOL 60 . Les spécifications de la langue représentative sont les suivantes :
ALGOL 60 (1960) [1] On distingue les types de quantités suivants : variables simples, tableaux, étiquettes, commutateurs et procédures. La portée d’une grandeur est l’ensemble d’énoncés et d’expressions dans lequel la déclaration de l’identificateur associé à cette grandeur est valide. C (2007) [2] Un identifiant peut désigner un objet ; une fonction; une étiquette ou un membre d’une structure, d’une union ou d’une énumération ; un nom typedef ; un nom d’étiquette ; un nom de macro ; ou un paramètre macro. Le même identifiant peut désigner différentes entités à différents points du programme. […] Pour chaque entité différente qu’un identificateur désigne, l’identificateur n’est visible (c’est-à-dire ne peut être utilisé) que dans une région du texte du programme appelée sa portée. Allez (2013) [3] Une déclaration lie un identificateur non vide à une constante, un type, une variable, une fonction, une étiquette ou un package. […] La portée d’un identificateur déclaré est l’étendue du texte source dans lequel l’identificateur désigne la constante, le type, la variable, la fonction, l’étiquette ou le package spécifié.
Le plus souvent, “portée” fait référence au moment où un nom donné peut faire référence à une variable donnée – lorsqu’une déclaration a un effet – mais peut également s’appliquer à d’autres entités, telles que des fonctions, des types, des classes, des labels , des constantes et des énumérations.
Portée lexicale vs portée dynamique
Une distinction fondamentale dans la portée est ce que signifie “partie d’un programme”. Dans les langages à portée lexicale (également appelée portée statique ), la résolution de nom dépend de l’emplacement dans le code source et du contexte lexical (également appelé contexte statique ), qui est défini par l’endroit où la variable ou la fonction nommée est définie. En revanche, dans les langages à portée dynamique , la résolution du nom dépend de l’ État du programme lorsque le nom est rencontré, qui est déterminé par le contexte d’exécution (également appelé contexte d’exécution , contexte d’ appel ou contexte dynamique). En pratique, avec la portée lexicale, un nom est résolu en recherchant le contexte lexical local, puis en cas d’échec, en recherchant le contexte lexical externe, et ainsi de suite ; alors qu’avec une portée dynamique, un nom est résolu en recherchant le contexte d’exécution local, puis en cas d’échec, en recherchant le contexte d’exécution externe, et ainsi de suite, en remontant la pile des appels. [4]
La plupart des langages modernes utilisent la portée lexicale pour les variables et les fonctions, bien que la portée dynamique soit utilisée dans certains langages, notamment certains dialectes de Lisp, certains langages de “script” et certains langages modèles . [c] Perl 5 offre à la fois une portée lexicale et dynamique. Même dans les langues à portée lexicale, la portée des fermetures peut être déroutante pour les non-initiés, [ citation nécessaire ] car celles-ci dépendent du contexte lexical dans lequel la fermeture est définie, et non de l’endroit où elle est appelée.
La résolution lexicale peut être déterminée au moment de la compilation , et est également connue sous le nom de liaison précoce , tandis que la résolution dynamique ne peut en général être déterminée qu’au moment de l’exécution , et est donc connue sous le nom de liaison tardive .
Notions connexes
Dans la programmation orientée objet , la distribution dynamique sélectionne une méthode d’objet au moment de l’exécution, bien que le fait que la liaison de nom réelle soit effectuée au moment de la compilation ou de l’exécution dépend du langage. La portée dynamique de facto est courante dans les langages de macro , qui ne font pas directement la résolution de noms, mais se développent plutôt sur place.
Certains frameworks de programmation comme AngularJS utilisent le terme “portée” pour signifier quelque chose de complètement différent de la façon dont il est utilisé dans cet article. Dans ces frameworks, la portée n’est qu’un objet du langage de programmation qu’ils utilisent ( JavaScript dans le cas d’AngularJS) qui est utilisé de certaines manières par le framework pour émuler la portée dynamique dans un langage qui utilise la portée lexicale pour ses variables. Ces portées AngularJS peuvent elles-mêmes être en contexte ou non (au sens habituel du terme) dans n’importe quelle partie du programme, en suivant les règles habituelles de portée variable du langage comme tout autre objet, et en utilisant leur propre héritage et transclusionrègles. Dans le contexte d’AngularJS, le terme “$scope” (avec un signe dollar) est parfois utilisé pour éviter toute confusion, mais l’utilisation du signe dollar dans les noms de variables est souvent déconseillée par les guides de style. [5]
Utiliser
La portée est un élément important de la résolution de noms , [d] qui est à son tour fondamentale pour la sémantique du langage . La résolution de nom (y compris la portée) varie selon les langages de programmation et, au sein d’un langage de programmation, varie selon le type d’entité ; les règles de portée sont appelées règles de portée (ou règles de portée ). Avec les Espaces de noms , les règles de portée sont cruciales dans la programmation modulaire , de sorte qu’un changement dans une partie du programme ne casse pas une partie non liée.
Aperçu
Lors de l’examen de la portée, il existe trois concepts de base : la portée, l’ étendue et le contexte. “Portée” et “contexte” en particulier sont souvent confondus : la portée est une propriété d’une liaison de nom, tandis que le contexte est une propriété d’une partie d’un programme, c’est-à-dire soit une portion de code source ( contexte lexical ou contexte statique ) ou une partie du temps d’exécution ( contexte d’exécution, contexte d’exécution, contexte d’ appel ou contexte dynamique ). Le contexte d’exécution consiste en un contexte lexical (au point d’exécution actuel) plus un état d’exécution supplémentaire tel que la pile d’appels . [e]À proprement parler, pendant l’exécution, un programme entre et sort des portées de diverses liaisons de noms, et à un moment donné de l’exécution, les liaisons de noms sont “dans le contexte” ou “pas dans le contexte”, d’où les liaisons de noms “entrent dans le contexte” ou “sortent du contexte ” lorsque l’exécution du programme entre ou sort de la portée. [f] Cependant, dans la pratique, l’utilisation est beaucoup plus souple.
La portée est un concept au niveau du code source et une propriété des liaisons de noms, en particulier les liaisons de noms de variables ou de fonctions – les noms dans le code source sont des références à des entités du programme – et fait partie du comportement d’un compilateur ou d’un interpréteur d’un langage . En tant que tels, les problèmes de portée sont similaires aux pointeurs , qui sont un type de référence utilisé plus généralement dans les programmes. L’utilisation de la valeur d’une variable lorsque le nom est dans le contexte mais que la variable n’est pas initialisée est analogue au déréférencement (accès à la valeur de) un Pointeur sauvage , car il n’est pas défini. Cependant, comme les variables ne sont pas détruites tant qu’elles ne sortent pas de leur contexte, l’analogue d’un pointeur suspendu n’existe pas.
Pour les entités telles que les variables, la portée est un sous-ensemble de la durée de vie (également appelé extent )—un nom ne peut faire référence qu’à une variable qui existe (éventuellement avec une valeur indéfinie), mais les variables qui existent ne sont pas nécessairement visibles : une variable peut exister mais être inaccessible (la valeur est stockée mais non référencée dans un contexte donné), ou accessible mais pas via le nom donné, auquel cas elle n’est pas dans le contexte (le programme est “hors de la portée du nom”). Dans d’autres cas, la “durée de vie” n’est pas pertinente – une étiquette (position nommée dans le code source) a une durée de vie identique au programme (pour les langages compilés statiquement), mais peut être en contexte ou non à un point donné du programme, et de même pour variables statiques — une variable globale statiqueest en contexte pour l’ensemble du programme, tandis qu’une Variable locale statique n’est en contexte qu’au sein d’une fonction ou d’un autre contexte local, mais les deux ont une durée de vie de toute l’exécution du programme.
Déterminer à quelle entité un nom fait référence est connu sous le nom de résolution de nom ou de liaison de nom (en particulier dans la programmation orientée objet ) et varie selon les langages. Étant donné un nom, le langage (correctement, le compilateur ou l’interpréteur) vérifie toutes les entités qui sont en contexte pour les correspondances ; en cas d’ambiguïté (deux entités portant le même nom, comme une variable globale et locale portant le même nom), les règles de résolution de nom sont utilisées pour les distinguer. Le plus souvent, la résolution de noms repose sur une règle de “contexte interne vers externe”, telle que la règle Python LEGB (Local, Enclosing, Global, Built-in) : les noms sont implicitement résolus dans le contexte pertinent le plus étroit. Dans certains cas, la résolution de nom peut être explicitement spécifiée, comme par le globaletnonlocalmots-clés en Python ; dans d’autres cas, les règles par défaut ne peuvent pas être remplacées.
Lorsque deux noms identiques sont en contexte en même temps, faisant référence à des entités différentes, on dit qu’un masquage de nom se produit, où le nom de priorité supérieure (généralement le plus interne) “masque” le nom de priorité inférieure. Au niveau des variables, c’est ce qu’on appelle l’ occultation des variables . En raison du risque d’ erreurs logiques liées au masquage, certains langages interdisent ou découragent le masquage, ce qui génère une erreur ou un avertissement au moment de la compilation ou de l’exécution.
Divers langages de programmation ont différentes règles de portée pour différents types de déclarations et de noms. De telles règles de portée ont un effet important sur la sémantique du langage et, par conséquent, sur le comportement et l’exactitude des programmes. Dans des langages comme C++ , l’accès à une variable non liée n’a pas de sémantique bien définie et peut entraîner un comportement indéfini , similaire à la référence à un pointeur pendant ; et les déclarations ou noms utilisés en dehors de leur portée généreront des erreurs de syntaxe .
Les portées sont fréquemment liées à d’autres constructions de langage et déterminées implicitement, mais de nombreux langages offrent également des constructions spécifiquement pour contrôler la portée.
Niveaux de portée
La portée peut varier d’aussi peu qu’une seule expression à autant que l’ensemble du programme, avec de nombreuses gradations possibles entre les deux. La règle de portée la plus simple est la portée globale : toutes les entités sont visibles dans l’ensemble du programme. La règle de portée modulaire la plus élémentaire est la portée à deux niveaux, avec une portée globale n’importe où dans le programme et une portée locale dans une fonction. Une programmation modulaire plus sophistiquée permet une portée de module distincte, où les noms sont visibles dans le module (privés au module) mais non visibles à l’extérieur. Au sein d’une fonction, certains langages, tels que C, permettent à la portée de bloc de restreindre la portée à un sous-ensemble d’une fonction; d’autres, notamment les langages fonctionnels, autorisent la portée de l’expression, pour restreindre la portée à une seule expression. D’autres portées incluent la portée du fichier (notamment en C) qui se comporte de la même manière que la portée du module,
Un problème subtil est de savoir exactement quand une portée commence et se termine. Dans certains langages, tels que C, la portée d’un nom commence à la déclaration de nom, et donc différents noms déclarés dans un bloc donné peuvent avoir des portées différentes. Cela nécessite de déclarer les fonctions avant utilisation, mais pas nécessairement de les définir, et nécessite une déclaration en avant dans certains cas, notamment pour la récursivité mutuelle. Dans d’autres langages, tels que Python, la portée d’un nom commence au début du bloc pertinent où le nom est déclaré (comme le début d’une fonction), quel que soit l’endroit où il est défini, de sorte que tous les noms dans un bloc donné ont le même périmètre. En JavaScript, la portée d’un nom déclaré avec letou constcommence à la déclaration de nom, et la portée d’un nom déclaré avecvarcommence au début de la fonction où le nom est déclaré, qui est connu sous le nom de levage de variable . Le comportement des noms dans le contexte qui ont une valeur indéfinie diffère : en Python, l’utilisation de noms indéfinis génère une erreur d’exécution, tandis qu’en JavaScript, les noms indéfinis déclarés avec varsont utilisables dans toute la fonction car ils sont implicitement liés à la valeur undefined.
Portée de l’expression
La portée d’une liaison de nom est une expression , connue sous le nom de portée d’expression . La portée d’expression est disponible dans de nombreux langages, en particulier les langages fonctionnels qui offrent une fonctionnalité appelée expressions let permettant à la portée d’une déclaration d’être une expression unique. Ceci est pratique si, par exemple, une valeur intermédiaire est nécessaire pour un calcul. Par exemple, dans Standard ML , si f()renvoie 12 , alors est une expression qui donne 144 , en utilisant une variable temporaire nommée x pour éviter d’appelerlet val x = f() in x * x endf()à deux reprises. Certains langages avec une portée de bloc se rapprochent de cette fonctionnalité en offrant une syntaxe pour qu’un bloc soit intégré dans une expression ; par exemple, l’expression Standard ML susmentionnée pourrait être écrite en Perl sous la forme , ou en GNU C sous la forme .do { my $x = f(); $x * $x }({ int x = f(); x * x; })
En Python, les variables auxiliaires dans les expressions de générateur et les compréhensions de liste (en Python 3) ont une portée d’expression.
En C, les noms de variables dans un prototype de fonction ont une portée d’expression, connue dans ce contexte sous le nom de portée de protocole de fonction . Comme les noms de variables dans le prototype ne sont pas mentionnés (ils peuvent être différents dans la définition réelle) – ce ne sont que des mannequins – ils sont souvent omis, bien qu’ils puissent être utilisés pour générer de la documentation, par exemple.
Portée du bloc
La portée d’une liaison de nom est un bloc , appelé portée de bloc . La portée de bloc est disponible dans de nombreux langages de programmation structurés en blocs, mais pas tous. Cela a commencé avec ALGOL 60 , où “[e]very déclaration … n’est valable que pour ce bloc.”, [6] et est aujourd’hui particulièrement associée aux langues des familles et traditions Pascal et C. Le plus souvent, ce bloc est contenu dans une fonction, limitant ainsi la portée à une partie d’une fonction, mais dans certains cas, comme Perl, le bloc peut ne pas être dans une fonction.
unsigned int sum_of_squares ( const unsigned int N ) { entier non signé ret = 0 ; pour ( int non signé n = 1 ; n <= N ; n ++ ) { const int non signé n_squared = n * n ; ret += n_carré ; } retour ret ; }
Un exemple représentatif de l’utilisation de la portée de bloc est le code C illustré ici, où deux variables sont portées à la boucle : la variable de boucle n , qui est initialisée une fois et incrémentée à chaque itération de la boucle, et la variable auxiliaire n_squared , qui est initialisé à chaque itération. Le but est d’éviter d’ajouter des variables à la portée de la fonction qui ne sont pertinentes que pour un bloc particulier – par exemple, cela évite les erreurs où la variable de boucle générique i a accidentellement déjà été définie sur une autre valeur. Dans cet exemple, l’expression n * nne serait généralement pas affectée à une variable auxiliaire, et le corps de la boucle serait simplement écrit ret += n * n, mais dans des exemples plus compliqués, les variables auxiliaires sont utiles.
Les blocs sont principalement utilisés pour le flux de contrôle, comme avec les boucles if, while et for, et dans ces cas, la portée du bloc signifie que la portée de la variable dépend de la structure du flux d’exécution d’une fonction. Cependant, les langages avec une portée de bloc permettent généralement également l’utilisation de blocs “nus”, dont le seul but est de permettre un contrôle précis de la portée des variables. Par exemple, une variable auxiliaire peut être définie dans un bloc, puis utilisée (par exemple, ajoutée à une variable avec une portée de fonction) et supprimée lorsque le bloc se termine, ou une boucle while peut être incluse dans un bloc qui initialise les variables utilisées à l’intérieur de la boucle qui ne doit être initialisé qu’une seule fois.
Une subtilité de plusieurs langages de programmation, comme Algol 68 et C (démontré dans cet exemple et normalisé depuis C99 ), est que les variables de portée de bloc peuvent être déclarées non seulement dans le corps du bloc, mais aussi dans l’instruction de contrôle, si quelconque. Ceci est analogue aux paramètres de fonction, qui sont déclarés dans la déclaration de fonction (avant le début du bloc du corps de la fonction) et dans la portée de l’ensemble du corps de la fonction. Ceci est principalement utilisé dans les boucles for , qui ont une instruction d’initialisation distincte de la condition de boucle, contrairement aux boucles while, et est un idiome courant.
La portée de bloc peut être utilisée pour l’observation. Dans cet exemple, à l’intérieur du bloc, la variable auxiliaire aurait également pu être appelée n , masquant le nom du paramètre, mais cela est considéré comme un style médiocre en raison du potentiel d’erreurs. De plus, certains descendants de C, tels que Java et C #, bien qu’ils prennent en charge la portée de bloc (en ce sens qu’une variable locale peut sortir de son contexte avant la fin d’une fonction), ne permettent pas à une variable locale d’en masquer une autre. . Dans de tels langages, la tentative de déclaration du second n entraînerait une erreur de syntaxe et l’une des n variables devrait être renommée.
Si un bloc est utilisé pour définir la valeur d’une variable, la portée du bloc nécessite que la variable soit déclarée en dehors du bloc. Cela complique l’utilisation d’instructions conditionnelles à Affectation unique . Par exemple, en Python, qui n’utilise pas de portée de bloc, on peut initialiser une variable comme telle :
si c : a = “foo” sinon : a = “”
où aest accessible après l’ ifinstruction.
En Perl, qui a une portée de bloc, cela nécessite à la place de déclarer la variable avant le bloc :
mon $a ; si ( c ) { $a = ‘foo’ ; } sinon { $a = ” ; }
Souvent, cela est plutôt réécrit à l’aide d’affectations multiples, initialisant la variable à une valeur par défaut. En Python (où ce n’est pas nécessaire) ce serait :
a = “” si c : a = “foo”
alors qu’en Perl ce serait :
mon $a = ” ; si ( c ) { $a = ‘foo’ ; }
Dans le cas d’une affectation de variable unique, une alternative consiste à utiliser l’ Opérateur ternaire pour éviter un bloc, mais ce n’est en général pas possible pour les affectations de variables multiples, et est difficile à lire pour une logique complexe.
Il s’agit d’un problème plus important en C, notamment pour l’affectation de chaînes, car l’initialisation d’une chaîne peut allouer automatiquement de la mémoire, tandis que l’affectation d’une chaîne à une variable déjà initialisée nécessite l’allocation de mémoire, une copie de chaîne et la vérification de leur réussite.
{ mon $compteur = 0 ; sub incrément_compteur { return ++ $compteur ; } }
Certains langages permettent d’appliquer le concept de portée de bloc, à des degrés divers, en dehors d’une fonction. Par exemple, dans l’extrait de code Perl à droite, $counterest un nom de variable avec une portée de bloc (en raison de l’utilisation du mot- myclé), alors increment_counterqu’est un nom de fonction avec une portée globale. Chaque appel à increment_counteraugmentera la valeur de $counterde un et renverra la nouvelle valeur. Le code en dehors de ce bloc peut appeler increment_counter, mais ne peut pas autrement obtenir ou modifier la valeur de $counter. Cet idiome permet de définir des fermetures en Perl.
Portée de la fonction
Lorsque la portée des variables déclarées dans une fonction ne s’étend pas au-delà de cette fonction, on parle de portée de la fonction . [7] La portée de la fonction est disponible dans la plupart des langages de programmation qui offrent un moyen de créer une variable locale dans une fonction ou une sous- routine : une variable dont la portée se termine (qui sort de son contexte) lorsque la fonction revient. Dans la plupart des cas, la durée de vie de la variable est la durée de l’appel de la fonction – c’est une variable automatique, créé lorsque la fonction démarre (ou que la variable est déclarée), détruit lorsque la fonction revient – tant que la portée de la variable est dans la fonction, bien que la signification de “dans” dépende du fait que la portée est lexicale ou dynamique. Cependant, certains langages, tels que C, fournissent également des variables locales statiques , où la durée de vie de la variable est la durée de vie entière du programme, mais la variable n’est en contexte que lorsqu’elle se trouve à l’intérieur de la fonction. Dans le cas des variables locales statiques, la variable est créée lors de l’initialisation du programme et détruite uniquement lorsque le programme se termine, comme avec une Variable globale statique , mais n’est que dans le contexte d’une fonction, comme une variable locale automatique.
Il est important de noter que dans la portée lexicale, une variable avec une portée de fonction n’a de portée que dans le contexte lexical de la fonction : elle sort de son contexte lorsqu’une autre fonction est appelée dans la fonction et revient dans son contexte lorsque la fonction revient – les fonctions appelées n’ont pas accès aux variables locales des fonctions appelantes, et les variables locales ne sont en contexte que dans le corps de la fonction dans laquelle elles sont déclarées. En revanche, dans la portée dynamique, la portée s’étend au contexte d’exécution de la fonction : les variables locales restent dans le contexte lorsqu’une autre fonction est appelée, ne sortant du contexte que lorsque la fonction de définition se termine, et donc les variables locales sont dans le contexte de la fonction. dans lequel elles sont définies et toutes appelées fonctions. Dans les langages avec une portée lexicale et des fonctions imbriquées , les variables locales sont dans le contexte des fonctions imbriquées, car elles se trouvent dans le même contexte lexical, mais pas pour les autres fonctions qui ne sont pas imbriquées lexicalement. Une variable locale d’une fonction englobante est appelée variable non locale pour la fonction imbriquée. La portée de la fonction s’applique également aux fonctions anonymes .
def carré ( n ): retour n * n def sum_of_squares ( n ): total = 0 i = 0 while i <= n : total += square ( i ) i += 1 return total
Par exemple, dans l’extrait de code Python à droite, deux fonctions sont définies : square et sum_of_squares . square calcule le carré d’un nombre ; sum_of_squares calcule la somme de tous les carrés jusqu’à un certain nombre. (Par exemple, square(4) est 4 2 = 16 , et sum_of_squares(4) est 0 2 + 1 2 + 2 2 + 3 2 + 4 2 = 30 .)
Chacune de ces fonctions a une variable nommée n qui représente l’argument de la fonction. Ces deux variables n sont complètement séparées et non liées, bien qu’elles portent le même nom, car ce sont des variables locales à portée lexicale avec une portée de fonction : la portée de chacune est sa propre fonction lexicalement séparée et, par conséquent, elles ne se chevauchent pas. Par conséquent, sum_of_squares peut appeler square sans que son propre n ne soit modifié. De même, sum_of_squares a des variables nommées total et i ; ces variables, en raison de leur portée limitée, n’interfèrent pas avec les variables nommées total ou iqui pourrait appartenir à n’importe quelle autre fonction. En d’autres termes, il n’y a aucun risque de collision de noms entre ces noms et des noms non apparentés, même s’ils sont identiques.
Aucun masquage de nom ne se produit : une seule variable nommée n est en contexte à un moment donné, car les portées ne se chevauchent pas. En revanche, si un fragment similaire devait être écrit dans un langage à portée dynamique, le n de la fonction appelante resterait en contexte dans la fonction appelée – les portées se chevaucheraient – et serait masqué (“ombré”) par le nouveau n dans la fonction appelée.
La portée de la fonction est beaucoup plus compliquée si les fonctions sont des objets de première classe et peuvent être créées localement dans une fonction, puis renvoyées. Dans ce cas, toutes les variables de la fonction imbriquée qui ne lui sont pas locales (variables non liées dans la définition de la fonction, qui se résolvent en variables dans un contexte englobant) créent une fermeture , car non seulement la fonction elle-même, mais aussi son contexte (de variables ) doit être renvoyé, puis potentiellement appelé dans un contexte différent. Cela nécessite beaucoup plus de support de la part du compilateur et peut compliquer l’analyse du programme.
Portée du fichier
La portée d’une liaison de nom est un fichier, appelé portée de fichier . La portée du fichier est en grande partie particulière à C (et C++), où la portée des variables et des fonctions déclarées au niveau supérieur d’un fichier (pas dans une fonction) est pour l’ensemble du fichier – ou plutôt pour C, de la déclaration jusqu’à la fin de le fichier source, ou plus précisément l’ unité de traduction (liaison interne). Cela peut être vu comme une forme de portée de module, où les modules sont identifiés avec des fichiers, et dans des langages plus modernes est remplacé par une portée de module explicite. En raison de la présence d’instructions d’inclusion, qui ajoutent des variables et des fonctions au contexte interne et peuvent elles-mêmes appeler des instructions d’inclusion supplémentaires, il peut être difficile de déterminer ce qui est en contexte dans le corps d’un fichier.
Dans l’extrait de code C ci-dessus, le nom de la fonction sum_of_squares a une portée de fichier.
Portée des modules
La portée d’une liaison de nom est un module, appelé portée de module . La portée du module est disponible dans les langages de programmation modulaires où les modules (qui peuvent s’étendre sur divers fichiers) sont l’unité de base d’un programme complexe, car ils permettent de masquer et d’exposer des informations sur une interface limitée. La portée du module a été lancée dans la famille de langages Modula , et Python (qui a été influencé par Modula) est un exemple contemporain représentatif.
Dans certains langages de programmation orientés objet qui ne prennent pas directement en charge les modules, tels que C++, une structure similaire est plutôt fournie par la hiérarchie des classes, où les classes sont l’unité de base du programme, et une classe peut avoir des méthodes privées. Ceci est correctement compris dans le contexte de la répartition dynamique plutôt que de la résolution et de la portée du nom, bien qu’ils jouent souvent des rôles analogues. Dans certains cas, ces deux fonctionnalités sont disponibles, comme dans Python, qui a à la fois des modules et des classes, et l’organisation du code (en tant que fonction au niveau du module ou méthode privée conventionnelle) est un choix du programmeur.
Portée mondiale
La portée d’une liaison de nom est un programme entier, appelé portée globale . Les noms de variables avec une portée globale – appelées Variables globales – sont souvent considérés comme une mauvaise pratique, du moins dans certains langages, en raison de la possibilité de collisions de noms et de masquage involontaire, ainsi que d’une faible modularité, et la portée des fonctions ou la portée des blocs sont considérées comme préférables. Cependant, la portée globale est généralement utilisée (selon le langage) pour divers autres types de noms, tels que les noms de fonctions, les noms de classes et les noms d’autres types de données . Dans ces cas, des mécanismes tels que les Espaces de noms sont utilisés pour éviter les collisions.
Portée lexicale vs portée dynamique
L’utilisation de variables locales – de noms de variables à portée limitée, qui n’existent que dans une fonction spécifique – permet d’éviter le risque d’une collision de noms entre deux variables portant le même nom. Cependant, il existe deux approches très différentes pour répondre à cette question : Que signifie être « dans » une fonction ?
Dans la portée lexicale (ou portée lexicale ; également appelée portée statique ou portée statique ), si la portée d’un nom de variable est une certaine fonction, alors sa portée est le texte du programme de la définition de la fonction : dans ce texte, le nom de la variable existe et est lié à la valeur de la variable, mais en dehors de ce texte, le nom de la variable n’existe pas. En revanche, dans la portée dynamique (ou portée dynamique ), si la portée d’un nom de variable est une certaine fonction, alors sa portée est la période pendant laquelle la fonction s’exécute : pendant que la fonction est en cours d’exécution, le nom de la variable existe et est lié à sa valeur, mais après le retour de la fonction, le nom de la variable n’existe pas. Cela signifie que si la fonctionf appelle une fonction g définie séparément , puis sous portée lexicale, la fonction g n’a pas accès aux variables locales de f (en supposant que le texte de g n’est pas à l’intérieur du texte de f ), tandis que sous portée dynamique, la fonction g a accès aux variables locales de f (puisque g est invoqué lors de l’invocation de f ).
$ # langage bash $ x = 1 $ fonction g () { echo $x ; x = 2 ; } $ fonction f () { local x = 3 ; g ; } $ f # est-ce que cela affiche 1 ou 3 ? 3 $ echo $x # est-ce que cela affiche 1 ou 2 ? 1
Considérez, par exemple, le programme sur la droite. La première ligne, , crée une variable globale x et l’initialise à 1 . La deuxième ligne, , définit une fonction g qui imprime (“fait écho”) la valeur actuelle de x , puis définit x sur 2 (écrasant la valeur précédente). La troisième ligne définit une fonction f qui crée une variable locale x (cachant la variable globale portant le même nom) et l’initialise à 3 , puis appelle g . La quatrième ligne, , appelle f . La cinquième ligne,x=1function g() { echo $x ; x=2 ; }function f() { local x=3 ; g ; }fecho $x, imprime la valeur actuelle de x .
Alors, qu’est-ce que ce programme imprime exactement ? Cela dépend des règles de portée. Si le langage de ce programme est un langage qui utilise la portée lexicale, alors g imprime et modifie la variable globale x (parce que g est défini en dehors de f ), donc le programme imprime 1 puis 2 . En revanche, si ce langage utilise une portée dynamique, alors g imprime et modifie la variable locale de f x (parce que g est appelé depuis f ), donc le programme imprime 3 puis 1 . (Il se trouve que le langage du programme est Bash, qui utilise une étendue dynamique ; donc le programme imprime 3 puis 1 . Si le même code était exécuté avec ksh93 qui utilise la portée lexicale, les résultats seraient différents.)
Portée lexicale
Avec la portée lexicale , un nom fait toujours référence à son contexte lexical. Il s’agit d’une propriété du texte du programme et elle est rendue indépendante de la pile des appels d’exécution par l’implémentation du langage. Étant donné que cette mise en correspondance nécessite uniquement une analyse du texte statique du programme, ce type de portée est également appelé portée statique . La portée lexicale est standard dans tous les langages basés sur ALGOL tels que Pascal , Modula-2 et Ada ainsi que dans les langages fonctionnels modernes tels que ML et Haskell . Il est également utilisé dans le langage Cet ses parents syntaxiques et sémantiques, bien qu’avec différents types de limitations. La portée statique permet au programmeur de raisonner sur les références d’objets telles que les paramètres, les variables, les constantes, les types, les fonctions, etc. en tant que simples substitutions de noms. Cela facilite beaucoup la création de code modulaire et le raisonnement à ce sujet, car la structure de dénomination locale peut être comprise de manière isolée. En revanche, la portée dynamique oblige le programmeur à anticiper tous les contextes d’exécution possibles dans lesquels le code du module peut être invoqué.
programme A ; var I : entier ; K : caractère ; procédure B ; var K : réel ; L : entier ; procédure C ; var M : réel ; début (*portée A+B+C*) fin ; (*portée A+B*) fin ; (*périmètre A*) fin .
Par exemple, Pascal a une portée lexicale. Considérez le fragment de programme Pascal à droite. La variable Iest visible en tout point, car elle n’est jamais masquée par une autre variable du même nom. La charvariable Kn’est visible que dans le programme principal car elle est masquée par la realvariable Kvisible dans la procédure Bet Cuniquement. La variable Lest également visible uniquement dans la procédure Bet Cne masque aucune autre variable. La variable Mn’est visible que dans la procédure Cet n’est donc accessible ni depuis la procédure Bni depuis le programme principal. De plus, la procédure Cn’est visible que dans la procédure Bet ne peut donc pas être appelée depuis le programme principal.
Il aurait pu y avoir une autre procédure Cdéclarée dans le programme en dehors de procédure B. L’endroit dans le programme où ” C” est mentionné détermine alors laquelle des deux procédures nommées Cil représente, donc précisément analogue à la portée des variables.
L’implémentation correcte de la portée lexicale dans les langages avec des fonctions imbriquées de première classe n’est pas triviale, car elle nécessite que chaque valeur de fonction porte avec elle un enregistrement des valeurs des variables dont elle dépend (le couple de la fonction et de ce contexte est appelé une fermeture ). Selon l’implémentation et l’architecture de l’ordinateur , la recherche de variables peut devenir légèrement inefficace lorsque des fonctions très profondément imbriquées lexicalement sont utilisées, bien qu’il existe des techniques bien connues pour atténuer cela. [8] [9] De plus, pour les fonctions imbriquées qui ne font référence qu’à leurs propres arguments et variables locales (immédiates), tous les emplacements relatifs peuvent être connus au moment de la compilation . Aucune surcharge n’est donc encourue lors de l’utilisation de ce type de fonction imbriquée. Il en va de même pour certaines parties d’un programme où les fonctions imbriquées ne sont pas utilisées et, naturellement, pour les programmes écrits dans un langage où les fonctions imbriquées ne sont pas disponibles (comme dans le langage C).
Histoire
La portée lexicale a été utilisée pour la première fois au début des années 1960 pour le langage impératif ALGOL 60 et a été reprise dans la plupart des autres langages impératifs depuis lors. [4]
Des langages comme Pascal et C ont toujours eu une portée lexicale, car ils sont tous deux influencés par les idées qui sont entrées dans ALGOL 60 et Algol 68 (bien que C n’inclue pas de fonctions lexicalement imbriquées ).
Perl est un langage à portée dynamique qui a ajouté une portée statique par la suite.
L’ interpréteur Lisp original (1960) utilisait une portée dynamique. La liaison profonde , qui se rapproche de la portée statique (lexicale), a été introduite vers 1962 dans LISP 1.5 (via le dispositif Funarg développé par Steve Russell , travaillant sous John McCarthy ).
Tous les premiers Lisps utilisaient une portée dynamique, lorsqu’ils étaient basés sur des interpréteurs. En 1982, Guy L. Steele Jr. et le Common LISP Group publient An overview of Common LISP , [10] une brève revue de l’histoire et des implémentations divergentes de Lisp jusqu’à ce moment et une revue des fonctionnalités qu’un Common Lisp la mise en œuvre devrait avoir. À la page 102, nous lisons :
La plupart des implémentations LISP sont incohérentes en interne en ce que, par défaut, l’interpréteur et le compilateur peuvent attribuer une sémantique différente aux programmes corrects; cela provient principalement du fait que l’interpréteur suppose que toutes les variables ont une portée dynamique, tandis que le compilateur suppose que toutes les variables sont locales à moins d’être forcé de supposer le contraire. Cela a été fait dans un souci de commodité et d’efficacité, mais peut entraîner des bogues très subtils. La définition de Common LISP évite de telles anomalies en demandant explicitement à l’interpréteur et au compilateur d’imposer une sémantique identique aux programmes corrects.
Les implémentations de Common LISP devaient donc avoir une portée lexicale . Encore une fois, à partir d’ un aperçu de Common LISP :
De plus, Common LISP offre les fonctionnalités suivantes (dont la plupart sont empruntées à MacLisp, InterLisp ou Lisp Machines Lisp): (…) Variables à portée lexicale complète. Le soi-disant “problème Funarg” [11] [12] est complètement résolu, à la fois dans les cas descendants et ascendants.
La même année au cours de laquelle An overview of Common LISP a été publié (1982), les conceptions initiales (également par Guy L. Steele Jr.) d’un Lisp compilé à portée lexicale, appelé Scheme , avaient été publiées et des implémentations de compilateur étaient tentées. À cette époque, on craignait généralement que la portée lexicale en Lisp soit inefficace à mettre en œuvre. Dans A History of T , [13] Olin Shivers écrit :
Tous les Lisps sérieux utilisés en production à cette époque étaient définis dynamiquement. Personne qui n’avait pas lu attentivement la thèse de Rabbit [14] (écrite par Guy Lewis Steele Jr. en 1978) ne pensait que la portée lexicale volerait ; même les quelques personnes qui l’ avaient lu croyaient un peu que cela allait fonctionner dans une utilisation de production sérieuse.
Le terme “portée lexicale” date au moins de 1967, [15] tandis que le terme “portée lexicale” date au moins de 1970, où il a été utilisé dans le Projet MAC pour décrire les règles de portée du dialecte Lisp MDL (alors connu sous le nom de ” Confusion”). [16]
Portée dynamique
Avec une portée dynamique , un nom fait référence au contexte d’exécution. Il est rare dans les langues modernes. [4] En termes techniques, cela signifie que chaque nom a une pile globale de liaisons. L’ introduction d’une variable locale avec le nom xpousse une liaison sur la xpile globale (qui peut avoir été vide), qui est supprimée lorsque le flux de contrôle quitte la portée. L’évaluation xdans n’importe quel contexte produit toujours la liaison supérieure. Notez que cela ne peut pas être fait au moment de la compilation car la pile de liaison n’existe qu’au moment de l’exécution , c’est pourquoi ce type de portée est appelé portée dynamique .
Généralement, certains blocs sont définis pour créer des liaisons dont la durée de vie est le temps d’exécution du bloc ; cela ajoute certaines fonctionnalités de portée statique au processus de portée dynamique. Cependant, étant donné qu’une section de code peut être appelée à partir de nombreux emplacements et situations différents, il peut être difficile de déterminer dès le départ quelles liaisons s’appliqueront lorsqu’une variable est utilisée (ou s’il en existe une). Cela peut être bénéfique ; l’application du Principe de moindre connaissance suggère que le code évite selon les raisonspour (ou les circonstances de) la valeur d’une variable, mais utilisez simplement la valeur selon la définition de la variable. Cette interprétation étroite des données partagées peut fournir un système très flexible pour adapter le comportement d’une fonction à l’état actuel (ou politique) du système. Cependant, cet avantage repose sur une documentation minutieuse de toutes les variables utilisées de cette manière ainsi que sur un évitement prudent des hypothèses sur le comportement d’une variable, et ne fournit aucun mécanisme pour détecter les interférences entre les différentes parties d’un programme. Certains langages, comme Perl et Common Lisp , permettent au programmeur de choisir une portée statique ou dynamique lors de la définition ou de la redéfinition d’une variable. Des exemples de langages qui utilisent la portée dynamique incluent Logo , Emacs Lisp ,LaTeX et les langages shell bash , dash et PowerShell .
La portée dynamique est assez facile à mettre en œuvre. Pour trouver la valeur d’un nom, le programme peut parcourir la pile d’exécution, en vérifiant chaque enregistrement d’activation (le cadre de pile de chaque fonction) pour une valeur pour le nom. En pratique, cela est rendu plus efficace grâce à l’utilisation d’une liste d’associations , qui est une pile de paires nom/valeur. Les paires sont poussées sur cette pile chaque fois que des déclarations sont faites et sautées chaque fois que les variables sortent de leur contexte. [17] La liaison peu profonde est une stratégie alternative considérablement plus rapide, utilisant une table de référence centrale , qui associe chaque nom à sa propre pile de significations. Cela évite une recherche linéaire pendant l’exécution pour trouver un nom particulier, mais il faut veiller à maintenir correctement cette table. [17]Notez que ces deux stratégies supposent un ordre dernier-entré-premier-sorti ( LIFO ) pour les liaisons pour n’importe quelle variable ; en pratique, toutes les fixations sont ainsi ordonnées.
Une implémentation encore plus simple est la représentation des variables dynamiques avec des Variables globales simples. La liaison locale est effectuée en enregistrant la valeur d’origine dans un emplacement anonyme sur la pile qui est invisible pour le programme. Lorsque cette étendue de liaison se termine, la valeur d’origine est restaurée à partir de cet emplacement. En fait, la portée dynamique est née de cette manière. Les premières implémentations de Lisp utilisaient cette stratégie évidente pour implémenter des variables locales, et la pratique survit dans certains dialectes encore utilisés, tels que GNU Emacs Lisp. La portée lexicale a été introduite dans Lisp plus tard. Ceci est équivalent au schéma de liaison superficielle ci-dessus, sauf que la table de référence centrale est simplement le contexte de liaison de variable globale, dans lequel la signification actuelle de la variable est sa valeur globale. La gestion des Variables globales n’est pas complexe.
La portée dynamique fournit une excellente abstraction pour le stockage local des threads, mais s’il est utilisé de cette manière, il ne peut pas être basé sur la sauvegarde et la restauration d’une variable globale. Une stratégie de mise en œuvre possible consiste à ce que chaque variable ait une clé locale de thread. Lors de l’accès à la variable, la clé locale du thread est utilisée pour accéder à l’emplacement de la mémoire locale du thread (par le code généré par le compilateur, qui sait quelles variables sont dynamiques et lesquelles sont lexicales). Si la clé locale du thread n’existe pas pour le thread appelant, l’emplacement global est utilisé. Lorsqu’une variable est liée localement, la valeur précédente est stockée dans un emplacement caché de la pile. Le stockage local du thread est créé sous la clé de la variable et la nouvelle valeur y est stockée. D’autres remplacements imbriqués de la variable dans ce thread enregistrent et restaurent simplement cet emplacement local du thread. Lorsque le contexte de remplacement initial le plus externe se termine,
Avec la transparence référentielle , la portée dynamique est limitée à la pile d’arguments de la fonction actuelle uniquement et coïncide avec la portée lexicale.
Extension macro
Dans les langages modernes, l’ expansion de macros dans un préprocesseur est un exemple clé de portée dynamique de facto. Le langage macro lui-même ne fait que transformer le code source, sans résoudre les noms, mais comme l’expansion est effectuée sur place, lorsque les noms dans le texte développé sont ensuite résolus (notamment les variables libres), ils sont résolus en fonction de l’endroit où ils sont développés (vaguement “appelé”), comme si la portée dynamique se produisait.
Le préprocesseur C , utilisé pour l’expansion des macros , a de facto une portée dynamique, car il ne fait pas de résolution de nom par lui-même et il est indépendant de l’endroit où la macro est définie. Par exemple, la macro :
#define ADD_A(x) x + a
se développera pour ajouter aà la variable passée, ce nom n’étant résolu que plus tard par le compilateur en fonction de l’endroit où la macro ADD_Aest “appelée” (proprement, développée). Correctement, le préprocesseur C ne fait qu’une analyse lexicale , en développant la macro pendant l’étape de tokenisation, mais sans analyser dans un arbre de syntaxe ni effectuer de résolution de nom.
Par exemple, dans le code suivant, le nom adans la macro est résolu (après expansion) dans la variable locale sur le site d’expansion :
#define ADD_A(x) x + a void add_one ( int * x ) { const entier a = 1 ; * x = ADD_A ( * x ); } void add_two ( int * x ) { const int a = 2 ; * x = ADD_A ( * x ); }
Noms qualifiés
Comme nous l’avons vu, l’une des principales raisons de la portée est qu’elle permet d’éviter les collisions de noms, en permettant à des noms identiques de se référer à des choses distinctes, avec la restriction que les noms doivent avoir des portées distinctes. Parfois, cette restriction est gênante ; lorsque de nombreuses choses différentes doivent être accessibles dans un programme, elles ont généralement toutes besoin de noms avec une portée globale, donc différentes techniques sont nécessaires pour éviter les collisions de noms.
Pour résoudre ce problème, de nombreuses langues proposent des mécanismes d’organisation des noms globaux. Les détails de ces mécanismes et les termes utilisés dépendent de la langue ; mais l’idée générale est qu’un groupe de noms peut lui-même recevoir un nom – un préfixe – et, si nécessaire, une entité peut être désignée par un nom qualifié composé du nom plus le préfixe. Normalement, ces noms auront, en un sens, deux ensembles de portées : une portée (généralement la portée globale) dans laquelle le nom qualifié est visible, et une ou plusieurs portées plus étroites dans lesquelles le nom non qualifié (sans le préfixe) est visible comme Bien. Et normalement ces groupes peuvent eux-mêmes être organisés en groupes ; c’est-à-dire qu’ils peuvent être imbriqués .
Bien que de nombreuses langues prennent en charge ce concept, les détails varient considérablement. Certains langages ont des mécanismes, tels que les espaces de noms en C++ et C# , qui servent presque exclusivement à permettre aux noms globaux d’être organisés en groupes. D’autres langages ont des mécanismes, tels que des packages dans Ada et des structures dans Standard ML , qui combinent cela avec l’objectif supplémentaire de permettre à certains noms d’être visibles uniquement par les autres membres de leur groupe. Et les langages orientés objet permettent souvent aux classes ou aux objets singleton de remplir cet objectif (qu’ils soient également ou nonavoir un mécanisme dont c’est le but principal). De plus, les langues fusionnent souvent ces approches ; par exemple, les packages de Perl sont largement similaires aux Espaces de noms de C++, mais peuvent éventuellement servir de classes pour la programmation orientée objet ; et Java organise ses variables et ses fonctions en classes, mais organise ensuite ces classes en packages de type Ada.
Par langue
Apprendre encore plus Cette section a besoin d’être agrandie . Vous pouvez aider en y ajoutant . ( avril 2013 ) |
Les règles de portée pour les langues représentatives suivent.
C
En C, la portée est traditionnellement appelée liaison ou visibilité , en particulier pour les variables. C est un langage à portée lexicale avec une portée globale (appelée liaison externe ), une forme de portée de module ou de portée de fichier (appelée liaison interne ) et une portée locale (au sein d’une fonction) ; au sein d’une fonction, les portées peuvent en outre être imbriquées via la portée du bloc. Cependant, le C standard ne prend pas en charge les fonctions imbriquées.
La durée de vie et la visibilité d’une variable sont déterminées par sa classe de stockage . Il existe trois types de durées de vie en C : statique (exécution de programme), automatique (exécution de bloc, allouée sur la pile) et manuelle (allouée sur le tas). Seuls les variables statiques et automatiques sont pris en charge pour les variables et gérés par le compilateur, tandis que la mémoire allouée manuellement doit être suivie manuellement sur différentes variables. Il existe trois niveaux de visibilité en C : lien externe (global), lien interne (en gros fichier) et portée de bloc (qui inclut des fonctions) ; les portées de bloc peuvent être imbriquées et différents niveaux de liaison interne sont possibles grâce à l’utilisation d’inclusions. La liaison interne en C est la visibilité au niveau de l’ unité de traduction , à savoir un fichier source après traitement par lePréprocesseur C , incluant notamment tous les include pertinents.
Les programmes C sont compilés sous forme de fichiers objets séparés , qui sont ensuite liés dans un exécutable ou une bibliothèque via un éditeur de liens . Ainsi, la résolution de noms est répartie entre le compilateur, qui résout les noms dans une unité de traduction (plus vaguement, “unité de compilation”, mais il s’agit bien d’un concept différent), et l’éditeur de liens, qui résout les noms entre les unités de traduction ; voir le lien pour une discussion plus approfondie.
En C, les variables avec une portée de bloc entrent dans le contexte lorsqu’elles sont déclarées (pas en haut du bloc), sortent du contexte si une fonction (non imbriquée) est appelée dans le bloc, reviennent dans le contexte lorsque la fonction revient, et sortir du contexte à la fin du bloc. Dans le cas des variables locales automatiques, elles sont également allouées à la déclaration et désallouées en fin de bloc, tandis que pour les variables locales statiques, elles sont allouées à l’initialisation du programme et désallouées à la fin du programme.
Le programme suivant illustre une variable avec une portée de bloc entrant dans le contexte à mi-chemin du bloc, puis sortant du contexte (et en fait étant désallouée) lorsque le bloc se termine :
#include <stdio.h> int principal ( vide ) { car x = ‘m’ ; printf ( “%c n ” , x ); { printf ( “%c n ” , x ); car x = ‘b’ ; printf ( “%c n ” , x ); } printf ( “%c n ” , x ); }
Le programme affiche :
m m b m
Il existe d’autres niveaux de portée en C. [18] Les noms de variable utilisés dans un prototype de fonction ont une visibilité de prototype de fonction et un contexte de sortie à la fin du prototype de fonction. Comme le nom n’est pas utilisé, cela n’est pas utile pour la compilation, mais peut être utile pour la documentation. Les noms d’étiquette pour l’instruction GOTO ont une portée de fonction, tandis que les noms d’étiquette de cas pour les instructions de commutateur ont une portée de bloc (le bloc du commutateur).
C++
Toutes les variables que nous avons l’intention d’utiliser dans un programme doivent avoir été déclarées avec leur spécificateur de type à un point antérieur du code, comme nous l’avons fait dans le code précédent au début du corps de la fonction main lorsque nous avons déclaré que a, b, et result étaient de type int. Une variable peut avoir une portée globale ou locale. Une variable globale est une variable déclarée dans le corps principal du code source, en dehors de toutes les fonctions, tandis qu’une variable locale est une variable déclarée dans le corps d’une fonction ou d’un bloc.
Les versions modernes permettent une portée lexicale imbriquée.
Rapide
Swift a une règle similaire pour les étendues avec C++, mais contient différents modificateurs d’accès.
Modificateur | Portée immédiate | Dossier | Module/paquet contenant | Reste du monde |
---|---|---|---|---|
ouvrir | Oui | Oui | Oui | Oui, permet la sous-classe |
Publique | Oui | Oui | Oui | Oui, interdit la sous-classe |
interne | Oui | Oui | Oui | Non |
fichierprivé | Oui | Oui | Non | Non |
privé | Oui | Non | Non | Non |
Aller
Go est limité lexicalement à l’aide de blocs. [3]
Java
Java est limité lexicalement.
Une classe Java peut contenir trois types de variables : [19]
Variables locales sont définis à l’intérieur d’une méthode ou d’un bloc particulier. Ces variables sont locales à l’endroit où elles ont été définies et à des niveaux inférieurs. Par exemple, une boucle à l’intérieur d’une méthode peut utiliser les variables locales de cette méthode, mais pas l’inverse. Les variables de la boucle (locales à cette boucle) sont détruites dès que la boucle se termine. Variables membres également appelés champs sont des variables déclarées dans la classe, en dehors de toute méthode. Par défaut, ces variables sont disponibles pour toutes les méthodes de cette classe et également pour toutes les classes du package. Paramètres sont des variables dans les déclarations de méthode.
En général, un ensemble de crochets définit une portée particulière, mais les variables au niveau supérieur d’une classe peuvent différer dans leur comportement en fonction des mots-clés modificateurs utilisés dans leur définition. Le tableau suivant montre l’accès aux membres autorisé par chaque modificateur. [20]
Modificateur | Classe | Emballer | Sous-classe | Monde |
---|---|---|---|---|
Publique | Oui | Oui | Oui | Oui |
protégé | Oui | Oui | Oui | Non |
(pas de modificateur) | Oui | Oui | Non | Non |
privé | Oui | Non | Non | Non |
Javascript
JavaScript a des règles de portée simples , [21] mais les règles d’initialisation variable et de résolution de noms peuvent causer des problèmes, et l’utilisation généralisée des fermetures pour les rappels signifie que le contexte lexical d’une fonction lorsqu’elle est définie (qui est utilisée pour la résolution de noms) peut être très différent de le contexte lexical lorsqu’il est appelé (ce qui n’est pas pertinent pour la résolution de noms). Les objets JavaScript ont une résolution de nom pour les propriétés, mais il s’agit d’un sujet distinct.
JavaScript a une portée lexicale [22] imbriquée au niveau de la fonction, le contexte global étant le contexte le plus externe. Cette portée est utilisée à la fois pour les variables et pour les fonctions (c’est-à-dire les déclarations de fonction, par opposition aux variables de type fonction ). [23] La portée de bloc avec les mots clés letet constest standard depuis ECMAScript 6. La portée de bloc peut être produite en enveloppant le bloc entier dans une fonction, puis en l’exécutant ; c’est ce qu’on appelle le modèle d’ expression de fonction immédiatement invoquée (IIFE).
Bien que la portée de JavaScript soit simple – lexicale, au niveau de la fonction – les règles d’initialisation et de résolution de nom associées sont source de confusion. Premièrement, l’affectation à un nom qui n’est pas dans la portée crée par défaut une nouvelle variable globale, et non une variable locale. Deuxièmement, pour créer une nouvelle variable locale, il faut utiliser le mot- varclé ; la variable est alors créée en haut de la fonction, avec valeur undefinedet la variable reçoit sa valeur lorsque l’expression d’affectation est atteinte :
Une variable avec un Initialiser reçoit la valeur de son AssignmentExpression lorsque le VariableStatement est exécuté, pas lorsque la variable est créée. [24]
C’est ce qu’on appelle le hissage de variable [25] — la déclaration, mais pas l’initialisation, est hissée au sommet de la fonction. Troisièmement, l’accès aux variables avant l’initialisation génère undefined, plutôt qu’une erreur de syntaxe. Quatrièmement, pour les déclarations de fonction, la déclaration et l’initialisation sont toutes deux hissées au sommet de la fonction, contrairement à l’initialisation de variable. Par exemple, le code suivant produit une boîte de dialogue avec une sortieindéfini, comme la déclaration de la variable locale est hissée, masquant la variable globale, mais l’initialisation ne l’est pas, donc la variable est indéfinie lorsqu’elle est utilisée :
un = 1 ; fonction f () { alerte ( une ); var a = 2 ; } f ();
De plus, comme les fonctions sont des objets de première classe en JavaScript et sont fréquemment affectées en tant que rappels ou renvoyées par des fonctions, lorsqu’une fonction est exécutée, la résolution du nom dépend de l’endroit où elle a été définie à l’origine (le contexte lexical de la définition), et non du contexte lexical. contexte ou contexte d’exécution où il est appelé. Les portées imbriquées d’une fonction particulière (de la plus globale à la plus locale) en JavaScript, en particulier d’une fermeture, utilisée comme rappel, sont parfois appelées chaîne de portée , par analogie avec la chaîne prototype d’un objet.
Les fermetures peuvent être produites en JavaScript en utilisant des fonctions imbriquées, car les fonctions sont des objets de première classe. [26] Le retour d’une fonction imbriquée à partir d’une fonction englobante inclut les variables locales de la fonction englobante en tant que contexte lexical (non local) de la fonction renvoyée, ce qui donne une fermeture. Par example:
function newCounter () { // retourne un compteur qui s’incrémente à l’appel (commençant à 0) // et qui retourne sa nouvelle valeur var a = 0 ; var b = fonction () { une ++ ; retourner un ; } ; retour b ; } c = nouveauCompteur (); alerte ( c () + ‘ ‘ + c ()); // affiche “1 2”
Les fermetures sont fréquemment utilisées dans JavaScript, car elles sont utilisées pour les rappels. En effet, tout accrochage d’une fonction dans le contexte local en tant que rappel ou retour d’une fonction crée une fermeture s’il y a des variables non liées dans le corps de la fonction (avec le contexte de la fermeture basé sur les portées imbriquées du contexte lexical actuel , ou “chaîne de portée”); cela peut être accidentel. Lors de la création d’un rappel basé sur des paramètres, les paramètres doivent être stockés dans une fermeture, sinon cela créera accidentellement une fermeture qui fait référence aux variables dans le contexte englobant, qui peut changer. [27]
La résolution de noms des propriétés des objets JavaScript est basée sur l’héritage dans l’arborescence du prototype (un chemin vers la racine de l’arbre est appelé une chaîne de prototypes ) et est distincte de la résolution des noms des variables et des fonctions.
Zézayer
Les dialectes Lisp ont diverses règles de portée.
Le Lisp original utilisait une portée dynamique; c’est Scheme , inspiré d ‘ ALGOL , qui a introduit la portée statique (lexicale) dans la famille Lisp.
Maclisp utilisait la portée dynamique par défaut dans l’interpréteur et la portée lexicale par défaut dans le code compilé, bien que le code compilé puisse accéder aux liaisons dynamiques en utilisant des SPECIALdéclarations pour des variables particulières. [28] Cependant, Maclisp a traité la liaison lexicale plus comme une optimisation que ce à quoi on pourrait s’attendre dans les langues modernes, et elle n’est pas venue avec la fonctionnalité de fermeture à laquelle on pourrait s’attendre de la portée lexicale dans les Lisps modernes. Une opération distincte, *FUNCTION, était disponible pour contourner quelque peu maladroitement une partie de ce problème. [29]
Common Lisp a adopté la portée lexicale de Scheme , [30] tout comme Clojure .
ILISISP a une portée lexicale pour les variables ordinaires. Il a aussi des variables dynamiques, mais elles sont dans tous les cas explicitement marquées ; ils doivent être définis par une defdynamicforme spéciale, liés par une dynamic-letforme spéciale et accessibles par une dynamicforme spéciale explicite. [31]
Certains autres dialectes de Lisp, comme Emacs Lisp , utilisent toujours la portée dynamique par défaut. Emacs Lisp a maintenant une portée lexicale disponible sur une base par tampon. [32]
Python
Pour les variables, Python a une portée de fonction, une portée de module et une portée globale. Les noms entrent dans le contexte au début d’une portée (fonction, module ou portée globale) et sortent du contexte lorsqu’une fonction non imbriquée est appelée ou que la portée se termine. Si un nom est utilisé avant l’initialisation de la variable, cela déclenche une exception d’exécution. Si une variable est simplement accessible (non affectée), la résolution de nom suit la règle LEGB (Local, Enclosing, Global, Built-in) qui résout les noms dans le contexte pertinent le plus étroit. Cependant, si une variable est affectée à, elle déclare par défaut une variable dont la portée commence au début du niveau (fonction, module ou global), et non à l’affectation. Ces deux règles peuvent être remplacées par un globalounonlocal(en Python 3) déclaration préalable à l’utilisation, qui permet d’accéder aux Variables globales même s’il existe une variable non locale de masquage, et de les affecter à des Variables globales ou non locales.
À titre d’exemple simple, une fonction résout une variable dans la portée globale :
>>> def f (): … print ( x ) … >>> x = “global” >>> f () global
Notez que xest défini avant d’ fêtre appelé, donc aucune erreur n’est générée, même s’il est défini après sa référence dans la définition de f. Lexiquement, il s’agit d’une référence directe , ce qui est autorisé en Python.
Ici, l’affectation crée une nouvelle variable locale, qui ne change pas la valeur de la variable globale :
>>> def f (): … x = “f” … print ( x ) … >>> x = “global” >>> print ( x ) global >>> f () f > >> imprimer ( x ) global
L’affectation à une variable dans une fonction entraîne sa déclaration locale à la fonction, sa portée est donc l’ensemble de la fonction, et donc son utilisation avant cette affectation génère une erreur. Cela diffère de C, où la portée de la variable locale commence à sa déclaration. Ce code génère une erreur :
>>> def f (): … print ( x ) … x = “f” … >>> x = “global” >>> f () Traceback (appel le plus récent en dernier) : File ” <stdin>” , ligne 1 , dans <module> Fichier “<stdin>” , ligne 2 , dans f UnboundLocalError : variable locale ‘x’ référencée avant l’affectation
Les règles de résolution de noms par défaut peuvent être remplacées par les mots-clés globalou nonlocal(en Python 3). Dans le code ci-dessous, la global xdéclaration dans gsignifie que xse résout en la variable globale. Il est donc possible d’y accéder (car il a déjà été défini) et de l’affecter à la variable globale, plutôt que de déclarer une nouvelle variable locale. Notez qu’aucune globaldéclaration n’est nécessaire dans f- puisqu’elle n’affecte pas la variable, elle résout par défaut la variable globale.
>>> def f (): … print ( x ) … >>> def g (): … global x … print ( x ) … x = “g” … > >> x = “global” >>> f () global >>> g () global >>> f () g
globalpeut également être utilisé pour les fonctions imbriquées. En plus de permettre l’affectation à une variable globale, comme dans une fonction non imbriquée, cela peut également être utilisé pour accéder à la variable globale en présence d’une variable non locale :
>>> def f (): … def g (): … global x … print ( x ) … x = “f” … g () … >>> x = “global” >>> f () global
Pour les fonctions imbriquées, il existe également la nonlocaldéclaration, pour l’affectation à une variable non locale, similaire à l’utilisation globaldans une fonction non imbriquée :
>>> def f (): … def g (): … nonlocal x # Python 3 uniquement … x = “g” … x = “f” … g () … print ( x ) … >>> x = “global” >>> f () g >>> print ( x ) global
R
R est un langage à portée lexicale, contrairement aux autres implémentations de S où les valeurs des variables libres sont déterminées par un ensemble de Variables globales, tandis que dans R, elles sont déterminées par le contexte dans lequel la fonction a été créée. [33] Les contextes de portée peuvent être consultés en utilisant une variété de fonctionnalités (telles que parent.frame()) qui peuvent simuler l’expérience de la portée dynamique si le programmeur le souhaite.
Il n’y a pas de portée de bloc :
une <- 1 { une <- 2 } message ( une ) ## 2
Les fonctions ont accès à la portée dans laquelle elles ont été créées :
une <- 1 f <- fonction () { message ( une ) } f () ## 1
Les variables créées ou modifiées dans une fonction y restent :
une <- 1 f <- fonction () { message ( une ) a <- 2 message ( une ) } f () ## 1 ## 2 message ( une ) ## 1
Les variables créées ou modifiées dans une fonction y restent sauf si l’affectation à la portée englobante est explicitement demandée :
une <- 1 f <- fonction () { message ( une ) a <<- 2 message ( une ) } f () ## 1 ## 2 message ( une ) ## 2
Bien que R ait une portée lexicale par défaut, les portées des fonctions peuvent être modifiées :
a <- 1 f <- function () { message ( a ) } my_env <- new.env () my_env $ a <- 2 f () ## 1 environment ( f ) <- my_env f () ## 2
Voir également
- Clôture (informatique)
- Variable globale
- Variable locale
- Laisser s’exprimer
- Variable non locale
- Liaison de nom
- Résolution de noms (langages de programmation)
- Variables (portée et étendue)
- Masquage d’informations
- Expressions de fonction immédiatement appelées en Javascript
- Durée de vie de l’objet
Remarques
- ^ Voir la définition pour la signification de “portée” par rapport à “contexte”.
- ^ “Portée dynamique” base la résolution de nom sur l’ étendue (durée de vie), et non sur la portée , et est donc formellement inexacte.
- ^ Par exemple, le moteur de modèle Jinja pour Python utilise par défaut à la fois la portée lexicale (pour les importations) et la portée dynamique (pour les inclusions), et permet de spécifier le comportement avec des mots-clés ; voir Comportement du contexte d’importation .
- ^ “Résolution de nom” et “liaison de nom” sont en grande partie synonymes ; au sens strict, la “résolution” détermine à quel nom une utilisation particulière d’un nom se réfère, sans l’associer à aucune signification, comme dans la syntaxe abstraite d’ordre supérieur , tandis que la “liaison” associe le nom à une signification réelle. En pratique, les termes sont utilisés de manière interchangeable.
- ^ Pour le code auto-modifiable, le contexte lexical lui-même peut changer pendant l’exécution.
- ^ En revanche, * “le contexte d’une liaison de nom”, * “une liaison de nom entrant dans la portée” ou * “une liaison de nom sortant de la portée” sont tous incorrects – une liaison de nom a une portée, tandis qu’une partie d’un programme a un contexte .
Références
- ^ “Rapport sur le langage algorithmique Algol 60”, 2.7. Quantités, types et étendues
- ^ WG14 N1256 (version mise à jour 2007 de la norme C99 ), 6.2.1 Champs d’application des identifiants, 07/09/2007
- ^ a b La spécification du langage de programmation Go : déclarations et portée , version du 13 novembre 2013
- ^ un bc Borning A. CSE 341 – Portée Lexicale et Dynamique . Université de Washington.
- ^ Crockford, Douglas. “Conventions de code pour le langage de programmation JavaScript” . Récupéré le 04/01/2015 .
- ^ Backus, JW; Wegstein, JH; Van Wijngaarden, A.; Woodger, M.; Bauer, FL; Vert, J. ; Katz, C.; McCarthy, J.; Perlis, AJ; Rutishauser, H.; Samelson, K.; Vauquois, B. (1960). “Rapport sur le langage algorithmique ALGOL 60”. Communications de l’ACM . 3 (5): 299. doi : 10.1145/367236.367262 . S2CID 278290 .
- ^ “Fonctions – Javascript : MDN” . Les variables définies à l’intérieur d’une fonction ne sont pas accessibles depuis n’importe où en dehors de la fonction, car la variable est définie uniquement dans la portée de la fonction. Cependant, une fonction peut accéder à toutes les variables et fonctions définies à l’intérieur de la portée dans laquelle elle est définie.
- ^ ” Pragmatique du langage de programmation “, table des symboles LeBlank-Cook
- ^ ” Une abstraction de table de symboles pour implémenter des langages avec un contrôle de portée explicite “, LeBlank-Cook, 1983
- ^ Louis Steele, Guy (août 1982). “Un aperçu de Common LISP”. LFP ’82: Actes du symposium ACM 1982 sur LISP et programmation fonctionnelle : 98–107. doi : 10.1145/800068.802140 . ISBN 0897910826. S2CID 14517358 .
- ^ Joel, Moïse (juin 1970). “La fonction de FONCTION dans LISP”. MIT AI Mémo 199 . Laboratoire d’intelligence artificielle du MIT.
- ^ Steele, Guy Lewis Jr.; Sussman, Gerald Jay (mai 1978). “L’art de l’interprète; ou, le complexe de modularité (parties zéro, un et deux)”. Mémo MIT AI 453 . Laboratoire d’intelligence artificielle du MIT.
- ^ Frissons, Olin. “Histoire de T” . Paul Graham . Récupéré le 5 février 2020 .
- ^ Steele, Guy Lewis Jr. (mai 1978). “RABBIT : Un compilateur pour SCHEME”. MIT. hdl : 1721.1/6913 . {{cite journal}}: Cite journal requires |journal= (help)
- ↑ « Portée lexicale », Computer and Program Organization, Part 3 , p. 18 ans, chez Google Books , Université du Michigan. Conférences d’été sur l’ingénierie, 1967
- ^ « Portée lexicale », Rapport d’avancement du Projet MAC, Volume 8 , p. 80, chez Google Livres , 1970.
- ^ a b Scott 2009 , 3.4 Portée de mise en œuvre, p. 143.
- ^ ” Portée “, XL C/C++ V8.0 pour Linux, IBM
- ^ “Déclarer des variables de membre (Les didacticiels Java TM> Apprendre le langage Java> Classes et objets)” . docs.oracle.com . Récupéré le 19 mars 2018 .
- ^ “Contrôle de l’accès aux membres d’une classe (Les didacticiels Java TM> Apprendre le langage Java> Classes et objets)” . docs.oracle.com . Récupéré le 19 mars 2018 .
- ^ ” Tout ce que vous devez savoir sur la portée des variables Javascript “, Saurab Parakh , Coding is Cool , 2010-02-08
- ^ “ES5 annoté” . es5.github.io . Récupéré le 19 mars 2018 .
- ^ “Fonctions” . Documents Web MDN . Récupéré le 19 mars 2018 .
- ^ ” 12.2 Variable Statement “, Annotated ECMAScript 5.1, Dernière mise à jour : 2012-05-28
- ^ ” JavaScript Scope and Hoisting “, Ben Cherry , Adéquatement Bon , 08/02/2010
- ^ Fermetures Javascript , Richard Cornford. mars 2004
- ^ ” Explication de la portée et des fermetures de JavaScript “, Robert Nyman, 9 octobre 2008
- ^ Pitman, Kent (16 décembre 2007). “Le Manuel Maclisp Révisé (Le Pitmanual), Édition du Dimanche Matin” . MACLISP.info . Les déclarations d’HyperMeta Inc. et le compilateur, Concept “Variables” . Consulté le 20 octobre 2018 . Si la variable à lier a été déclarée spéciale, la liaison est compilée en tant que code pour imiter la façon dont l’interpréteur lie les variables
- ^ Pitman, Kent (16 décembre 2007). “Le Manuel Maclisp Révisé (Le Pitmanual), Édition du Dimanche Matin” . MACLISP.info . HyperMeta Inc. L’évaluateur, formulaire spécial . Consulté le 20 octobre 2018 . est destiné à aider à résoudre le « problème Funarg », mais il ne fonctionne que dans certains cas faciles. *FUNCTION*FUNCTION
- ^ Pitman, Kent; et coll. (version palmée de la norme ANSI X3.226-1994) (1996). “Common Lisp HyperSpec” . Lispworks.com . LispWorks Ltd. 1.1.2 Historique . Consulté le 20 octobre 2018 . MacLisp a amélioré la notion Lisp 1.5 de variables spéciales … Les principales influences sur Common Lisp étaient Lisp Machine Lisp, MacLisp, NIL, S-1 Lisp, Spice Lisp et Scheme.
- ^ “Langage de programmation ILISP, brouillon de travail ILISP 23.0” (PDF) . ILISISP.info . 11.1 Le principe lexical . Consulté le 20 octobre 2018 . Les liaisons dynamiques sont établies et accessibles par un mécanisme distinct (c’est-à-dire , et ). defdynamicdynamic-letdynamic
- ^ “Liaison lexicale” . EmacsWiki . Consulté le 20 octobre 2018 . Emacs 24 a une liaison lexicale facultative, qui peut être activée par tampon.
- ^ “FAQ R” . cran.r-project.org . Récupéré le 19 mars 2018 .
- Abelson, Harold ; Sussman, Gérald Jay ; Sussman, Julie (1996) [1984]. Structure et interprétation des programmes informatiques . Cambridge, Massachusetts : MIT Press . ISBN 0-262-51087-1.
- “Adressage lexical”
- Scott, Michael L. (2009) [2000]. Programmation pragmatique du langage (troisième éd.). Éditions Morgan Kaufmann. ISBN 978-0-12-374514-9.
- Chapitre 3 : Noms, étendues et liaisons, pp. 111–174
- Section 13.4.1 : Langages de script : fonctionnalités innovantes : noms et portées, pp. 691–699