Variables lexicales et spéciales

Il y a deux types de variables en Common Lisp : variables lexicales et variables spéciales (ou dynamiques) (en Anglais : lexical variables et special variables) . Les variables lexicales en Common Lisp sont similaires aux variables d'autres langages de programmation. Une telle variable peut être créée par un let. Les paramètres d'une fonction sont des variables lexicales. Pour des raisons d'efficacité, il n'est pas possible de manipuler la liaison entre un symbole représentant une variable lexicale et sa valeur. Les seules opérations permises sont la récupération et l'affectation de la valeur. Pour ces deux opérations, le nom de la variable est connu par le compilateur ce qui permet à ce dernier de générer du code efficace. La portée d'une variable lexicale est limitée à une partie du programme déterminée seulement par son texte. Exemple :

* (let ((x 10))
    (defun f (y)
      (+ x y)))

F
* (defun g (x)
    (f x))

G
* (g 3)

13
*

Ici, il y a deux variables différentes dont le nom est x : la première est créée par le let et référencée dans f et la deuxième est créée implicitement quand g est invoquée et référencée dans le corps de g.

Les variables spéciales, par contre, sont très différentes des variables lexicales. La portée d'une variable spéciale n'est pas limitée au texte du programme. Une telle variable est créée par defparameter (entre autres). Voici le même exemple avec une variable spéciale :

* (defparameter x 5)

X
* (let ((x 10))
    (defun f (y)
      (+ x y)))

F
* (defun g (x)
    (f x))

G
* (g 3)

6
* x

5
*

Ici, il y a une seule variable x. Elle est spéciale car elle est créée par defparameter. Lorsque f est définie, cette variable aura la valeur 10 comme dans l'exemple précédent. Mais l'appel de g avec l'argument 3 modifie la valeur de la variable x pour la durée de l'exécution de g, même pendant l'appel à f.

Les variables spéciales se comportent d'une façon tellement différente des variable lexicales que les programmeurs Common Lisp ont décidé d'avertir un lecteur éventuel du programme de la présence d'une variable spéciale : la convention est d'entourer le nom d'une variable spéciale par des astérisques comme pour *print-level* ou *read-base*.

L'utilisation de variables spéciales est une technique de programmation importante en Common Lisp. L'utilisation la plus fréquente est d'éviter le passage d'un argument à une fonction dont la valeur est presque toujours la même, mais cependant susceptible de changer. Un exemple classique de ce type d'utilisation est illustré par les variables *read-base* et *print-base*. Celles-ci déterminent la base à utiliser pour lire et afficher les nombres. Cette base est presque toujours 10, mais il peut arriver qu'on ait besoin de la changer. Sans variable spéciale, on a deux possibilités pour changer le comportement des fonctions de lecture et d'affichage. La première est d'utiliser une variable globale comme ceci :

* *print-base*

10
* (print 11)

11
11
* (setf *print-base* 8)

10
* (print 11)

13
13
* (setf *print-base* 10)

10
* (print 11)

11
11
*

Dans cette exemple, *print-base* est affectée à 8 pour la durée d'un calcul (ici le calcul trivial 11), puis à nouveau affectée à 10. C'est une méthode peu pratique, en particulier si le calcul risque de provoquer une erreur, auquel cas la deuxième affectation ne sera pas effectuée (à moins de compliquer le code).

Dans l'exemple ci-dessus, on remarque que l'évaluation de l'appel à print donne deux lignes d'affichage de la part du système : la première est l'effet de bord de print dont le résultat est d'afficher la valeur et la deuxième est la valeur de l'expression (print 11), en l'occurrence la même que l'argument donné à print.

La deuxième possibilité est d'écrire les fonctions de lecture et d'affichage avec un paramètre supplémentaire indiquant la valeur de la base ; ceci introduit beaucoup de code supplémentaire, souvent le même à chaque fois.

Avec les variables spéciales, il est possible de changer temporairement une valeur pendant la durée d'un calcul, sans crainte d'erreur :

* *print-base*

10
* (print 11)

11
11
* (let ((*print-base* 8)) (print 11))

13
11
* *print-base*

10
* (print 11)

11
11
*
Dans cette exemple, on remarque les lignes :

* (let ((*print-base* 8)) (print 11))

13
11

La valeur de la variable spéciale *print-base* est 8 pendant l'exécution de la fonction print. La première ligne d'affichage donne donc 13. La deuxième ligne d'affichage (11) représente la valeur de l'expression (let ((*print-base* 8)) (print 11)) entière. Cette valeur est 11 et lorsque cette valeur est affichée, la valeur de *print-base* est redevenue 10. Ici, même si le corps du let provoque une erreur, la valeur de *print-base* redeviendra 10.

Pour les variables spéciales, il est possible de manipuler explicitement la correspondance entre symboles et valeurs. La fonction symbol-value permet d'accéder à la valeur d'un symbole et de la changer avec setf :

* (symbol-value '*print-base*)

10
* (setf (symbol-value '*print-base*) 8)

10
* 11

13
* (defparameter *sym* '*print-base*)

*SYM*
* *sym*

*PRINT-BASE*
* (symbol-value *sym*)

10
* (setf (symbol-value *sym*) 10)

10
* 11

11
*

Irene DURAND
2011-10-14