Portée (programmation informatique)
( Apprenez comment et quand supprimer ce message ) En programmation , la portée d'une association de nom (l'association d'un nom à une entité, comme une variable ) correspond à ...
En programmation , la portée d'une association de nom (l'association d'un nom à une entité, comme une variable ) correspond à la partie du programme où cette association est valide. Autrement dit, la portée d'un nom est l'endroit où il peut être utilisé pour désigner une entité . Ailleurs dans le programme, le nom peut désigner une autre entité (il peut avoir une autre association), ou ne désigner rien du tout (il peut ne pas être associé). La portée permet d'éviter les conflits de noms en autorisant un même nom à désigner différents objets, à condition que ces noms aient des portées distinctes. La portée d'une association de nom est également appelée visibilité d'une entité, notamment dans la documentation plus ancienne ou plus technique ; cette notion se rapporte à l'entité référencée, et non au nom qui la référence.
Le terme « portée » est également utilisé pour désigner l'ensemble de toutes les liaisons de noms valides au sein d'une partie d'un programme ou à un point donné d'un programme, ce qui est plus correctement appelé contexte ou environnement .
parler et en pratique pour la plupart des langages de programmation , l' « partie d'un programme » désigne une portion de code source (une zone de texte) et est appelée portée lexicale . Dans certains langages, cependant, elle désigne une portion du temps d'exécution (une période durant laquelle le programme est exécuté ) et est appelée portée dynamique . Ces deux termes peuvent prêter à confusion – ils utilisent des termes techniques de manière inappropriée, comme expliqué dans la définition – mais la distinction elle-même est exacte et précise, et il s'agit des termes standard respectifs. Cet article se concentre principalement sur la portée lexicale, 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 à implémenter : à l’usage, il suffit de remonter dans le code source pour déterminer à quelle entité un nom se réfère ; à l’ implémentation, on peut maintenir une liste des noms et des contextes lors de la compilation ou de l’interprétation d’un programme. Des difficultés apparaissent avec le masquage de noms , les déclarations anticipées et le hoisting , tandis que des difficultés bien plus subtiles se posent avec les variables non locales , notamment dans les fermetures .
Définition
La définition stricte de la « portée » (lexicale) d'un nom ( identificateur ) est sans ambiguïté : la portée lexicale est « la portion du code source dans laquelle s'applique la liaison d'un nom à une entité ». Cette définition est pratiquement inchangée depuis 1960, date de la spécification d' ALGOL 60. Voici quelques spécifications de langages représentatifs :
- ALGOL 60 (1960)
- On distingue les types de quantités suivants : variables simples, tableaux, étiquettes, instructions conditionnelles et procédures. La portée d’une quantité correspond à l’ensemble des instructions et expressions dans lesquelles la déclaration de l’identificateur associé à cette quantité est valide.
- C (2007)
- Un identificateur 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 de type ; un nom d’étiquette ; un nom de macro ; ou un paramètre de macro. Un même identificateur peut désigner différentes entités à différents endroits du programme. [...] Pour chaque entité différente désignée par un identificateur, celui-ci n’est visible (c’est-à-dire utilisable) que dans une zone du texte du programme appelée sa portée.
- Go (2013)
- Une déclaration associe un identificateur non vide à une constante, un type, une variable, une fonction, une étiquette ou un paquetage. [...] La portée d'un identificateur déclaré correspond à l'étendue du texte source dans laquelle cet identificateur désigne la constante, le type, la variable, la fonction, l'étiquette ou le paquetage spécifié.
Le terme « portée » désigne le plus souvent le cas où un nom donné peut faire référence à une variable donnée — lorsqu'une déclaration a un effet — mais il peut également s'appliquer à d'autres entités, telles que des fonctions, des types, des classes, des étiquettes , des constantes et des énumérations.
Portée lexicale vs. portée dynamique
Une distinction fondamentale en matière de portée réside dans la définition de « partie d'un programme ». Dans les langages à portée lexicale (ou statique ), la résolution des noms dépend de leur emplacement dans le code source et du contexte lexical (ou statique ), 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 des noms dépend de l' état du programme au moment où le nom est rencontré, état déterminé par le contexte d'exécution ( ou contexte d' appel ). Concrètement, avec une portée lexicale , la résolution d'un nom s'effectue en recherchant dans le contexte lexical local, puis, en cas d'échec, dans le contexte lexical externe, et ainsi de suite. Avec une portée dynamique, la résolution s'effectue en recherchant dans le contexte d'exécution local, puis, en cas d'échec, dans le contexte d'exécution externe, et ainsi de suite, en remontant la pile d'appels.
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 . Perl 5 offre à la fois la portée lexicale et la portée dynamique. Les fonctions qui utilisent des variables à portée lexicale sont appelées fermetures .
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 .
Concepts connexes
En programmation orientée objet , la répartition dynamique sélectionne une méthode d'objet à l'exécution, mais le moment de la liaison des noms (compilation ou exécution) dépend du langage. La portée dynamique de facto est courante dans les langages de macros , qui n'effectuent pas directement la résolution des noms, mais procèdent à une expansion sur place.
Certains frameworks de programmation, comme AngularJS, utilisent le terme « scope » dans un sens syntaxique différent de celui employé dans cet article. Dans ces frameworks, le scope est simplement un objet du langage de programmation utilisé ( JavaScript dans le cas d'AngularJS) qui sert à émuler la portée dynamique dans un langage utilisant la portée lexicale pour ses variables. Ces scopes AngularJS peuvent être contextuels ou non (au sens habituel du terme) dans n'importe quelle partie du programme, suivant les règles de portée des variables du langage comme tout autre objet, et utilisant leurs propres règles d'héritage et de transclusion . 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 généralement déconseillée par les guides de style.
Utiliser
La portée est un élément important de la résolution de noms , elle-même fondamentale pour la sémantique du langage . La résolution de noms (y compris la portée) varie d'un langage de programmation à l'autre et, au sein d'un même langage, selon le type d'entité ; les règles de portée sont appelées règles de portée . Avec les espaces de noms , les règles de portée sont essentielles à la programmation modulaire , car une modification dans une partie du programme n'affecte pas les autres parties.
Aperçu
({intx=f();x*x;})En Python, les variables auxiliaires dans les expressions génératrices et les compréhensions de listes (en Python 3) ont une portée d'expression.
En C, les noms de variables dans le prototype d'une fonction ont une portée d'expression, appelée ici portée de protocole de fonction . Comme ces noms de variables ne sont pas utilisés dans la définition (ils peuvent différer de la définition proprement dite) — ce sont simplement des variables fictives —, ils sont souvent omis, même s'ils peuvent servir à 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 par blocs, mais pas dans tous. Elle a été introduite avec ALGOL 60 , où « chaque déclaration… n'est valable que pour ce bloc » , et est aujourd'hui particulièrement associée aux langages des familles Pascal et C. Le plus souvent, ce bloc est contenu dans une fonction, limitant ainsi sa portée à une partie de celle-ci. Cependant, dans certains cas, comme en Perl, le bloc peut ne pas se trouver à l'intérieur d'une fonction.
Un exemple représentatif de l'utilisation de la portée de bloc est le code C présenté ici, où deux variables sont limitées à la boucle : la variable de boucle `n` , initialisée une seule fois et incrémentée à chaque itération, et la variable auxiliaire ` n_squared` , initialisée à chaque itération. L'objectif est d'éviter d'ajouter à la portée de la fonction des variables qui ne sont pertinentes que pour un bloc particulier ; par exemple, cela évite les erreurs dues à une affectation accidentelle de la variable de boucle générique `i`n * n . Dans cet exemple, l'expression ne serait généralement pas affectée à une variable auxiliaire, et le corps de la boucle serait simplement écrit. result += n * nCependant, dans des exemples plus complexes, les variables auxiliaires s'avèrent utiles.
Les blocs servent principalement à contrôler le flux d'exécution, notamment avec les boucles `if`, `while` et `for`. Dans ces cas, la portée d'une variable dépend de la structure du flux d'exécution de la fonction. Cependant, les langages à portée de bloc autorisent généralement aussi 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 de portée fonctionnelle) et libérée à la fin du bloc. De même, une boucle `while` peut être placée dans un bloc qui initialise les variables utilisées à l'intérieur de la boucle et qui ne doivent être initialisées qu'une seule fois.
Une subtilité de plusieurs langages de programmation, tels qu'Algol 68 et C (illustrée dans cet exemple et normalisée depuis C99 ), réside dans la possibilité de déclarer des variables de portée bloc non seulement au sein du corps du bloc, mais aussi dans l'instruction de contrôle, le cas échéant. Ceci est analogue aux paramètres de fonction, déclarés lors de la déclaration de la fonction (avant le début du corps de la fonction) et dont la portée s'étend sur toute la durée de ce corps. Cette pratique est principalement utilisée dans les boucles `for` , qui possèdent une instruction d'initialisation distincte de la condition de boucle, contrairement aux boucles `while`, et constitue une construction courante.
La portée de bloc permet de masquer des variables. Dans cet exemple, à l'intérieur du bloc, la variable auxiliaire aurait également pu être nommée `n` , masquant ainsi le nom du paramètre. Cependant, cette pratique est déconseillée en raison des risques d'erreurs. De plus, certains langages dérivés du C, comme Java et C#, bien que prenant en charge la portée de bloc (une variable locale pouvant être déclarée hors de son contexte avant la fin d'une fonction), n'autorisent pas le masquage d'une variable locale par une autre. Dans ces langages, la déclaration d'une seconde variable `n` entraînerait une erreur de syntaxe, et l'une des variables `n` devrait être renommée.
Si un bloc est utilisé pour définir la valeur d'une variable, la portée du bloc exige que la variable soit déclarée en dehors du bloc. Cela complique l'utilisation des instructions conditionnelles avec une seule affectation . Par exemple, en Python, qui n'utilise pas la portée de bloc, on peut initialiser une variable comme ceci :
où aest accessible après l' ifinstruction.
En Perl, qui utilise la portée de bloc, cela nécessite en revanche de déclarer la variable avant le bloc :
On réécrit souvent cette expression en utilisant plusieurs affectations, en initialisant la variable à une valeur par défaut. En Python (où cela n'est pas nécessaire), cela donnerait :
En Perl, cela donnerait :
En cas d'affectation d'une seule variable, une alternative consiste à utiliser l' opérateur ternaire pour éviter un bloc, mais cela n'est généralement pas possible pour les affectations de plusieurs variables et est difficile à lire pour une logique complexe.
C’est un problème plus important en C, notamment pour l’affectation de chaînes de caractères, 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 la chaîne et la vérification du succès de ces opérations.
Certains langages permettent d'appliquer, à des degrés divers, la notion de portée de bloc en dehors d'une fonction. Par exemple, dans l'extrait de code Perl ci-contre, $counter`variable` a une portée de bloc (grâce au mymot-clé `const`), tandis increment_counterque `function` a une portée globale. Chaque appel à ` increment_counterfunction` incrémente la valeur de `variable` $counterde un et retourne sa nouvelle valeur. Le code situé en dehors de ce bloc peut appeler `function` increment_counter, mais ne peut ni obtenir ni modifier la valeur de `variable` $counter. Cette construction permet de définir des fermetures en Perl.
Portée fonctionnelle
Lorsque la portée des variables déclarées à l'intérieur d'une fonction ne s'étend pas au-delà de cette fonction, on parle de portée de fonction . La portée de fonction est disponible dans la plupart des langages de programmation qui offrent la possibilité de créer une variable locale dans une fonction ou une sous-routine : une variable dont la portée se termine (qui sort du contexte) lorsque la fonction se termine. Dans la plupart des cas, la durée de vie de la variable correspond à celle de l'appel de fonction ; il s'agit d'une variable automatique , créée au démarrage de la fonction (ou lors de sa déclaration) et détruite à la fin de son exécution. La portée de la variable est limitée à la fonction, bien que la signification de « à l'intérieur » dépende du type de portée (lexicale ou dynamique). Cependant, certains langages, comme le C, proposent également des variables locales statiques , dont la durée de vie correspond à celle du programme, mais qui ne sont accessibles au contexte que lorsqu'elles se trouvent à l'intérieur de la fonction. Dans le cas des variables locales statiques, la variable est créée à l'initialisation du programme et détruite à sa fin, comme une variable globale statique , mais son contexte est limité à l'intérieur de la fonction, à l'instar d'une variable locale automatique.
Il est important de noter que, dans la portée lexicale, une variable de portée fonctionnelle 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 à l'intérieur de celle-ci, et y revient lorsque la fonction se termine. Les fonctions appelées n'ont pas accès aux variables locales des fonctions appelantes, et les variables locales n'ont de contexte que dans le corps de la fonction où 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 leur contexte lorsqu'une autre fonction est appelée, et n'en sortent que lorsque la fonction les définissant se termine. Ainsi, les variables locales ont de contexte à la fois dans la fonction qui les définit et dans toutes les fonctions appelées . Dans les langages à portée lexicale et à fonctions imbriquées , les variables locales ont de contexte pour les fonctions imbriquées, car elles appartiennent au même contexte lexical, mais pas pour les autres fonctions non imbriquées lexicalement. Une variable locale d'une fonction englobante est dite non locale pour cette fonction imbriquée. La portée fonctionnelle s'applique également aux fonctions anonymes .
Par exemple, dans l'extrait de code Python à droite, deux fonctions sont définies : ` sq` squareet sum_of_squares`sum`. `sq` squarecalcule le carré d'un nombre ; ` sum_of_squaressum` calcule la somme de tous les carrés inférieurs ou égaux à un nombre donné. (Par exemple, ` 4²`square(4) vaut ` 4²` , et ` 0² + 1² + 2² + 3² + 4²` vaut `0² + 1² + 2² + 3² + 4²` . )16sum_of_squares(4)30
Chacune de ces fonctions possède une variable nommée `n` qui représente son argument. Ces deux variables `n` sont totalement distinctes et indépendantes, malgré leur nom identique, car ce sont des variables locales à portée lexicale limitée à la fonction : la portée de chacune est une fonction distincte, et elles ne se chevauchent donc pas. Par conséquent, sum_of_squareson peut appeler ` squaren` sans que sa propre variable ` n` soit modifiée. De même, ` sum_of_squaresn` possède des variables nommées `total` et `i` ; ces variables, du fait de leur portée limitée, n'interféreront pas avec d'autres variables nommées `total` ou `i` appartenant à d'autres fonctions. Autrement dit, il n'y a aucun risque de conflit de noms entre ces variables et d'autres variables non liées, même si elles sont identiques.
Aucun masquage de nom n'a lieu : une seule variable nommée n est accessible à un instant donné, car les portées ne se chevauchent pas. En revanche, si un fragment similaire était écrit dans un langage à portée dynamique, la variable n de la fonction appelante resterait accessible dans la fonction appelée (les portées se chevaucheraient) et serait masquée (« ombragée ») par la nouvelle variable n de la fonction appelée.
La portée des fonctions est nettement plus complexe si elles sont des objets de première classe, pouvant être créées localement par une fonction puis renvoyées. Dans ce cas, toute variable de la fonction imbriquée qui n'est pas locale à celle-ci (variables non liées dans la définition de la fonction, qui sont résolues en variables dans un contexte englobant) crée une fermeture , car non seulement la fonction elle-même, mais aussi son contexte (de variables) doivent être renvoyés, puis potentiellement appelés dans un contexte différent. Ceci exige une prise en charge bien plus importante de la part du compilateur et peut complexifier l'analyse du programme .
Portée du fichier
La portée d'une liaison de nom est limitée au fichier, ce qu'on appelle la portée de fichier . La portée de fichier est spécifique au C (et au C++), où la portée des variables et des fonctions déclarées à la racine d'un fichier (hors des fonctions elles-mêmes) s'étend à l'ensemble du fichier, ou plus précisément, en C, de la déclaration jusqu'à la fin du fichier source, ou plus précisément jusqu'à l'unité de traduction (liaison interne). On peut y voir une forme de portée de module, où les modules sont identifiés par des fichiers, et qui, dans les langages plus modernes, est remplacée par une notion explicite de portée de module. En raison de la présence d'instructions `include`, qui ajoutent des variables et des fonctions au contexte interne et peuvent elles-mêmes appeler d'autres instructions `include`, il peut être difficile de déterminer ce qui est inclus dans le contexte au sein du corps d'un fichier.
Dans l'extrait de code C ci-dessus, le nom de la fonction sum_of_squaresa une portée globale (en C, liaison externe). Ajouter un élément staticà la signature de la fonction lui conférerait une portée de fichier (liaison interne).
Portée du module
La portée d'une liaison de nom est un module, appelée portée de module . La portée de module est disponible dans les langages de programmation modulaires où les modules (pouvant s'étendre sur plusieurs fichiers) constituent l'unité de base d'un programme complexe, car ils permettent de masquer des informations et d'exposer une interface limitée. La portée de module a été introduite pour la première fois dans la famille de langages Modula , et Python (influencé par Modula) en est un exemple contemporain représentatif.
Dans certains langages de programmation orientés objet ne prenant pas directement en charge les modules, comme le C++ avant C++20 , une structure similaire est fournie par la hiérarchie des classes. Les classes constituent l'unité de base du programme et peuvent contenir des méthodes privées. Ce principe s'applique davantage à la répartition dynamique des appels qu'à la résolution de noms et à la portée des méthodes, bien que ces deux aspects soient souvent analogues. Dans certains cas, ces deux mécanismes sont disponibles, comme en Python, qui propose à la fois des modules et des classes. L'organisation du code (sous forme de fonction de module ou de méthode privée conventionnelle) est alors laissée au choix du programmeur.
Portée mondiale
La portée d'une liaison de nom s'étend à l'ensemble du programme ; on parle alors de portée globale . L'utilisation de noms de variables à portée globale (appelées variables globales ) est souvent déconseillée, du moins dans certains langages, en raison des risques de conflits de noms et de masquage involontaire, ainsi que d'une faible modularité. La portée de fonction ou de bloc est généralement préférable. Cependant, la portée globale est couramment utilisée (selon le langage) pour d'autres types de noms, tels que les noms de fonctions, de classes et d'autres types de données . Dans ces cas, des mécanismes comme les espaces de noms permettent d'éviter les conflits.
Portée lexicale vs. portée dynamique$ # Langage bash $ x = 1 $ function g () { echo $x ; x = 2 ; } $ function f () { local x = 3 ; g ; } $ f # Est-ce que ceci affiche 1 ou 3 ? 3 $ echo $x # Est-ce que ceci affiche 1 ou 2 ? 1
Prenons l'exemple du programme de droite. La première ligne, `__init__`, crée une variable globale et l'initialise à `0` . La deuxième ligne, `__init__` , définit une fonction qui affiche la valeur actuelle de `0` , puis la modifie (en écrasant la valeur précédente). La troisième ligne, `__init__`, définit une fonction qui crée une variable locale (masquant la variable globale du même nom) et l'initialise à `0`, puis appelle `__init__` . La quatrième ligne, `__init__`, appelle `__init__` . La cinquième ligne, `__init__`, affiche la valeur actuelle de `0` .x=1x1functiong(){echo$x;x=2;}gxx2functionf(){localx=3;g;}fx3gfecho$xx
Les règles de portée déterminent ce que ce programme affiche. Si le langage utilisé utilise la portée lexicale, alors g`f` affiche et modifie la variable globale `f` x(car `f` gest définie en dehors de f`f`), donc le programme affiche ` 1f` puis `f` 2. En revanche, si ce langage utilise la portée dynamique, alors g`f` affiche et modifie fla variable locale `f` x(car ` gf` est appelée depuis `f` f), donc le programme affiche `f` 3puis `f` 1. (Dans le cas présent, le programme est écrit en Bash , qui utilise la portée dynamique ; il affiche donc `f` 3puis `f` 1. Si le même code était exécuté avec ksh93 , qui utilise la portée lexicale, les résultats seraient différents.)
étendue lexicale
Avec la portée lexicale , un nom fait toujours référence à son contexte lexical. Cette propriété, inhérente au texte du programme, est rendue indépendante de la pile d'appels d'exécution par l'implémentation du langage. Puisque cette correspondance ne nécessite que l'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 comme ML et Haskell . Elle est également utilisée en C et dans ses langages apparentés sur les plans syntaxique et sémantique, bien qu'avec des limitations différentes. La portée statique permet au programmeur de raisonner sur les références d'objets (paramètres, variables, constantes, types, fonctions, etc.) comme de simples substitutions de noms. Ceci facilite grandement la conception de code modulaire et son analyse, car la structure de nommage locale peut être comprise isolément. À l'inverse, la portée dynamique oblige le programmeur à anticiper tous les contextes d'exécution possibles dans lesquels le code du module peut être invoqué.
Par exemple, Pascal utilise la portée lexicale. Prenons l'exemple du fragment de programme Pascal ci-contre. La variable Iest visible partout, car elle n'est jamais masquée par une autre variable du même nom. La charvariable Kest visible uniquement dans le programme principal, car elle est masquée par la realvariable Kvisible uniquement dans les procédures Aet B. La variable Lest également visible uniquement dans les procédures Aet , Bmais elle ne masque aucune autre variable. La variable Mest visible uniquement dans la procédure Bet n'est donc accessible ni depuis cette procédure Ani depuis le programme principal. De même, la procédure Bn'est visible que dans la procédure Aet ne peut donc pas être appelée depuis le programme principal. Il aurait pu exister une autre procédure nommée Bdéclarée dans le programme en dehors de la procédure B. L'endroit dans le programme où «B » est mentionné détermine alors laquelle des deux procédures nommées Belle représente, de manière analogue à la portée des variables.
L'implémentation correcte de la portée lexicale dans les langages possédant des fonctions imbriquées de première classe n'est pas triviale, car elle exige que chaque valeur de fonction contienne une trace des valeurs des variables dont elle dépend (la paire fonction-contexte est appelée fermeture ). Selon l'implémentation et l'architecture de l'ordinateur , la recherche de variables peut devenir légèrement inefficace lors de l'utilisation de fonctions très profondément imbriquées lexicalement , bien qu'il existe des techniques connues pour atténuer ce problème. De plus, pour les fonctions imbriquées qui ne font référence qu'à leurs propres arguments et aux variables (immédiatement) locales, toutes les positions relatives sont connues à la compilation . L'utilisation de ce type de fonction imbriquée n'entraîne donc aucune surcharge. Il en va de même pour les 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 en 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.
Les langages comme Pascal et C ont toujours eu une portée lexicale, puisqu'ils sont tous deux influencés par les idées qui ont conduit à ALGOL 60 et ALGOL 68 (bien que C n'ait pas inclus de fonctions imbriquées lexicalement ).
Perl est un langage à portée dynamique auquel la portée statique a été ajoutée ultérieurement.
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 la direction de John McCarthy ).
Tous les premiers Lisp utilisaient la portée dynamique lorsqu'ils étaient basés sur des interpréteurs. En 1982, Guy L. Steele Jr. et le Common Lisp Group ont publié *An overview of Common Lisp * , un bref aperçu de l'histoire et des différentes implémentations de Lisp jusqu'alors, ainsi qu'une description des caractéristiques que devrait posséder une implémentation de Common Lisp . À la page 102, on peut lire :
La plupart des implémentations LISP présentent une incohérence interne : par défaut, l’interpréteur et le compilateur peuvent attribuer des sémantiques différentes à des programmes corrects. Cela provient principalement du fait que l’interpréteur considère toutes les variables comme ayant une portée dynamique, tandis que le compilateur les considère comme locales, sauf indication contraire. Ce choix, motivé par des raisons de commodité et d’efficacité, peut engendrer des bogues très subtils. La définition de Common LISP évite ces anomalies en exigeant explicitement que l’interpréteur et le compilateur imposent une sémantique identique aux programmes corrects.
Les implémentations de Common Lisp devaient donc avoir une portée lexicale . (Voir aussi : « Présentation 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 problème dit « FUNARG » est entièrement résolu, aussi bien dans le cas descendant que dans le cas ascendant.
La même année que la publication de *An overview of Common LISP* (1982), les premières ébauches (également de Guy L. Steele Jr.) d'un Lisp compilé à portée lexicale, appelé Scheme , avaient été publiées et des implémentations de compilateurs étaient en cours d'élaboration. À cette époque, on craignait généralement que la portée lexicale en Lisp soit difficile à implémenter. Dans *A History of T* , Olin Shivers écrit :
Tous les Lisp sérieusement utilisés en production à cette époque étaient à portée dynamique. Personne, hormis ceux qui avaient lu attentivement la thèse Rabbit (écrite par Guy Lewis Steele Jr. en 1978), ne croyait au succès de la portée lexicale ; même les rares personnes qui l’ avaient lue faisaient preuve d’une certaine audace quant à son fonctionnement en production.
Le terme « portée lexicale » remonte au moins à 1967, tandis que le terme « portée lexicale » remonte au moins à 1970, où il a été utilisé dans le cadre du projet MAC pour décrire les règles de portée du dialecte Lisp MDL (alors connu sous le nom de « Muddle »).
Portée lexicale en programmation moderne
Dans les langages de programmation modernes, la portée lexicale joue un rôle crucial dans la mise en œuvre des paradigmes de programmation fonctionnelle. Des langages comme JavaScript , Python et Swift s'appuient fortement sur la portée lexicale pour garantir que les fonctions puissent accéder aux variables de leur contexte de définition, même si elles sont exécutées en dehors de cette portée. Ceci est particulièrement important pour les fermetures, qui découlent directement de la portée lexicale.
Par exemple, en JavaScript, les fermetures sont fréquemment utilisées pour la programmation asynchrone et la gestion d'événements, car elles permettent aux fonctions de rappel de conserver l'accès aux variables de la portée extérieure, ce qui facilite un code plus propre et plus modulaire. De même, Python et Swift utilisent la portée lexicale pour implémenter des fermetures et permettre des modèles puissants comme les fonctions d'ordre supérieur.
Portée dynamique
Avec la portée dynamique , un nom fait référence à un contexte d'exécution. Techniquement, cela signifie que chaque nom possède une pile globale de liaisons. L'introduction d'une variable locale nommée xajoute une liaison à la pile globale x(qui peut être vide au départ), laquelle est dépilée lorsque le flux d'exécution quitte la portée. L'évaluation xdans n'importe quel contexte renvoie toujours la liaison la plus récente. Notez que cela ne peut pas être fait à la compilation, car la pile de liaisons n'existe qu'à l'exécution ; c'est pourquoi ce type de portée est appelé portée dynamique .
La portée dynamique est rare dans les langages modernes.
Généralement, certains blocs sont définis pour créer des liaisons dont la durée de vie correspond à la durée d'exécution du bloc ; cela ajoute certaines caractéristiques de la portée statique au processus de portée dynamique. Cependant, comme une section de code peut être appelée depuis de nombreux endroits et dans diverses situations, il peut être difficile de déterminer d'emblée quelles liaisons s'appliqueront lorsqu'une variable est utilisée (voire même si une liaison existe). Cela peut s'avérer avantageux ; l'application du principe de moindre connaissance suggère que le code évite de dépendre des raisons (ou des circonstances) de la valeur d'une variable, mais utilise simplement la valeur conformément à sa définition. Cette interprétation restrictive des données partagées peut fournir un système très flexible pour adapter le comportement d'une fonction à l'état (ou à la politique) actuel du système. Toutefois, cet avantage repose sur une documentation rigoureuse de toutes les variables utilisées de cette manière et sur une évitement strict des suppositions concernant le comportement d'une variable, et ne fournit aucun mécanisme pour détecter les interférences entre différentes parties d'un programme. Certains langages, comme Perl et Common Lisp , permettent au programmeur de choisir entre une portée statique ou dynamique lors de la définition ou de la redéfinition d'une variable. Parmi les langages utilisant une portée dynamique, on peut citer Logo , Emacs Lisp , LaTeX et les langages shell bash , dash et PowerShell .
La portée dynamique est relativement simple à implémenter. 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 y trouver une valeur correspondant à ce nom. En pratique, cette opération est optimisée grâce à l'utilisation d'une liste d'associations , qui est une pile de paires nom/valeur. Les paires sont empilées lors de chaque déclaration et dépilées lorsque les variables sortent de leur contexte. La liaison superficielle est une stratégie alternative considérablement plus rapide, utilisant une table de références centrale qui associe chaque nom à sa propre pile de significations. Ceci évite une recherche linéaire lors de l'exécution pour trouver un nom particulier, mais il convient de veiller à la bonne maintenance de cette table. Notez que ces deux stratégies supposent un ordre d'entrée/sortie de type dernier entré, premier sorti ( LIFO ) pour les liaisons de toute variable ; en pratique, toutes les liaisons sont ordonnées de cette manière.
Une implémentation encore plus simple consiste à représenter les variables dynamiques par de simples variables globales. La liaison locale est réalisée en sauvegardant la valeur d'origine dans un emplacement anonyme de la pile, invisible pour le programme. Lorsque la portée de cette liaison se termine, la valeur d'origine est restaurée à partir de cet emplacement. De fait, la portée dynamique a vu le jour de cette manière. Les premières implémentations de Lisp utilisaient cette stratégie évidente pour implémenter les variables locales, et cette pratique perdure dans certains dialectes encore utilisés, tels que GNU Emacs Lisp. La portée lexicale a été introduite ultérieurement dans Lisp. Elle est équivalente au schéma de liaison superficielle décrit précédemment, à ceci près que la table de référence centrale est simplement le contexte de liaison de la variable globale, dans lequel la signification actuelle de la variable correspond à sa valeur globale. La gestion des variables globales est simple. Par exemple, un objet symbole peut disposer d'un emplacement dédié pour sa valeur globale.
La portée dynamique offre une excellente abstraction pour le stockage local au thread , mais son utilisation dans ce contexte ne peut reposer sur la sauvegarde et la restauration d'une variable globale. Une stratégie d'implémentation possible consiste à attribuer à chaque variable une clé locale au thread. Lors de l'accès à la variable, cette clé est utilisée pour accéder à son emplacement mémoire local (grâce au code généré par le compilateur, qui distingue les variables dynamiques des variables lexicales). Si aucune clé locale n'existe pour le thread appelant, l'emplacement global est utilisé. Lorsqu'une variable est liée localement, sa valeur précédente est stockée dans une zone cachée de la pile. Un espace de stockage local est créé sous la clé de la variable, et la nouvelle valeur y est stockée. Les redéfinitions imbriquées de la variable au sein de ce thread sauvegardent et restaurent simplement cet emplacement local. Lorsque le contexte de la redéfinition initiale, la plus externe, se termine, la clé locale est supprimée, exposant à nouveau la version globale de la variable à ce thread.
Avec la transparence référentielle, la portée dynamique est limitée à la pile d'arguments de la fonction courante et coïncide avec la portée lexicale.
Expansion macroéconomique
La macro sera développée pour ajouter une valeur aà la variable passée en paramètre. Le nom de cette macro ne sera résolu que plus tard par le compilateur, en fonction de l'endroit où elle ADD_Aest « appelée » (plus précisément, développée). En pratique, le préprocesseur C effectue uniquement une analyse lexicale , développant la macro lors de la tokenisation, mais sans l'analyser syntaxiquement ni résoudre son nom.
Par exemple, dans le code suivant, le nom ade la macro est résolu (après expansion) en la variable locale au niveau du site d'expansion :
Noms qualifiés
Comme nous l'avons vu, l'une des principales raisons d'être de la portée est d'éviter les conflits de noms, en permettant à des noms identiques de désigner des éléments distincts, à condition que ces noms aient des portées différentes. Cette restriction peut parfois s'avérer contraignante ; lorsque de nombreux éléments différents doivent être accessibles dans un programme, ils nécessitent généralement tous des noms de portée globale, ce qui requiert des techniques différentes pour éviter les conflits de noms.
Pour remédier à cela, de nombreux langages proposent des mécanismes d'organisation des noms globaux. Les détails de ces mécanismes, ainsi que la terminologie employée, varient selon le langage ; l'idée générale est qu'un groupe de noms peut lui-même se voir attribuer un nom (un préfixe) et, si nécessaire, une entité peut être désignée par un nom qualifié composé du nom et du préfixe. Ces noms possèdent généralement deux portées : une portée (généralement la portée globale) où le nom qualifié est visible, et une ou plusieurs portées plus restreintes où le nom non qualifié (sans préfixe) est également visible. Ces groupes peuvent eux-mêmes être organisés en groupes, c'est-à-dire qu'ils peuvent être imbriqués .
Bien que de nombreux langages prennent en charge ce concept, les détails varient considérablement. Certains langages disposent de mécanismes, tels que les espaces de noms en C++ et C# , qui servent presque exclusivement à organiser les noms globaux en groupes. D'autres langages proposent des mécanismes, tels que les packages en Ada et les structures en Standard ML , qui combinent cette fonctionnalité avec la possibilité supplémentaire de rendre certains noms visibles uniquement par les membres de leur groupe. Les langages orientés objet permettent souvent aux classes ou aux objets singleton de remplir cette fonction (qu'ils disposent ou non d' un mécanisme dédié). De plus, les langages combinent fréquemment ces approches ; par exemple, les packages de Perl sont très similaires aux espaces de noms de C++, mais peuvent également servir de classes pour la programmation orientée objet ; et Java organise ses variables et fonctions en classes, puis ces classes en packages, à l'instar d'Ada.
Par langue
Les règles de portée pour les langues représentatives suivent.