**Cours 7**
**Contenu**
* Fonctions d'application fonctionnelles: mapcar, mapcan * Listes d'association: assoc, acons, assoc-if, assoc-if-not * Fonctions sur les listes d'associations: pairlis, rassoc, rassoc-if, rassoc-if-not, pairlis * Égalités * Comparaison d'atomes (eql), de nombres (=), identité (eq) * Retour sur appel de fonction (passage par value) * Paramètres des fonctions (requis, &optional, &rest, &key) * macro assert, jeu de tests, macro ignore-errors
Les transparents 60--70
.
**Listes d'association**
Une *liste d'association* (en Anglais : association list, ou alist) est une liste dont les éléments sont des paires (*cons*). L'intention est que le *car* de chaque paire soit une *clé* pour nommer l'objet dans le *cdr* de la paire. Une liste d'association est donc une table indexée.
Une liste d'association n'est certainement pas la façon la plus efficace de construire une table indexée. Mais pour des tables de petite taille, c'est une méthode simple car Common Lisp fournit plusieurs fonctions permettant de la manipuler.
La fonction la plus fondamentale pour la manipulation de listes d'association est *assoc*. Cette fonction prend deux paramètres, une clé et une liste d'association. La liste est parcourue à la recherche d'une paire dont la clé (le*car*) corresponde à la clé donnée en argument. Si une telle clé est trouvée, *la paire entière* est renvoyée. Sinon, *nil* est renvoyé. Exemples :
\* (assoc 'oeuf '((eau . water) (oeuf . egg) (sel . salt)))
(OEUF . EGG) \* (defparameter \*languages\* '((symbolic . (lisp ml haskell)) (imperative . (lisp c pascal cobol fortran)) (object-oriented . (lisp c++ java)) (numeric . (lisp fortran))))
\*LANGUAGES\* \* (assoc 'imperative \*languages\*)
(IMPERATIVE LISP C PASCAL COBOL FORTRAN) \* (cdr (assoc 'imperative \*languages\*))
(LISP C PASCAL COBOL FORTRAN) \* (assoc 'postfix \*languages\*)
NIL \*
Remarques : Une paire de type *(a . (b ...))* (i.e., dont le *cdr* est une liste) est la même chose qu'une liste *(a b ...)*. La fonction *print* utilise toujours la deuxième syntaxe. C'est pourquoi le résultat de l'expression *(assoc 'imperative \*languages\*)* est affiché différemment de la façon dont il a été tapé. L'expression *(cdr (assoc ...))* qui permet d'obtenir la valeur sans la clé est un*idiome* Common Lisp. Ça fonctionne même si il n'y a pas de paire correspodant à la clé, car en Common Lisp la valeur de l'expression *(cdr nil)*est *nil*.
Le prédicat pour tester l'équivalence entre deux clés est *eql* par défaut. Un autre prédicat peut être choisi grâce à un argument *mot-clé*[8.1](file:///E%3A%5C%5CIrene_Book%5C%5Cfootnode.html%22%20%5Cl%20%22foot6684) *:test*. Exemple :
\* (assoc "oeuf" '(("eau" . "water") ("oeuf" . "egg") ("sel" . "salt")))
NIL \* (assoc "oeuf" '(("eau" . "water") ("oeuf" . "egg") ("sel" . "salt")) :test #'string-equal)
("oeuf" . "egg") \*
Pour éviter un test avec négation, l'argument *mot-clé* *:test-not* peut également être utilisé.
Il est possible d'appliquer une fonction arbitraire à la clé avant d'effectuer le test. Pour cela, il faut utiliser un arguement mot-clé *:key*. Exemple :
\* (assoc "oe" '(("eau" . "water") ("oeuf" . "egg") ("sel" . "salt")) :test #'string-equal :key (lambda (s) (subseq s 0 2)))
("oeuf" . "egg") \*
La fonction *assoc-if* est similaire à *assoc*. Elle prend deux paramètres obligatoires, un prédicat et une liste d'association. Le prédicat est appliqué à la clé de chaque paire de la liste jusqu'à ce que le prédicat renvoie *vrai*. La paire correspondante est alors renvoyée comme résultat de l'appel à *assoc-if*. Un argument *mot-clé* *:key* peut également être passé comme pour *assoc*. La fonction *assoc-if-not* est similaire à *assoc-if*, mis à part que c'est la négation du prédicat qui est utilisée. Exemples :
\* (assoc-if #'(lambda (s) (string-equal (subseq s 0 2) "oe")) '(("eau" . "water") ("oeuf" . "egg") ("sel" . "salt")))
("oeuf" . "egg") \* (assoc-if-not #'(lambda (s) (> (length s) 3)) '(("eau" . "water") ("oeuf" . "egg") ("sel" . "salt")))
("eau" . "water") \*
Pour ajouter un élément à une liste d'association, il est bien sûr possible d'utiliser *cons* ou *push*. La fonction *acons* permet de créer une paire et de la ``conser'' au début d'une liste d'association simultanément :
\* (acons 'a 1 '((b . 2) (c . 3)))
((A . 1) (B . 2) (C . 3)) \*
Il est également possible d'utiliser *pairlis* permettant de construire une liste d'association à partir d'une liste de clés et d'une liste de valeurs.
Pour supprimer une paire dans une liste d'association, utiliser une fonction comme *remove*, *remove-if* ou *remove-if-not* (ou les versions destructives correspondantes *delete*, *delete-if* et *delete-if-not*).
Pour changer la valeur d'une clé dans une liste d'association, utiliser l'idiome*(setf (cdr (assoc ...)) expr)*.
Les trois fonctions *rassoc*, *rassoc-if* et *rassoc-if-not* fonctionnent comme*assoc*, *assoc-if* et *assoc-if-not* mais avec les rôles clé-valeur inversés.
Puisque une liste d'association est une liste et une liste, une séquence, il est également possible d'utiliser l'ensemble des fonctions définies pour les listes et les séquences sur les listes d'association. En particulier, les fonctions *find*,*member* et *position*.
Pour copier une liste d'association, utiliser la fonction spécialisée *copy-alist*.
Fonctions
La notion de fonction est centrale en Common Lisp. Les fonctions Common Lisp sont plus petites et plus nombreuses que celles d'autres langages. Le paramétrage des fonctions est plus flexible et autorise arguments facultatifs et mots-clés. Les fonctions sont aussi fréquemment utilisées pour paramétrer le comportement d'autres fonctions ; dans ce cas, la fonction passée en argument est souvent anonyme.
Dans ce chapitre, nous regardons comment définir des fonctions (globales et locales) et toutes les possibilités offertes concernant les paramètres.
**Sous-sections**
* [Définition de fonctions globales](file:///F%3A%5C%5CIrene_Book%5C%5Cnode88.html)
* [Liste de paramètres](file:///F%3A%5C%5CIrene_Book%5C%5Cnode89.html)
* + [Paramètres requis](file:///F%3A%5C%5CIrene_Book%5C%5Cnode90.html)
* + [Paramètres facultatifs](file:///F%3A%5C%5CIrene_Book%5C%5Cnode91.html)
* + [Paramètre reste](file:///F%3A%5C%5CIrene_Book%5C%5Cnode92.html)
* + [Paramètres mots-clés](file:///F%3A%5C%5CIrene_Book%5C%5Cnode93.html)
* + [Exemples de listes de paramètres](file:///F%3A%5C%5CIrene_Book%5C%5Cnode94.html)
* [Définitions de fonctions locales](file:///F%3A%5C%5CIrene_Book%5C%5Cnode95.html)
Publicité
* [Fonctions de première classe et anonymes](file:///F%3A%5C%5CIrene_Book%5C%5Cnode96.html)
* [Appeler une fonction](file:///F%3A%5C%5CIrene_Book%5C%5Cnode97.html)
Définition de fonctions globales
La grande majorité des fonctions sont globales. Une telle fonction est accessible à partir de son nom partout dans un programme.
Comme pour une variable spéciale, le nom d'une fonction globale est un symbole. Le même symbole peut être utilisé pour nommer une fonction et une variable spéciale. On parle d'espaces de noms. Common Lisp a (au moins) deux espaces de noms différents : l'espace des noms de variables et l'espace des noms de fonctions. Il est donc relativement courant de voir des constructions comme celle-ci (de McCLIM) :
(defmethod graft ((graft graft)) graft)
Pour définir une fonction globale, on utilise le plus souvent la macro defun. La syntaxe générale de defun est :
(defun nom lambda-liste expression-1 ... expression-n)
où nom est le nom de la fonction et lambda-liste la liste des paramètres de la fonction, traitée en détail dans la section [7.2](file:///F%3A%5CIrene_Book%5Cnode89.html#seclisteparm). Le corps de la fonction, à savoir la suite d'expressions, constitue un progn implicite (voir chapitre [10](file:///F%3A%5CIrene_Book%5Cnode132.html#contextes)). La valeur de expression-n sera la valeur de l'appel à la fonction. La suite d'expressions qui précède, si elle existe, devrait donc toujours avoir des effets de bords. Exemple :
\* (defparameter \*count\* 0)
\*COUNT\* \* (defun next () (incf \*count\*) (1- \*count\*))
NEXT \* (next)
0 \* (next)
1 \*
Dans cet exemple, la liste des paramètres est vide. La première expression du corps incrémente la variable spéciale \*count\* et la deuxième (et dernière) expression renvoie cette valeur moins 1. Cet exemple est un peu artificiel, car on aura plutôt tendance à l'écrire comme ceci :
\* (defparameter \*count\* 0)
\*COUNT\* \* (defun next () (prog1 \*count\* (incf \*count\*)))
NEXT \* (next)
0 \* (next)
1 \*
voire :
\* (defparameter \*count\* -1)
\*COUNT\* \* (defun next () (incf \*count\*))
NEXT \* (next)
0 \* (next)
1 \*
La définition avec defun peut être imbriquée dans d'autres expressions. Dans ce cas, ce n'est pas une fonction locale qui est définie, mais une fonction globale avec éventuellement une partie de l'environnement close (voir Annexe [B](file:///F%3A%5CIrene_Book%5Cnode315.html#diffscheme)) ; on parle de fermeture :
\* (let ((count -1)) (defun next () (incf count))) Converted NEXT.
NEXT \* (next)
0 \* (next)
1 \*
Le message ``Converted NEXT.'' vient du compilateur CMUCL. Dans cet exemple, la variable lexicale count est enfermée dans le contexte où la fonction next est définie. Il est impossible de la modifier sans passer par la fonction next.
La macro defun modifie l'association entre un symbole et une fonction. Cette association est accessible au programmeur par le biais de symbol-function :
\* (let ((count -1)) (defun next () (incf count))) Converted NEXT.
NEXT \* (eq #'next (symbol-function 'next))
T \* (setf (symbol-function 'next) #'+)
#<Function + 102671B9> \* (next 10 4)
14 \*
Cette possibilité est utilisée par exemple par l'outil de mise au point et par le traceur afin d'entourer une fonction d'un prologue et d'un épilogue.
Liste de paramètres
**Sous-sections**
* [Paramètres requis](file:///F%3A%5C%5CIrene_Book%5C%5Cnode90.html)
* [Paramètres facultatifs](file:///F%3A%5C%5CIrene_Book%5C%5Cnode91.html)
* [Paramètre reste](file:///F%3A%5C%5CIrene_Book%5C%5Cnode92.html)
* [Paramètres mots-clés](file:///F%3A%5C%5CIrene_Book%5C%5Cnode93.html)
* [Exemples de listes de paramètres](file:///F%3A%5C%5CIrene_Book%5C%5Cnode94.html)
Paramètres requis
La liste des paramètres d'une fonction peut contenir plusieurs types de paramètres. Elle peut bien sûr contenir des paramètres ordinaires. En Common Lisp, un tel paramètre est appelé paramètre requis (en Anglais : required parameter), car on doit obligatoirement fournir une valeur pour ce paramètre lors de l'appel à la fonction. Un paramètre requis est un symbole qui, pendant l'appel à la fonction, sera une variable lexicale contenant la valeur d'un argument fourni. Exemple :
\* (defun muladd (x y z) (+ (\* x y) z))
Publicité
MULADD \* (muladd 3 4 5)
17 \*
Ici, x, y et z sont des paramètres requis. Pendant l'appel (muladd 3 4 5), x sera une variable lexicale contenant initialement la valeur 3, y sera une variable lexicale contenant initialement la valeur 4 et z sera une variable lexicale contenant initialement la valeur 5.
Un paramètre requis fonctionne comme une variable lexicale normale. Il est donc possible de modifier sa valeur :
\* (defun bizarre (x) (incf x 3) (expt 2 x))
BIZARRE \* (bizarre 4)
128 \*
Ici, la variable lexicale x a été modifiée avant d'être utilisée dans l'expression finale du corps.
Paramètres facultatifs
Après les paramètres requis, on aura éventuellement des paramètres facultatifs (en Anglais : optional parameters). Le début des paramètres facultatifs (et donc la fin des paramètres requis) est signalé par le symbole &optional. Exemple :
\* (defun f (a b &optional c d) (list a b c d))
F \* (f 1 2)
(1 2 NIL NIL) \* (f 1 2 3)
(1 2 3 NIL) \* (f 1 2 3 4)
(1 2 3 4) \*
Chaque paramètre facultatif peut être indiqué de trois manières différentes. Dans l'exemple ci-dessus, il est indiqué par un symbole simple. Dans ce cas, la valeur par défaut (si l'argument correspondant n'est pas fourni) est nil.
Il est possible de préciser une valeur par défaut pour un paramètre facultatif. On remplace le symbole simple par une liste de deux éléments, la variable et l'expression à évaluer si le paramètre n'est pas fourni. L'expression sera évaluée à chaque fois que le paramètre n'est pas fourni. Exemple :
\* (defparameter \*l\* (list 9 8 7 6 5 4 3 2 1))
\*L\* \* (defun f (a b &optional c (d (pop \*l\*))) (list a b c d))
F \* (f 1 2 3 4)
(1 2 3 4) \* (f 1 2 3)
(1 2 3 9) \* (f 1 2 3)
(1 2 3 8) \* (f 1 2)
(1 2 NIL 7) \* (f 1 2)
(1 2 NIL 6) \*
Paramètre reste
À la suite des paramètres facultatifs peut figurer un unique paramètre reste (en Anglais : rest parameter) lequel est signalé par le symbole &rest suivi d'un symbole qui indique le nom de la variable.
Si un paramètre reste est présent, la fonction accepte un nombre d'arguments supérieur ou égal au nombre de paramètres requis et facultatifs. Les argument supplémentaires sont alors disponibles dans une liste comme valeur de la variable reste. Exemple :
\* (defparameter \*l\* (list 9 8 7 6 5 4 3 2 1))
\*L\* \* (defun f (a b &optional c (d (pop \*l\*)) &rest e) (list a b c d e))
F \* (f 1 2 3)
(1 2 3 9 NIL) \* (f 1 2 3 4 5)
(1 2 3 4 (5)) \* (f 1 2 3 4 5 6 7 8 9)
(1 2 3 4 (5 6 7 8 9)) \*
Avec ce système, on ne peut pas détecter si la valeur du paramètre vient d'un passage explicite ou d'une évaluation de l'expression pour déterminer une valeur par défaut. Pour cela, il est possible de rajouter un paramètre supplémentaire dont la valeur sera vrai si et seulement si l'argument correspondant a été fourni. Exemple :
\* (defun f (x &optional (y 25 y-supplied-p)) (list x y y-supplied-p))
F \* (f 1)
(1 25 NIL) \* (f 1 25)
(1 25 T) \*
Paramètres mots-clés
Le paramètre reste peut être suivi d'une suite de paramètres mot-clés. Le début de la suite est signalé par le symbole &key. Un paramètre mot-clé peut être un symbole. Pour passer l'argument correspondant, deux arguments doivent être passés, le nom du paramètre et sa valeur. Le nom est indiqué par un symbole dont le nom est précédé de `:' (ce qui veut dire qu'il s'agit d'un symbole dans le paquetage des mots-clés. Si l'argument correspondant n'a pas été passé, la valeur du paramètre sera nil. Exemple :
\* (defun f (x &key y z) (list x y z))
F \* (f 1)
(1 NIL NIL) \* (f 1 :y 2)
(1 2 NIL) \* (f 1 :z 3)
(1 NIL 3) \* (f 1 :z 3 :y 2)
(1 2 3) \* (f 1 :y 2 :z 3)
(1 2 3) \*
Un paramètre mot-clé peut être accompagné d'une expression à évaluer si l'argument correspondant n'est pas fourni. Exemple :
Publicité
\* (defun f (x &key (y (cons 'a 'b)) z) (list x y z))
F \* (f 1)
(1 (A . B) NIL) \* (f 1 :y 2)
(1 2 NIL) \* (f 1 :z 3)
(1 (A . B) 3) \*
Comme avec les paramètres facultatifs, il est possible de détecter si l'argument correspondant a été fourni. Exemple :
\* (defun f (x &key (y 25 y-supplied-p)) (list x y y-supplied-p))
F \* (f 1)
8(1 25 NIL) \* (f 1 :y 25)
(1 25 T) \*
Si le symbole &allow-other-keys fait suite aux paramètres mots-clés, alors il est possible de passer d'autres couples (mot-clé, valeur) en arguments. Ces arguments seront simplement ignorés :
\* (defun f (x &key y &allow-other-keys) (list x y))
F \* (f 23)
(23 NIL) \* (f 23 :y 2)
(23 2) \* (f 23 :y 2 :lskdfjs 22)
(23 2) \*
Ce mécanisme est plus utile en combinaison avec &rest, car le paramètre &rest contient toujours l'ensemble des couples mot-clé et valeur correspondante passés en arguments :
\* (defun f (x &rest r &key y &allow-other-keys) (list x y r))
F \* (f 23)
(23 NIL NIL) \* (f 23 :y 2)
(23 2 (:Y 2)) \* (f 23 :y 2 :lsdfjs 22)
(23 2 (:Y 2 :LSDFJS 22)) \*
Exemples de listes de paramètres
Voici quelques exemples de listes de paramètres (de McCLIM) :
(defun make-rotation-transformation (angle &optional origin) (if origin (make-rotation-transformation\* angle (point-x origin) (point-y origin)) (make-rotation-transformation\* angle 0 0)))
(defun find-frame-manager (&rest options &key port &allow-other-keys) (declare (special \*frame-manager\*)) (if (boundp '\*frame-manager\*) \*frame-manager\* (if (and \*default-frame-manager\* (frame-manager-p \*default-frame-manager\*)) \*default-frame-manager\* (first (frame-managers (or port (apply #'find-port options)))))))
(defun make-pane (type &rest args) (apply #'make-pane-1 \*pane-realizer\* \*application-frame\* type args))
Définitions de fonctions locales
Les deux opérateurs spéciaux, flet et labels permettent de créer des fonctions locales à savoir des fonctions dont le nom est connu dans un contexte restreint. Les deux ont une syntaxe similaire à celle de let (voir chapitre [10](file:///F%3A%5CIrene_Book%5Cnode132.html#contextes)) :
(flet (fn-1 ... fn-m) expr-1 ... expr-n)
et
(labels (fn-1 ... fn-m) expr-1 ... expr-n)
Chaque fn-i est une fonction locale avec la syntaxe suivante :
(nom lambda-liste corpsexp-1 ... corpsexp-k)
et fonctionne comme une définition de fonction globale. Parmi les expr-i il y aura toujours au moins un appel à l'une des fonctions locales. Exemple :
\* (flet ((square (x) (\* x x)) (add3 (x) (+ x 3))) (square (add3 5)))
64 \*
La différence principale entre flet et labels est une question de portée des noms. Pour flet, cette portée ne couvre pas les corpsexp-i, alors que pour labels, c'est le cas. Par conséquent, flet est utile pour redéfinir temporairement des fonctions globales ou pour définir des fonctions qui ne s'appellent pas entre elles, alors que labels est utile pour des définitions mutuellement récursives. Exemples :
\* (flet ((+ (&rest l) (print l) (apply #'+ l))) (+ 2 3 4))
(2 3 4) 9 \* (labels ((fact (n) (if (< n 2) 1 (\* n (fact (1- n)))))) (fact 40))
815915283247897734345611269596115894272000000000 \*
C'est uniquement la liaison entre le nom et la fonction qui est locale. La fonction, comme pratiquement tous les objets Common Lisp, a une durée de vie indéfinie. Exemple :
\* (labels ((fact (n) (if (< n 2) 1 (\* n (fact (1- n)))))) (setf \*fn\* #'fact))
#<Interpreted Function FACT 4816B779> \* (funcall \*fn\* 40)
815915283247897734345611269596115894272000000000 \*