Cours 3

Programming, Math, etc. · course

Voir tous les documents en programmation

Cours 3

Contenu

  • Constantes
  • Documentation des symboles
  • Symboles
  • L'opérateur spécial QUOTE
  • Instructions conditionnelles: if, cond, case
  • Variables locales: let ,let\*

Les transparents: 33--42

Symboles

Un symbole Common Lisp correspond à un identificateur dans d'autres langages. La principale différence entre la représentation externe des identificateurs et celle des symboles Common Lisp est que Common Lisp autorise l'utilisation de davantage de caractères. En première approximation, on peut dire que n'importe quelle suite de lettres, chiffres, caractères +, -, \*, /, ![$<$](data:image/png;base64...), ![$>$](data:image/png;base64...), =, ^ qui n'est pas interprétable comme un nombre est traduite par read en un symbole. De plus, d'autres caractères comme (, ), espace, etc peuvent y figurer, à condition d'être précédés par anti-slash. De plus, il est possible de construire un symbole avec n'importe quelle suite de caractères, à condition de l'entourer de caractères ![$\Vert$](data:image/png;base64...).

Pour la fonction eval, un symbole est une variable (comme dans d'autres langages) et son évaluation consiste à rechercher sa valeur courante. Common Lisp contient un grand nombre de variables prédéfinies, dont la valeur détermine le fonctionnement du système, comme par exemple la variable \read-base\ illustrée plus loin. Par convention, les variables globales Common Lisp sont entourées d'astérisques, ce n'est toutefois pas une obligation.

Pour définir une variable, on utilise soit la macro defparameter, soit la macro defvar. La différence entre les deux est que defparameter réinitialise la variable si elle existe déjà, alors que defvar l'initialise uniquement si elle n'est pas déjà initialisée. Nous allons utiliser plutôt defparameter pour être sûr que la valeur soit toujours affectée. Exemples :

\ \read-base\*

10

\* (defparameter a>b "peut-etre")

A>B

\* a>b

"peut-etre"

\* (defparameter 2^4 16)

|2^4|

\* 2^4

16

\*

\* (defparameter ![$\backslash$](data:image/png;base64...)(![$\backslash$](data:image/png;base64...) bonjour 4)

|( BONJOUR|

\* |( BONJOUR|

4

\*

Remarques : La valeur de l'expression (defparameter sym val) est le symbole sym. Les lettres minuscules sont automatiquement traduites en majuscules par read (sauf si elles sont précédées pas anti-slash ou entourées de ![$\Vert$](data:image/png;base64...)). Dans cette implémentation, un symbole contenant ^ est toujours affiché (par print) entouré de ![$\Vert$](data:image/png;base64...).

Deux symboles nil et t sont prédéfinis en Common Lisp. Ils sont spéciaux, car chacun est sa propre valeur, c'est-à-dire que la valeur de la variable nil est le symbole nil et la valeur de la variable t est le symbole t. En fait, nil représente aussi la valeur booléenne faux et t, la valeur booléenne vrai.

Les symboles Common Lisp jouent plusieurs rôles, en plus de celui de variable. En particulier, ils peuvent être le nom d'une fonction, d'une macro ou d'un opérateur spécial. De plus, ils peuvent être utilisés comme des objets normaux, comme les entiers, les chaînes de caractères, ou les valeurs booléennes.

Effets de bord

Nous avons parlé d'expressions, d'évaluation d'expressions et de valeursd'expressions. Il est vrai que (pratiquement) toutes les expressions Common Lisp ont une valeur. Mais certaines expressions peuvent aussi avoir des effets de bord, à savoir qu'elles modifient l'environnement global d'une manière plus ou moins permanente. Certaines expressions sont même évaluées pour leurs effets de bord et non pour leur valeur.

L'expression avec effet de bord la plus souvent utilisée est sans doute l'affectation. Dans ce livre, nous allons utiliser la macro setf exclusivement pour l'affectation. Cette macro prend deux paramètres, le premier étant uneplace (en particulier une variable) et le deuxième une expression arbitraire. Le premier argument n'est pas évalué (setf ne peut donc pas être une fonction) alors que le deuxième l'est. Exemples :

\ \print-level\*

NIL

\ (setf \print-level\* 10)

10

\ \print-level\*

10

\*

Remarques : \print-level\* est une variable qui détermine le nombre maximum de niveaux de listes affichés par print. Si sa valeur est nil*, il n'y a pas de limitation. La valeur de l'expression d'affectation est la nouvelle valeur de la variable. Cette valeur de retour est rarement utilisée.

Un autre effet de bord dont nous avons souvent besoin est la définition de fonctions. Habituellement, on utilise la macro defun pour ce type de définitions. Le premier argument de la macro est le nom de la fonction à définir, le deuxième une liste de paramètres (souvent des symboles). Les arguments suivants forment une séquence arbitrairement longue (le corps de la fonction) d'expressions à évaluer lors d'un appel à la fonction. Exemples :

\* (defun pythagora (x y)

(sqrt (+ (\ x x) (\ y y))))

PYTHAGORA

\* (pythagora 3 4)

5.0

Publicité

\*

Ici, le nom de la fonction est pythagora, les arguments sont x et y et le corps contient une seule expression, (sqrt (+ (\ x x) (\ y y))).

On remarque l'indentation de l'expression du corps par rapport à la première ligne (2 caractères). Cette indentation est obligatoire et automatiquement suggérée par Emacs.

La valeur de l'expression de définition d'une fonction est le nom de la fonction (un symbole).

Suppression de l'évaluation

L'un des opérateurs spéciaux (voir section [4.3.2](file:///E%3A%5CIrene_Book%5Cnode53.html#secoperateurspeciaux)) les plus importants est quotequi permet de supprimer l'évaluation. Cet opérateur prend un seul paramètre et le résultat de l'évaluation de l'expression composée est l'argument passé :

\ \read-base\*

10

\ (quote \read-base\*)

\READ-BASE\

\ (quote (setf \read-base\* 8))

(SETF \READ-BASE\ 8)

\ \read-base\*

10

\ (defparameter \expr\ (quote (setf \read-base\* 8)))

\EXPR\

\ \expr\*

(SETF \READ-BASE\ 8)

\ (eval \expr\*)

8

\ \read-base\*

8

\*

On remarque ici l'utilisation explicite de la fonction eval. Il est très rare d'en avoir besoin dans un programme. De fait, il est très probable que l'utilisation d'eval indique une erreur grave de conception. Pour la grande majorité des cas où le programmeur serait tenté d'utiliser eval, l'utilisation d'une macrodevrait être plus appropriée.

La fonction read convertit automatiquement une expression ![$E$](data:image/png;base64...) précédée du caractère apostrophe (en Anglais : quote) en une expression composée (quote ![$E$](data:image/png;base64...)) :

\* (quote (this is a test))

(THIS IS A TEST)

\* '(this is a test)

(THIS IS A TEST)

\ '\read-base\*

\READ-BASE\

\*

Conditionnelles

Conformément à la tradition, Common Lisp propose plusieurs types de conditionnelles, à choisir en fonction de la situation.

Sous-sections

  • [if](file:///E%3A%5C%5CFeriel%5C%5CIrene_Book%5C%5Cnode136.html)
  • [when](file:///E%3A%5C%5CFeriel%5C%5CIrene_Book%5C%5Cnode137.html)[et](file:///E%3A%5C%5CFeriel%5C%5CIrene_Book%5C%5Cnode137.html)[unless](file:///E%3A%5C%5CFeriel%5C%5CIrene_Book%5C%5Cnode137.html)
  • [cond](file:///E%3A%5C%5CFeriel%5C%5CIrene_Book%5C%5Cnode138.html)
  • [case](file:///E%3A%5C%5CFeriel%5C%5CIrene_Book%5C%5Cnode139.html)

cond

On a souvent besoin d'imbriquer plusieurs conditionnelles dans une chaîne de tests. Avec if, ce type de chaîne risque de provoquer une indentation importante. Exemple :

(if (< x 12)

(...)

(if (< x 28)

Publicité

(...)

(if (< x 55)

(...)

(...))))

Pour gérer cette situation fréquente, Common Lisp propose une conditionnelle très générale : cond. Avec cond, il est possible de donner une suite declauses, chacune contenant une condition et une suite (progn implicite) d'expressions à évaluer si la condition vaut vrai. Les clauses sont indentées de la même manière, ce qui évite le gaspillage d'indentation. L'exemple ci-dessus se transforme alors en :

(cond ((< x 12) (...))

((< x 28) (...))

((< x 55) (...))

(t (...)))

La clause commençant par t n'est pas obligatoire. Elle sert de condition sinon finale et les expressions associées seront évaluées si aucune autre condition ne vaut vrai. Alors que n'importe quelle expression dont la valeur est vrai pourrait être utilisée, il est conventionnel d'utiliser t pour signaler la dernière phrase. Voici quelques exemples de McCLIM :

(cond ((= max r) (/ (- g b) diff))

((= max g) (+ 2 (/ (- b r) diff)))

(t (+ 4 (/ (- r g) diff))))

(cond ((slot-value pane 'armed)

(draw-rectangle\* pane 0 0 w h

:ink (gadget-highlighted-color pane)

:filled t)

(draw-edges-lines\* pane (- w 2) (- h 2) 1 1))

(t

(draw-rectangle\* pane 0 0 w h

:ink (gadget-normal-color pane)

:filled t)))

Le dernier exemple montre une utilisation de cond pour laquelle la seule raison de ne pas avoir utilisé un if est le progn implicite de la première clause. Avecif on aurait :

(if (slot-value pane 'armed)

(progn (draw-rectangle\* pane 0 0 w h

:ink (gadget-highlighted-color pane)

:filled t)

(draw-edges-lines\* pane (- w 2) (- h 2) 1 1))

(draw-rectangle\* pane 0 0 w h

:ink (gadget-normal-color pane)

:filled t)))

Le cond offre aussi la possibilité de mettre des clauses ne contenant que la condition. Si la condition d'une telle clause vaut vrai, c'est cette valeur qui sera la valeur de l'expression cond entière. Dans l'exemple suivant (de McCLIM), l'expression (gethash name \command-tables\) renvoie nil si le tableau de hachage \command-tables\* ne contient pas d'objet avec une clé égale à name*. Sinon, elle renvoie l'objet :

(cond ((command-table-p name) name)

((gethash name \command-tables\))

(errorp (error 'command-table-not-found))

(t nil))

if

La conditionnelle la plus fondamentale est le if. Son premier argument est une expression appelée la condition qui sera toujours évaluée. Le deuxième et le troisième arguments du if forment respectivement la partie alors et la partie sinon. Si la valeur de la condition vaut vrai (n'importe quel objet Common Lisp autre que nil) ou faux (l'objet nil), l'expression de la partie alors ou celle de la partie sinon sera évaluée. Exemples :

\ (if (> \read-base\* 7)

(progn (print "hello") (+ \read-base\ 22))

11)

"hello"

32

\ (if (< \read-base\* 7)

Publicité

(progn (print "hello") (+ \read-base\ 22))

11)

11

\* (if 0

(progn (print "hello") (+ \read-base\ 22))

11)

"hello"

32

\*

Il est possible d'omettre la partie sinon, qui sera alors nil par défaut :

\ (if (oddp \read-base\*)

(progn (print "hello") (+ \read-base\ 22)))

NIL

\*

mais c'est considéré comme du mauvais style.

case

La macro case est similaire à cond. Elle permet de confronter un objet à plusieurs clauses. Les expressions de la clause contenant l'objet seront évaluées.

Le premier paramètre du case est une expression arbitraire qui est toujours évaluée. Sa valeur donne un objet Common Lisp arbitraire (souvent un symbole). Après le premier paramètre figure une suite de clauses. Chaque clause contient une liste d'objets (ou un seul objet) à comparer avec la valeur de l'expression. Si cette valeur figure parmi les objets de la liste, les expressions de la clause seront évaluées (progn implicite). La liste de la dernière clause peut être remplacée par otherwise ou t, indiquant une clause à évaluer en dernier recours. Exemple (de McCLIM) :

(case after

(:start (push new-item menu))

((:end nil) (setf menu (nconc menu (list new-item))))

(:sort (setf menu (sort (cons new-item menu)

#'string-lessp

:key #'command-menu-item-name)))

(t (push new-item

(cdr (member after menu

:key #'command-menu-item-name

:test #'string-equal)))))

Dans cet exemple, l'expression est la variable after. La première clause est sélectionnée si la valeur de cette variable est le symbole (mot-clé) :start. Dans ce cas, l'expression (push new-item menu) sera évaluée. Si ce n'est pas le cas, mais que la valeur est soit :end, soit nil, alors l'expression de la deuxième clause sera évaluée, etc.

Chaque clause commence donc soit par une liste d'objets soit par un seul objet. Pour supprimer tout risque d'ambiguïté, une clause (sauf la dernière) ne peut pas commencer par le seul symbole t ou le seul symbole otherwise. Si l'un de ces symboles doit être utilisé, il faut le mettre dans une liste à un seul élément, donc (t) et (otherwise) respectivement.

Dans une clause, le seul objet nil sera interprété comme la liste vide et les expressions de la clause ne seront jamais évaluées. Pour choisir l'objet nil, écrire(nil).

Attention, le prédicat utilisé pour déterminer si la valeur de l'expression se trouve dans la liste d'objets d'une clause est eql. Si les objets sont des symboles, des caractères ou des nombres, cela ne pose pas de problème. Pour utiliser d'autres types d'objets commes des listes ou des chaînes de caractères, il faut faire attention ; la méthode normale ne s'applique probablement pas :

\* (defparameter abc "abc")

ABC

\* (case abc

("fga" 'no)

("abc" 'yes)

(t 'none))

NONE

\*

Il y a deux autres versions de case : ecase et ccase. Seule la première sera détaillée ici. La clause t ou otherwise du case normal est facultative. Lorsque elle n'est pas présente et qu'aucun des objets des autres clauses ne correspond, la valeur de l'expression case est nil. Parfois, on souhaite vérifier qu'au moins un des objets correspond. Dans ce cas, on peut choisir ecase (pour ``error case''). Cette version n'a pas de clause t ou otherwise et dans la situation décrite ci-dessus, au lieu de nil, on obtient une erreur.

On peut se poser la question de pourquoi case existe puisque que cond est plus général. La réponse est que case est potentiellement plus performant. Aveccase, une étiquette ne peut pas figurer dans plusieurs clauses et toutes les étiquettes sont des constantes, connues à la compilation. Le compilateur peut donc par exemple utiliser le hachage parfait pour émettre le code qui permet de sélectionner la bonne clause.