lundi 21 septembre 2020

22-Aller plus loin avec SLIME.


Je suppose que vous avez ouvert une session SLIME.

Sinon dans EMACS tapez M-x SLIME.


Rappelons que C représente la touche Ctrl, et M la touche Alt.

Vous venez de copier une fonction au REPL et elle vous semble mal indentée.

Vous pouvez procéder de deux façons:

1) Placez le curseur au début de chaque ligne et tapez la touche <TAB>.

2) Placez le curseur sur la parenthèse ouvrante et tapez: C-M-q.


Tapons ce qui suit:


C-c C-d C-h

Attention! Ne tapez pas C-c C-d h : ceci servirait à ouvrir l'hyperspec.

Une nouvelle fenêtre s'ouvre dans EMACS:

Début de la fenêtre.Je suppose que vous avez ouvert une session SLIME.

Sinon dans EMACS tapez M-x SLIME.


Rappelons que C représente la touche Ctrl.


Tapons ce qui suit:


C-c C-d C-h

Attention! Ne tapez pas C-c C-d h : ceci servirait à ouvrir l'hyperspec.

Une nouvelle fenêtre s'ouvre dans EMACS:


`slime-autodoc-mode' Minor Mode Bindings Starting With C-c C-d:

key                 binding

---                 -------


C-c C-d C-a slime-autodoc-manually

C-c C-d A slime-autodoc-manually


`slime-editing-mode' Minor Mode Bindings Starting With C-c C-d:

key                 binding

---                 -------


C-c C-d C-d slime-describe-symbol

C-c C-d C-f slime-describe-function

C-c C-d C-g common-lisp-hyperspec-glossary-term

C-c C-d C-p slime-apropos-package

C-c C-d C-z slime-apropos-all

C-c C-d # common-lisp-hyperspec-lookup-reader-macro

C-c C-d a slime-apropos

C-c C-d d slime-describe-symbol

C-c C-d f slime-describe-function

C-c C-d g common-lisp-hyperspec-glossary-term

C-c C-d h slime-documentation-lookup

C-c C-d p slime-apropos-package

C-c C-d z slime-apropos-all

C-c C-d ~ common-lisp-hyperspec-format

C-c C-d C-# common-lisp-hyperspec-lookup-reader-macro

C-c C-d C-~ common-lisp-hyperspec-format


Global Bindings Starting With C-c C-d:

key                 binding

---                 -------


[back]

Fin de la fenêtre.


Nous sommes dans le 'slime-autodoc-mode' qui nous fournit toutes les liaisons commençant par:

C-c C-d.


Vous voulez une description de la fonction CAR, tapez:

C-c C-d C-f (ou bien C-c C-d f)

Puis <CAR> et <ENTREE> dans le mini-tampon.

Une nouvelle fenêtre s'ouvre contenant des indications sur la fonction, en particulier sa documentation (en anglais).

C-x o      pour aller dans cette fenêtre.

q         pour quitter la fenêtre.


Vous voulez en savoir plus sur un package: CL par exemple. Tapez:


C-c C-d C-p    (ou bien C-c C-d p)

Puis <CL> et <ENTREE> dans le mini-tampon.

Vous obtenez, cette fois, tous les symboles externes du package Common Lisp: variables, fonctions, macros, type.

C-x o      pour aller dans cette fenêtre.

q         pour quitter la fenêtre.


Vous voulez connaître la fonction liée à un raccourci.
Il existe pour cela une fonction describe-key liée au raccourci C-h k. Tapez:

C-h k
Le mini-tampon vous annonce que vous venez d'appeler la fonction describe-key et attend le raccourci (clé) à décrire.
Justement, tapez: C-h k
La fenêtre suivante s'ouvre:

début

C-h k runs the command describe-key (found in global-map), which is an
interactive compiled Lisp function in ‘help.el’.

It is bound to C-h k, <f1> k, <help> k, <menu-bar> <help-menu>
<describe> <describe-key-1>.

(describe-key &optional KEY UNTRANSLATED UP-EVENT)

Display documentation of the function invoked by KEY.
KEY can be any kind of a key sequence; it can include keyboard events,
mouse events, and/or menu events.  When calling from a program,
pass KEY as a string or a vector.

If non-nil, UNTRANSLATED is a vector of the corresponding untranslated events.
It can also be a number, in which case the untranslated events from
the last key sequence entered are used.
UP-EVENT is the up-event that was discarded by reading KEY, or nil.

If KEY is a menu item or a tool-bar button that is disabled, this command
temporarily enables it to allow getting help on disabled items and buttons.

[back]

fin

Cette fenêtre vous explique ce que fait la fonction describe-key liée au raccourci que vous avez tapé.

Prenons un autre exemple.
Il existe un raccourci: C-h b ...Désolé, je ne me souviens plus à quelle fonction il est lié.
Qu'importe! Je viens de voir comment faire. Je tape:
C-h k
Puis dans le mini-tampon:
C-h b
J'obtiens:

début

C-h b runs the command describe-bindings (found in global-map), which
is an interactive compiled Lisp function in ‘help.el’.

It is bound to C-h b, <f1> b, <help> b, <menu-bar> <help-menu>
<describe> <list-keybindings>.

(describe-bindings &optional PREFIX BUFFER)

Display a buffer showing a list of all defined keys, and their definitions.
The keys are displayed in order of precedence.

The optional argument PREFIX, if non-nil, should be a key sequence;
then we display only bindings that start with that prefix.
The optional argument BUFFER specifies which buffer’s bindings
to display (default, the current buffer).  BUFFER can be a buffer
or a buffer name.

[back]

fin

On me rappelle, dans cette fenêtre, que C-h b est lié à la fonction describe-bindings qui affiche la liste de tous les raccourcis définis et leurs définitions.
...Ah, oui! Je me souviens maintenant.

Derechef, essayons cette fonction. Tapez:
C-h b
La fenêtre qui s'ouvre vous donne accès à tous les raccourcis. Génial!

Les clés (ou raccourcis) que nous venons d'utiliser sont propres à EMACS et s'adaptent au mode du tampon utilisé.
Ce qui veut dire que la réponse ne sera pas forcément la même pour tous les tampons.
Notez que les menus déroulants d'EMACS contiennent certaines clés et que ces menus varient d'un tampon à l'autre.
Il est temps de définir quelques clés d'EMACS. Reprenons celles déjà vues plus haut:

C-h k <key>
M-x describe-key
Décrit la fonction actuelle liée à <key> pour le tampon ayant le focus.
Vous avez deux méthodes d'accès à la fonction:
     1°) C-h k (C est la touche <Ctrl>)
     2°) M-x describe-key (M est la touche <Alt>)
      La touche de tabulation peut être utile pour retrouver la fonction describe-key.

Les autres clés fonctionnent sur le même principe:

C-h b
M-x describe-bindings
Répertorie les raccourcis clavier actuels pour le tampon ayant le focus.

C-h m
M-x describe-mode
Affiche toutes les touches de mode majeur disponibles, puis les touches de mode mineur, pour les modes du tampon ayant le focus.
Essayez!
Placez le curseur dans un tampon REPL, puis C-h m. Voyez le résultat.
Placez le curseur dans un tampon Fundamental, puis C-h m. Constatez un résultat différent.

quelles suite de touches venez-vous de taper? Vous avez un doute. Faites ceci:

C-h l
M-x view-lossage
Vous montre la séquence littérale de touches que vous avez tapées.
Vous constaterez que l'historique (dans une nouvelle fenêtre) remonte assez loin.
La toute dernière séquence doit être:
    C-h l [view-lossage]
La fonction liée apparaît entre crochet.

La désignation C-h est ce qu'on appelle une clé canonique. Elle signifie Ctrl-h ou <F1> actuellement.
Mais vous pouvez changer cette signification dans votre fichier .emacs qui initialise votre EMACS.
Si, par exemple, vous voulez dédier la touche <F1> à la fonction d'aide et C-h à la touche <Suppr> (ou Del), vous mettrez dans votre .emacs:
(global-set-key [f1] 'help-command)
(global-set-key "\C-h" 'delete-backward-char)
Mais attention! S i vous voulez faire appel, par exemple, à la fonction 'view-lossage', il ne faudra plus taper C-h l mais <F1> l.
C'est la fonction 'global-set-key' qui permet de lier une clé à une fonction. Sa syntaxe est:
(global-set-key KEY COMMAND)
Notez que, par défaut, la fonction 'global-set-key' n'est pas liée à une clé...
...Mais vous savez, maintenant, comment faire!
Sachez, aussi, qu'il peut y avoir un phénomène d'ombrage (shadowing) si la clé que vous voulez définir est déjà utilisée en liaison locale dans le tampon courant. Et...
... C'est tant mieux!

Les commandes d'évaluation:

C-x C-e
M-x slime-eval-last-expression
Évalue l'expression avant le curseur et affiche le résultat dans la zone mini-tampon.
exemple:
tapez dans le tampon slime-repl:
(car (nthcdr 3 '(a b c d e f)))
Votre curseur étant juste après la dernière parenthèse fermante.
Tapez C-x C-e
D s'affiche dans le mini-tampon.
Déplacez le curseur d'un cran vers la gauche.
Tapez C-x C-e
(D E F) s'affiche dans le mini-tampon.
Déplacez le curseur d'un cran vers la gauche.
Tapez C-x C-e
(A B C D E F) s'affiche dans le mini-tampon.
C'est toujours l'expression qui précède le curseur qui est évaluée.
Le curseur doit toujours être après une parenthèse fermante.
Ceci est utile pour évaluer une expression se trouvant à l'intérieur d'une autre.

C-M-x
M-x slime-eval-defun
Évalue la forme actuelle de niveau supérieur et affiche le résultat dans la zone mini-tampon.
Le raccourci C-M-x peut ne pas être défini dans votre implémentation (mais vous savez comment faire).
Sinon utilisez: M-x slime-eval-defun. Cette fonction traite spécialement les expressions 'defvar'.
Supposons que la variable définie par 'defvar' évolue pendant, disons, un calcul.
Si vous évaluez l'expression 'defvar' avec M-x slime-eval-defun, alors la variable sera réinitialisée à sa valeur initiale.
Vérifions cela sur un cas simple:
Je définis ma variable dans slime-repl:
(defvar *v* 1)
*V*
Je définis une fonction qui fait évoluer ma variable:
(defun new ()
   (setq *v* (1+ *v*)))
NEW
Je vérifie la valeur de la variable:
*v*
1
Je fais appel à la fonction plusieurs fois:
(new)
2
(new)
3
(new)
4
Je veux redéfinir ma variable à la valeur initiale (1):
(defvar *v* 1)
*V*
Cela ne marche pas:
*v*
4
Je place le curseur sur defvar et j'active ma fonction d'évaluation:
M-x slime-eval-defun (ou M-x s-e-d en utilisant la touche de tabulation et en completant)
Il ne se passe rien et c'est normal.
Je vérifie la valeur de ma variable:
*v*
1
La variable a bien été réinitialisée. Ouf!
Imaginez une situation de déboggage. Cela peut être très utile de réinitialiser une variable!

Remarque: si C-x C-e reçoit un argument numérique, la valeur rendue par l'évaluation est affichée dans le tampon courant à la suite de l'expression (et non dans le mini tampon).
Pour le vérifier, mettez le curseur à la fin d'une expression.
Tapez:
C-u 2
puis:
C-x C-e
Et constatez.

C-c : ou bien:  C-c C-e
M-x slime-évaluation-interactive
Évalue une expression lue dans le mini-tampon.
Exemple:
Tapez: C-c :
Puis une expression: (defvar *v* 1)
Le résultat s'affiche dans le mini-tampon: => *V*

C-c C-r
M-x slime-eval-region
Évalue la région.
Exemple:
Replacez le curseur sur la dernière expression: (defvar *v* 1) et sur le premier astérisque *.
Tapez: C-esp puis C-f C-f C-f (3 fois) pour avancer à la fin du deuxième astérisque.
Tapez: C-c C-r
Vous constatez que la variable *V* a été évaluée (dans le mini-tampon).
Pour désélectionner vous pouvez faire C-w C-y (C-w efface et C-y recopie)
Attention: si la sélection est une expression incomplète, une erreur est signalée.

Notez que ces commandes d'évaluation sont accessibles par le menu:
Quand vous êtes dans le tampon *slime-repl sbcl*, cliquez dans le menu sur SLIME, puis sur Evaluation.
Vous retrouvez les quatre fonctions précédentes.
Vous y trouvez aussi les fonctions 'Eval And Pretty-Print' et 'Eval Region And Pretty-Print' qui font la même chose que:
C-x C-e et C-c C-r respectivement, mais en ouvrant une nouvelle fenêtre pour l'affichage du résultat (au lieu du mini-tampon).

C-c C-p
M-x slime-pprint-eval-last-expression
Évalue l'expression avant le point et affiche le résultat dans un nouveau tampon.
Vous n'êtes pas dans REPL, mais dans un tampon contenant des expressions lisp (ou bien vous êtes en train de définir des fonctions).
Vous pouvez tester vos expressions à tout moment.

C-c E
M-x slime-edit-value
Modifie la valeur d'une forme setf-able dans un nouveau tampon *Edit <form>*. La valeur actuelle est inséré dans un tampon temporaire pour l'édition, vous pouvez alors la modifier, puis elle est défini dans Lisp lors de la validation avec C-c C-c.
Exemple: prenons la forme setf-able 'second'.
Je définis une liste pour ce qui suit:
(defparameter liste '(a b c d))
Je modifie le second élément de cette liste:
(setf (second liste) 5)
Je vérifie:
liste
(A 5 C D)
Je place le curseur à la fin de l'expression (second liste) dans (setf (second liste) 5)
et je demande l'évaluation avec:
C-c E
Je valide Ce qui s'affiche dans le mini_tampon.
Une fenêtre s'ouvre en affichant 5.
Je rectifie la valeur affichée: 5 --> 7
Et je valide avec C-c C-c.
Je vérifie:
liste
(A 7 C D)
Le deuxième élément a bien été modifié.

Quand le curseur se trouve dans une définition, vous pouvez faire appel à la fonction ainsi définie:
(vous êtes toujours dans le tampon *slime-repl sbcl*)
Ouvrez le menu SLIME-->Evaluation-->Call Defun
Entrez, éventuellement, les arguments et validez.
Ceci est pratique pour tester la fonction que vous voulez définir.

SAUVEGARDE D'UN TRAVAIL.

C-x C-f
    Permet de créer un nouveau fichier.
Exemple:
Supposons  que que nous soyons dans REPL.
Vous venez de programmer une nouvelle fonction. Disons:
(defun salutations ()
  (format t "Bonjour tout le monde."))
Vous voulez enregistrer cette fonction dans un fichier nommé "salut.lisp".
Ceci est nécessaire si vous voulez réutiliser cette fonction dans une prochaine session SLIME.
Car en quittant SLIME toutes vos définitions s'effacent!
Donc, copiez (dans REPL) la dite fonction: sélectionnez le texte et M-w.
Alors tapez:
C-x C-f
Puis dans le mini-tampon:
salut.lisp
Un nouveau tampon nommé "salut.lisp" souvre dans le mode "Lisp adoc [COMMON-LISP-USER sbcl]".
C-y    Pour coller la fonction.
Remarquez que l'indentation n'est pas forcément correcte.
Tapez <ENTREE>
      Et la fonction est réindentée convenablement.
A ce stade, vous pouvez évaluer et compiler la définition:
C-c C-c
Pour revenir dans RPEL, tapez:
C-c C-z      ou C-x b
Un message vous indique que la fonction a été compilée.
; compiling (DEFUN SALUTATIONS ...)
WARNING: redefining COMMON-LISP-USER::SALUTATIONS in DEFUN
De plus, le message indique que la fonction est redéfinie si on l'avait déjà définie dans REPL.
Maintenant, vous pouvez utiliser votre fonction compilée:
(salutations)
Bonjour tout le monde.
NIL

Si vous voulez modifier votre fonction, ou bien en définir une autre, tapez à nouveau:
C-x b
Le mini-tampon vous propose de basculer vers un autre buffer (tampon) et par défaut le tampon "salut.lisp".
Tapez <ENTREE> et vous êtes de nouveau dans le tampon "salut.lisp".
Ajoutons une documentation à notre fonction "salutations" et regardez ce qui se passe dans le mini-tampon:
Une indication montre comment doit être utilisée la fonction defun (de même avec la fonction format).
(defun salutations ()
  "Affiche des salutations au monde qui nous entoure."
  (format t "Bonjour tout le monde."))
Et définissons une deuxième salutation:
(defun salutations2 ()
  "Salutations du soir aux spectateurs de la salle."
  (format t "Bonsoir Mesdames et Messieurs."))
A ce niveau, vous recompilez avec:
C-c C-c
Le mini-tampon vous dit que la compilation est terminée, qu'il n'y a pas d'erreur, et donne le temps de la compilation.
Puis, pour retourner dans RPEL:
C-x b et <ENTREE>
Notez bien que:
      -la fonction salutations a été évaluée.
      -la fonction salutations2 a été compilée.
Essayons cette 2ème fonction:
(salutations2)
Bonsoir Mesdames et Messieurs.
NIL
Testons la documentation des deux fonctions:
(documentation 'salutations 'function)
NIL
(documentation 'salutations2 'function)
"Salutations du soir aux spectateurs de la salle."
La documentation de la 1ère fonction n'a pas été prise en compte (cette fonction était déjà compilée).
La documentation de la 2ème fonction est bien prise en compte.
Cela s'explique! Car le curseur dans le tampon salut.lisp se situait après la définition de la 2ème fonction. Donc, seule, la 2ème fonction est compilée.
Pour recompiler la 1ère fonction, mettez le curseur sur la 1ère définition ou à la fin de cette définition.
Puis:
C-c C-c et C-c C-z
Et tout devrait rentrer dans l'ordre. Essayez!
(documentation 'salutations 'function)
"Affiche des salutations au monde qui nous entoure."

Comme nous sommes très contents de ce travail, nous allons enregistrer le fichier salut.lisp pour pouvoir le réutiliser par la suite.
Revenez dans le tampon "salut.lisp" et tapez:
C-x C-s
Si vous quittez SLIME, pour retrouver vos fonctions il faudra ouvrir une nouvelle session SLIME et charger le fichier salut.lisp.

__________
Remarques: le chargement se fait, normalement avec (load "...").
Mais il se peut que cela ne marche pas. Une des raisons est qu'il n'est pas enregistré dans le répertoire courant.
Après une recherche, Je l'ai trouvé dans le répertoire racine ~. Mais le nom du fichier était #salut.lisp#.
J'ai pu le recharger en faisant:
(load "~/#salut.lisp#")
Les deux fonctions étaient bien fonctionnelles.
En voulant fermer le tampon salut.lisp, j'ai perdu toute trace du fichier (donc définitivement).
On peut en déduire que #salut.lisp# est un nom temporaire qui  tombe dans le garbage collector à la fermeture.
Ce Ramasse-miettes peut-il intervenir sur le disque dur? Probablement pas.
Donc si vous voulez sauvegarder correctement votre fichier, précisez le répertoire, donc le chemin complet (ce répertoire peut-être proposé dans le mini-tampon).
__________

Ouvrez le tampon salut.lisp:
File-->OpenFile
Et dans le mini-tampon:
~/votre/chemin/salut.lisp
Vous pouvez ajouter d'autres fonctions dans votre tampon actuel.
Pour compiler et charger les fonctions dans REPL, tapez:
C-c C-k
Basculez dans le tampon REPL et constatez:
; compiling file "/home/trochet/LISP/Clisp/salut.lisp" (written 11 OCT 2020 02:59:33 PM):

; /home/trochet/LISP/Clisp/salut.fasl written
; compilation finished in 0:00:00.007
Le fichier a été entièrement compilé (on vous indique la date et l'heure).
Un fichier salut.fasl a été créé (vous devez le retrouver a côté du fichier salut.lisp.
Ce fichier pourra être chargé dans REPL de la façon suivante:
(load "/home/trochet/LISP/Clisp/salut.fasl")
La compilation a été effectuée en 7/1000 de seconde.
...Et vos fonctions sont utilisables dans REPL.
Remarque: vous avez utilisé C-c C-k dans le tampon salut.lisp, et pourtant si vous demandez:
C-c C-d C-d, puis C-c C-k, le mini-tampon vous répond que ce raccourci n'est pas défini!
Ceci n'est peut-être pas vrai dans toutes les implémentations.

COMPILATIONS PARTICULIÈRES des précédents raccourcis.
C-u C-c C-c
    Compile une forme avec un maximum de débogage.
M-- C-c C-c
    Compile une forme pour gagner en vitesse.
C-u C-c C-k
    Compile un fichier avec un débogage maximal.
M-- C-c C-k
    Compile un fichier pour accentuer la vitesse.
C-c M-k
    Compile le fichier source du tampon actuel, mais ne le charge pas.

C-c C-l
M-x slime-load-file
Charge un fichier Lisp ou fasl. Cette commande utilise la fonction Common Lisp LOAD.
On utilise, de cette façon, le mini-tampon qui propose le chemin actuel et on se sert de la complétion pour écrire le fichier.
Exemple:
Redémarrer SLIME:
,(virgule) puis dans le mini-tampon: quit
M-x slime
C-c C-l
Dans le mini-tampon:
Load file: ~/LISP/Clisp/
ajouter: salut.fasl
Le fichier compilé est ajouté dans REPL.

lundi 22 août 2016

21-FONCTIONS et FERMETURES: (CLOSURES:)

>>>>
Fonctions et fermetures:

Programmes pour construire des fonctions:

(defun make-add (n)
  #'(lambda (x) (+ x n)))
MAKE-ADD       rend une fonction qui ajoute n à sa variable x
    il ne reste qu'à donner un nom à cette fonction par:

(setf add3 (make-add 3))
#
   définition de ADD3 qui prend comme valeur une fonction
 à l'aide de la fermeture LAMBDA (de ADD)
La fonction est appelée par:

(funcall add3 15)
18 ajoute 3 à l'argument 15

Il peut être intéressant de changer le nombre "fixe" à ajouter. Voici donc une variante de MAKE-ADD:

(defun make-add-b (n)
  #'(lambda (x &optional chgt) ;chgt est une expression dont la valeur de vérité importe pour la suite
      (if chgt   ;si vrai...
  (setq n x) ;...n prend la valeur de x, sinon...
  (+ x n)))) ;...n est ajouté à x

(defparameter add-b nil)
ADD-B      la variable ADD-B est initialisée à NIL

(setq add-b (make-add-b 1))
# ADD-B prend pour valeur une fonction qui ajoute 1 (arbitrairement)

(funcall add-b 3)
4 4 est bien le résultat de 3+1

(funcall add-b 5 t)
5 l'option T permet de changer la valeur à ajouter: 5 ici

(funcall add-b 3)
8 vérification: 3+5

Fonction linéaire mult5: x -> 5x

(defun mult (n)
  #'(lambda (x) (* x n)))
MULT       rend une fonction qui multiplie par n la variable x

(setf mult5 (mult 5))
#
   définition de mult5 qui prend comme valeur une fonction
 à l'aide de la fermeture LAMBDA (de MULT)

(funcall mult5 6)
30

Comme nous l'avons fait pour ADD, on peut vouloir changer le facteur de multiplication:

(defun make-mult-a (n)
  #'(lambda (x &optional chgt) ;chgt est une expression dont la valeur de vérité importe pour la suite
      (if chgt   ;si vrai...
  (setq n x) ;...n prend la valeur de x, sinon...
  (* x n)))) ;...x est multiplié par n

(setq mult-a (make-mult-a 1))
# MULT-A prend pour valeur une fonction qui multiplie par 1 (arbitrairement)

(funcall mult-a 3)
3 rend le résultat de 3x1

(funcall mult-a 2 t)
2 l'option T permet de changer la valeur du facteur (1 devient 2)

(funcall mult-a 3)
6 vérification: 3x2


Et maintenant, fabriquons des fonctions affines (f(x)=ax+b):

(defun affine (a b)
  #'(lambda (x) (+ b (* a x))))
AFFINE       rend une fonction affine x->ax+b

(setf aff2-3 (affine 2 3))
#
   définition de aff2-3: x->2x+3

(funcall aff2-3 3)
9 pour x=3 la fonction vaut 9

Je vous laisse définir une fonction MAKE-AFFINE qui construit une fonction affine de coefficients a et b pouvant changer ces coefficients à l'aide d'un argument optionnel vrai (T).

(mapcar (affine 2 3) '(-3 -2 -1 0 1 2 3))
(-3 -1 1 3 5 7 9)      pour une série de nombres

(mapcar #'cons lst (mapcar (affine 2 3) '(-3 -2 -1 0 1 2 3)))
((-3 . -3) (-2 . -1) (-1 . 1) (0 . 3) (1 . 5) (2 . 7) (3 . 9))
              pour avoir les couples de la forme (abscisse . ordonnée)

La fonction suivante permet de rendre la fonction composée de plusieurs fonctions:

(defun fonction-composée (&rest fns) ;fns est une suite de fonctions passées en argument
  (destructuring-bind (fn1 . rest) (reverse fns)    ;fns est retournée et destructurée sous la forme (fn1 . rest)
    #'(lambda (&rest args)           ;fermeture
(reduce #'(lambda (v f) (funcall f v)) ;appel aux fonctions suivantes dans
rest           ;la suite passée à lambda (2ème)
:initial-value (apply fn1 args))))) ;valeur initiale passée à reduce
FONCTION-COMPOSÉE rend la fonction composée de toutes les fonctions passées en argument

Exemples de composition:

(mapcar (fonction-composée #'list #'round #'sqrt)
'(4 9 16 20 25))
((2) (3) (4) (4) (5))

On se propose de calculer la racine-carrée d'un nombre positif au dixième près.
Pour cela on multiplie le nombre par 100, puis on prend l'arrondi de sa racine-carrée.
On divise le résultat par 10 et on convertit en flottant pour éviter les fractions.

D'abord, on va définir quelques fonctions conformément à ce qui précède:

(setf mult100 (mult 100))
#
   définition d'une fonction qui multiplie par 100
 mult a déjà été définie

(defun div (n)
  #'(lambda (x) (/ x n)))
DIV       rend une fonction qui divise par n

(setf div10 (div 10))
#
   définition d'une fonction qui divise par 10

(setf sqrt-au-10ème (fonction-composée #'float div10 #'round #'sqrt mult100))
#
   rend une fonction-composée qui calcule la racine-carrée au 1/10ème près

(defun racine-carrée-au-1/10 (n)
  "rend la racine-carrée d'un nombre positif au dixième près."
  (if (>= n 0)
      (funcall sqrt-au-10ème n)
      (error "Le nombre en argument doit être positif.")))
RACINE-CARRÉE-AU-1/10   définition de la fonction rendant la racine-carrée au 1/10ème près

(racine-carrée-au-1/10 21)
4.6       la racine-carrée de 21 est 4,6 au 1/10ème près

Construction d'un prédicat qui est la réunion de un ou plusieurs prédicats:
La fonction suivante est souvent nommée DISJOIN:

(defun union-pred (pred &rest preds)        ;un prédicat et le reste
  (if (null preds)        ;si le reste est vide...
      pred        ;...on prend le premier qui est le dernier
      (let ((réunion (apply #'union-pred preds)))   ;la variable réunion contient le prédicat suivant (récursion)
#'(lambda (&rest args)  ;fermeture appliquant...
    (or (apply pred args) (apply réunion args)))))) ;...la réunion des prédicats par cumul sur la récursion
UNION-PRED     rend un nouveau prédicat qui rend T si l'un des prédicats constituants rend T

(setf mon-pred1 (union-pred #'integerp #'symbolp))
#
   définit un nouveau prédicat mon-pred1 (qui n'est donc plus anonyme)

(mapcar mon-pred1 '(a "b" 3 5.4))
(T NIL T NIL)  a est bien un symbole même si ce n'est pas un entier
         "b" n'est ni un entier ni un symbole
 3 est un entier même si ce n'est pas un symbole
 5.4 n'est ni un entier ni un symbole

Construction d'un prédicat qui est l'intersection de un ou plusieurs prédicats:
La fonction suivante est souvent nommée CONJOIN:

(defun inter-pred (pred &rest preds)  ;un prédicat et le reste
  (if (null preds)    ;si le reste est vide...
      pred    ;...on prend le premier qui est le dernier
      (let ((inter (apply #'inter-pred preds))) ;la variable inter contient le prédicat suivant (récursion)
#'(lambda (&rest args) ;fermeture appliquant...
    (and (apply pred args) (apply inter args)))))) ;...une intersection des prédicats par cumul sur la récursion
INTER-PRED     rend un prédicat qui rend T si tous les prédicats constituants rendent T

(mapcar (inter-pred #'integerp #'oddp) '(a "b" 3 5.5))
(NIL NIL T NIL)           seul 3 est un entier impair
              ici, le prédicat utilisé par MAPCAR est laissé anonyme

Voici deux fonctions (souvent nommées CURRY: et RCURRY:) qui permettent
"d'adapter" une fonction à un contexte. Elles rendent une autre fonction
(ou un prédicat) adaptée à gauche ou à droite comme je vais l'expliquer plus loin:

(defun adaptation-gauche (fn &rest args)
  #'(lambda (&rest args2)
      (apply fn (append args args2))))
ADAPTATION-GAUCHE

(defun adaptation-droite (fn &rest args)
  #'(lambda (&rest args2)
      (apply fn (append args2 args))))
ADAPTATION-DROITE

Ces deux fonctions peuvent rendre la même fonction si la fonction
passée en argument a un caractère commutatif.
Si on passe en argument la fonction #'+ qui est commutative, on obtiendra la même
fonction que celle obtenue plus haut avec MAKE-ADD, et à gauche comme à droite:

(funcall (adaptation-gauche #'+ 3) 15)
18

(funcall (adaptation-droite #'+ 3) 15)
18

De plus, comme la fonction #'+ admet de nombreux arguments, on peut écrire par exemple:

(funcall (adaptation-gauche #'+ 3 4) 15 8)
30

Par contre, si on passe en argument la fonction #'- qui n'est pas commutative,
on obtient un résultat à gauche différent du résultat à droite:

(funcall (adaptation-gauche #'- 3) 15)
-12    résultat de 3-15

(funcall (adaptation-droite #'- 3) 15)
12    résultat de 15-3

On peut, également, transformer un prédicat avec ces fonctions d'adaptation:

(funcall (adaptation-gauche #'>= 3) 2.9)
T la question posée par le prédicat est: "est-ce que l'argument est plus petit ou égal à 3?".
donc T, ici.

(funcall (adaptation-gauche #'>= 3) 3)
T même question pour l'argument à la limite.

(funcall (adaptation-gauche #'>= 3) 3.1)
NIL même question pour un argument plus grand que 3 (donc qui n'est pas plus petit ou égal à 3).

Remplaçons dans ces 3 cas précédents "gauche" par "droite":

(funcall (adaptation-droite #'>= 3) 2.9)
NIL la question est, cette fois: "est-ce que l'argument est plus grand ou égal à 3".
donc NIL, ici.

(funcall (adaptation-droite #'>= 3) 3)
T même question à la limite

(funcall (adaptation-droite #'>= 3) 3.1)
T même question pour un argument plus grand que 3

Quelle est la meilleure disposition pour #'>=: adaptation-gauche ou adaptation-droite?
Cela se discute et, bien sûr, il faut savoir ce que l'on veut et ce que l'on fait.
J'ai ma préférence pour adaptation-droite pour cette fonction #'>=.

Je vous laisse réfléchir au cas de la fonction #'<=.
Là aussi, j'ai ma préférence pour adaptation-droite, car c'est la situation de l'argument
qui nous intéresse. Mais, je le répète: cela se discute!

On peut se poser la question de savoir si un nombre se trouve dans un intervalle
(disons fermé) [min max]. Donc s'il est supérieur ou égal à min et inférieur ou
égal max.

(defun intervallep (min max)
  "rend un prédicat qui vérifie si le nombre qu'on lui passe en argument est dans l'intervalle [min max]."
  (inter-pred (adaptation-droite #'>= min) (adaptation-droite #'<= max)))
INTERVALLEP

(mapcar (intervallep -1 2) '(-2 -1 -0.5 1 2 2.5))
(NIL T T T T NIL)     -2 et 2.5 ne sont pas dans l'intervalle [-1 2]

(mapcar #'cons lst (mapcar (intervallep -1 2) '(-3 -2 -1 0 1 2 3)))
((-3) (-2) (-1 . T) (0 . T) (1 . T) (2 . T) (3))
        sous cette forme on voit bien lesz valeurs qui sont dans l'intervalle: -1, 0, 1 et 2.
  rappelons que (-3) c'est aussi (-3 . NIL) écriture réduite automatiquement en lisp.

La fonction suivante est aussi nommée ALWAYS:

(defun toujours (x) #'(lambda (&rest args) x))
TOUJOURS rend une fonction constante dont la valeur vaut celle passée en argument

(setf const (always 2.7))
#

(funcall const)
2.7

(funcall const 5)
2.7

(funcall const 5 6)
2.7       quelque soit le nombre d'arguments passés à la fonction const
      elle rend 2.7

Portée lexicale:    une variable locale est toujours à peu près de portée lexicale.
Portée dynamique:   une variable globale est toujours une variable spéciale de portée dynamique.

(let ((x 10))
  (defun foo (y)
    (+ x y))) ;x se réfère à la variable définie dans ce LET
FOO    

(let ((x 20))
  (foo 2))
12   la fonction foo rend le calcul de 10+2 et non 20+2
  la portée est lexicale

(let ((x 10))
  (defun foo (y)
    (declare (special x))
    (+ x y)))       ;x est déclarée comme spéciale dans la définition de foo
FOO

(let ((x 20))
  (declare (special x)) ;x doit être déclarée comme spéciale pour utiliser la fonction foo
  (foo 2))
22   la fonction foo rend, cette fois le calcul de 20+2 et non 10+2
si x n'est pas déclarée comme spéciale, il y a une erreur
la portée est dynamique

(setf x 30)
30 toute variable initialisée avec setf est implicitement spéciale

(foo 2)
32 vérification

(defparameter x 40)
X      on peut éviter la déclaration précédente en utilisant DEFPARAMETER

(foo 2)
42 defparameter initialise des variables spéciales (ici 40)

*print-base* est une variable globale.
On utilise sa portée dynamique pour changer, temporairement, sa valeur:

(let ((*print-base* 16))
  (princ 15))
F princ affiche la valeur de 15 dans le système hexadécimal
15 puis la valeur de l'expression est rendue dans le système décimal,
après la sortie du LET.

Le prochain chapitre est en préparation: on approfondit SLIME.

samedi 30 mai 2015

18-LES SYMBOLES.

>>>>
Les symboles.

SYMBOLP:

(symbolp 'a)
T toute lettre (ou groupe de lettres) peut être un symbole

'a
A lisp convertit le symbole en majuscule automatiquement

(symbol-name 'a)
"A" tout symbole a un nom

Cependant, ce qui suit est aussi un symbole:

'|a b&C|
|a b&C| entre 2 barres verticales on peut placer tous les signes possibles
    on obtient un nouveau symbole qui a, lui aussi, un nom:

SYMBOL-NAME:

(symbol-name '|a b&C|)
"a b&C"        le nom est une chaine de caractères

(symbol-name '||)
""                           || est un symbole vide dont le nom est une chaine vide

'|ABC|
ABC abc, aBc, AbC et |ABC|          sont, heureusement, le même symbole avec le même nom

Les barres verticales ne font pas partie du symbole, elles ne servent qu'à le délimiter.
Chaque symbole possède une liste de propriétés ou plist (vide, probablement, au départ):

(setf (get 'abc 'vrai-nom) "les débuts")
"les débuts" la valeur "les débuts" est attribuée à la clé vrai-nom

(get 'abc 'vrai-nom)
"les débuts" get prend en argument le symbole et la clé, et rend la valeur de cette clé

(setf (get 'abc 'définition) "On dit, par exemple, l'abc du métier, pour parler de la base de connaissances.")
"On dit, par exemple, l'abc du métier, pour parler de la base de connaissances."
                      une autre clé: définition

SYMBOL-PLIST:

(symbol-plist 'abc)
(DÉFINITION
 "On dit, par exemple, l'abc du métier, pour parler de la base de connaissances."
 VRAI-NOM "les débuts")            rend la plist du symbole abc

On peut donc appliquer la fonction get-properties déjà vue:

(get-properties (symbol-plist 'abc) '(définition))
DÉFINITION
"On dit, par exemple, l'abc du métier, pour parler de la base de connaissances."
(DÉFINITION
 "On dit, par exemple, l'abc du métier, pour parler de la base de connaissances."
 VRAI-NOM "les débuts")

Un symbole est un véritable objet: il possède un nom, un package, une valeur comme variable, une valeur comme fonction et une liste de propriété.
C'est un peu plus compliqué d'obtenir un symbole à partir de son nom: cela fait intervenir la notion de package (voir LES PACKAGES:).
Un package est une table de symboles mettant en relation les noms et les symboles.
Un symbole qui est dans cette table est dit interne au package.
Un symbole lu la première fois devient automatiquement interne au package courant.

INTERN:   prend comme argument une chaine de caractères et un optionnel nom de package:
FIND-SYMBOL: (même chose, mais ne rend pas le symbole interne)

(intern "ABC")
ABC rend le symbole de nom "ABC"
:INTERNAL indique que le symbole est interne au package courant (en effet, nous l'avons déjà                                 utilisé)

ou bien:

(find-symbol "ABC")
ABC
:INTERNAL indique, aussi, que le symbole est interne, puisqu'il l'était précédemment

Remarque: nous avons écrit "ABC" en majuscules, ce qui correspond bien au symbole déjà utilisé. Sinon:

(intern "abc")
|abc| rend un symbole qui vient d'être créé
NIL ce symbole n'était pas dans le package courant

Mais:

(intern "abc")
|abc|
:INTERNAL le symbole est, maintenant, interne au package

INTERN fait en sorte que le symbole devient interne au package
alors que FIND-SYMBOL ne permet pas de rendre le symbole interne

Entendons-nous bien, |abc| ce n'est pas abc!
En effet, |abc| a pour nom "abc", tandis que abc a pour nom "ABC" (nous l'avons dit plus haut: abc c'est ABC).
Pour s'en persuader un peu plus:

(defparameter ABC 47)
ABC

(defparameter |abc| 23)
|abc|

abc
47

|abc|
23 abc et |abc| sont bien deux variables différentes

Faire appel à une variable non définie crée une erreur, ouvre le débogueur et...rend le symbole interne!

inconnu
; Evaluation aborted on #. à la sortie du débogueur

Rappelons que Clisp convertit les noms de variables en majuscules (donc lors de l'appel à inconnu).

(find-symbol "INCONNU") ;notons que nous écrivons, ici, inconnu en majuscules
INCONNU
:INTERNAL l'appel précédent à la variable inconnu(e) a rendu le symbole interne
                           (ce n'est pas FIND-SYMBOL qui rend le symbole interne)

Un symbole avec des barres verticales:

'|a\|b\|c|
|a\|b\|c| le backslash nécessaire pour introduire une barre verticale est (euh!... évidemment...)                          conservé dans le symbole.
Par contre, pas dans son nom:

(symbol-name '|a\|b\|c|)
"a|b|c"

(intern "a|b|c")
|a\|b\|c|
:INTERNAL

Que se passe-t-il pour le nom d'une fonction?

(intern "CONS")
CONS nous obtenons bien le symbole de la fonction
:INHERITED ce symbole, utilisable dans le package courant, est un héritage d'un autre package.
            (voir LES PACKAGES:)

La 2ème valeur retournée par INTERN peut aussi être :EXTERNAL.
Le symbole est alors externe au package courant, mais pas hérité.
Il faut signaler, également, que certains symboles sont non-internes et, ceci ne veut pas dire qu'ils sont hérités ou externes!
Ce sont les symboles gensyms que nous avons déjà rencontrés.
Un symbole gensym peut prendre la valeur d'une variable x à un moment donné et la conserver, alors que la variable x est amenée à évoluer.

SYMBOL-VALUE:

(symbol-value 'abc)
47 rend la valeur du symbole s'il représente une variable spéciale (variable globale)
ce n'est pas le cas des variables lexicales (variables locales),
car dans un code compilé, il n'y a aucune trace du symbole

Nous avons déjà vu, précédemment, SYMBOL-FUNCTION qui rend la fonction associée au symbole:

(symbol-function 'cons)
#<FUNCTION CONS>

On peut aussi demander le package associé au symbole:

(symbol-package 'cons)
#<PACKAGE "COMMON-LISP">

Pour résumer, un symbole possède un nom, un package, une valeur, une fonction et une plist.
...Enfin, quand tout cela est défini!
On y accède avec les fonctions: SYMBOL-NAME, SYMBOL-PACKAGE, SYMBOL-VALUE, SYMBOL-FUNCTION et SYMBOL-PLIST respectivement.
Attention! Certains symboles sont protégés dans leur package:
Il peut être difficile, alors, de déclarer cons, par exemple, comme une variable spéciale.


Pour trier les noms des symboles du package courant (voir LOOP et les packages: plus haut):

(sort (loop for s being the symbols in *package*
     collecting (symbol-name s)) #'string<)
("&ALLOW-OTHER-KEYS" "&AUX" "&BODY" "&ENVIRONMENT" "&KEY" "&OPTIONAL" "&REST"
 "&WHOLE" "*" "*" "**" "***" "*AFTER-GC-HOOKS*" "*BACKTRACE-FRAME-COUNT*"
...  le reste a été coupé
_____________________________________________
Ce qui suit est inspiré du travail de Paul Graham dans son ANSI-Common-Lisp.
J'ai ajouté quelques amménagements et commenté les fonctions.

Les fonctions suivantes opèrent sur des symboles obtenus à partir d'un texte (Le Petit Prince de Saint-Exupéry, par exemple)

(defparameter *mots* (make-hash-table :size 10000)) ;définition d'une table de hachage *mots* de 10000 clés

(defconstant maximot 100)       ;définition d'une constante valant 100

(defparameter *petit_pr* (merge-pathnames #p"lepetitprince.txt"))
     Le texte que l'on veut traiter doit être placé dans un fichier .txt
     *petit_pr* est un pathname vers ce fichier.

(defparameter *néo-texte* (merge-pathnames #p"nouveau.txt"))
     *néo-texte* est un pathname vers un nouveau fichier

On définit, ici, deux fermetures dans un LET: intro-table et initialisation.

(let ((précédent '|.|)) ;la variable locale précédent est initialisée avec le symbole |.|
  (defun intro-table (symbole) ;définition d'une première fermeture intro-table
    (let ((doublet (assoc symbole (gethash précédent *mots*))));doublet prend une valeur (symbole . alist), ou NIL si symbole n'est pas dans la table
      (if (null doublet)              ;si le doublet est NIL...
  (push (cons symbole 1) (gethash précédent *mots*));...(symbole . 1) est introduit dans la alist de précédent
  (incf (cdr doublet))))       ;sinon le CDR du doublet est incrémenté de 1
    (setf précédent symbole))     ;précédent prend la valeur de symbole pour le prochain appel à intro-table
  (defun initialisation ()     ;définition d'une deuxième fermeture initialisation
    (setf précédent '|.|)     ;réinitialise précédent
    (clrhash *mots*)))     ;réinitialise la table de hachage *mots*

Remarques: on peut dire que précédent retient le symbole précédent.
  Dans la 1ère fermeture, tous les symboles qui sont précéder par précédent sont enregistrés dans une alist qui est la valeur de la clé précédent.
  Chaque clé de la table de hachage pointe sur cette alist qui se construit de proche en proche.
  Si symbole est dans la alist pointée par précédent, le doublet correspondant est récupéré (sinon doublet est NIL).
  si doublet est NIL, (symb . 1) est ajouté à la alist pointée par précédent.
  sinon la valeur associée à précédent est incrémentée de 1.

La fonction ponctuation permet de transformer les signes de ponctuation en symbole:

(defun ponctuation (c)
  (case c
    (#\: '|:|)
    (#\» '|»|)
    (#\« '|«|)
    (#\. '|.|)
    (#\, '|,|)
    (#\; '|;|)
    (#\! '|!|)
    (#\? '|?|))) ;ponctuation rend un symbole correspondant au caractère

La fonction lit-texte construit des listes associatives placées dans une table de hachage:

(defun lit-texte (pathname)
  (with-open-file (flux pathname :direction :input) ;un flux est ouvert en entrée en provenance d'un fichier .txt définit par pathname
    (let ((tampon (make-string maximot))    ;tampon est une chaine de maximot caractères
  (pos 0))     ;pos est un index initialisé à zéro
      (do ((c (read-char flux nil :eof)     ;itération sur le caractère c lu à partir du fichier .txt
      (read-char flux nil :eof)))
  ((eql c :eof))     ;test de fin de fichier
(if (or (alpha-char-p c) (string= c #\-) (string= c #\RIGHT_SINGLE_QUOTATION_MARK))
    (progn           ;si c est un caractère alphabétique ou un trait d'union ou une apostrophe
      (setf (aref tampon pos) c)  ;c est placé dans le tampon à la position pos
      (incf pos))         ;pos est incrémenté d'une unité
    (progn     ;sinon...
      (unless (zerop pos)   ;sauf si pos égale zéro
(intro-table (intern (subseq tampon 0 pos))) ;le mot constitué par l'itération est transformé en symbole et envoyé à la table de
(setf pos 0))                        ;hachage, puis pos est réinitialisée à zéro
      (let ((p (ponctuation c)))      ;le signe de ponctuation éventuel est traité
(if p (intro-table p)))))))))      ;et envoyé dans la table de hachage

La fonction générer-texte fait appel à la fonction aléa-suivant pour construire un nouveau texte:

(defun aléa-suivant (précédent) ;fonction déterminant le symbole qui suit précédent
  (let* ((liste-assoc (gethash précédent *mots*)) ;on récupère la liste associative de précédent dans la table de hachage
 (x (random (reduce #'+ liste-assoc :key #'cdr)))) ;x valeur aléatoire entre 0 et la somme des CDR  des éléments de liste-assoc
    (dolist (doublet liste-assoc)            ;itération sur tous les éléments de liste-assoc à l'aide de la variable doublet
      (if (minusp (decf x (cdr doublet))) ;si x moins la valeur du CDR de doublet devient négatif...
  (return (car doublet)))))) ;...la fonction rend le symbole contenu dans doublet (CAR) avec interruption de l'itération

(defun générer-texte (n &optional (précédent '| |)) ;on peut imposer le premier mot à afficher avec l'argument optionnel précédent
           (format t "~a " précédent) ;affichage du symbole sous la forme d'une chaine de caractères
  (if (zerop n) ;si n atteind zéro...
      (terpri) ;...arrêt de l'affichage
      (let ((néo (aléa-suivant précédent))) ;le prochain symbole est placé dans néo
(générer-texte (1- n) néo)))) ;récursion: précédent prend la valeur de néo

Il ne faut pas s'attendre à obtenir un texte généré parfait, loin de là.
Mais, parfois, on trouve un sens à certaines phrases.
Les guillemets sont reconnus, mais non traités.
N'oubliez pas que vous pouvez utiliser la fermeture INITIALISATION pour réinitialiser la table de hachage et recommencer.

Comme le résultat n'est pas, forcément satisfaisant, je vous propose une modification des deux dernières fonctions.
Vous aurez, ainsi, la possibilité de choisir le mot suivant au lieu de le calculer de façon aléatoire.

(defun suivant (précédent)
  (let ((liste-assoc (gethash précédent *mots*)) ;liste-assoc est la alist du symbole précédent
(i 0))              ;i est initialisé à zéro
    (dolist (doublet liste-assoc)  ;itération sur la variable doublet
      (format t "~a: ~a~%" i doublet)  ;affichage itératif d'un numéro et d'un doublet de liste-assoc
      (incf i))              ;incrémentation de i de une unité
    (format t "votre choix: ")  ;message affiché
    (let ((j (read)))      ;j prend la valeur attendue par read
      (if (< j (length liste-assoc))  ;si j est inférieure à la longueur de liste-assoc...
  (car (nth j liste-assoc))  ;...la fonction rend le symbole du jème élément de liste-assoc
  '|.|))))    ;sinon le symbole . est rendu, ce qui permet de terminer une phrase

Avec la fonction suivante, le texte généré est envoyé dans un fichier nouveau.txt déterminé par le pathname *néo-texte*:

(defun générer-texte2 (n pathname &optional (précédent '|.|))
  (with-open-file (flux2 pathname :direction :output :if-exists :supersede) ;un flux est ouvert en sortie vers le fichier .txt
    (if (gethash précédent *mots*)      ;si le symbole optionnel est dans la table de hachage...
(format flux2 "~a " précédent)      ;...le symbole précédent est inscrit dans le fichier sous la forme d'une chaine de caractères
(progn                ;sinon...
  (setf précédent '|.|)      ;...précédent reprend sa valeur par défaut
  (format flux2 "~a " précédent)))   ;et est inscrit dans le fichier
    (labels ((gén-text (n flux2 précédent)   ;définition d'une fonction récursive
(if (zerop n)      ;si n atteint zéro...
   (terpri)      ;fin de l'inscription
   (let ((néo (suivant précédent)))  ;néo prend le symbole rendu par la fonction suivant
     (format flux2 "~a " néo)      ;néo est inscrit dans le fichier
     (gén-text (1- n) flux2 néo))))) ;appel récursif à la fonction en décrémentant n
      (gén-text n flux2 précédent))))


Prochainement: LES PACKAGES.

jeudi 30 avril 2015

20-LES ERREURS et LES INTERRUPTIONS:

>>>>
Les erreurs et les interruptions.

Préparez-vous à vous opposer à la loi de Murphy, car soyez sûr que, si un événement hautement improbable peut arriver, il arrivera.
On peut penser, par exemple, à l'accès à un fichier qui n'existe pas sur le disque:
- s'il fait partie du programme, c'est un échec,
- s'il est demandé par l'utilisateur, c'est une autre situation exceptionnelle qu'il faut traiter de façon ad hoc (ce n'est pas un échec).
 ou encore à l'accès au disque alors qu'il est plein: il y a épuisement d'une ressource.
 ou encore à l'accès au serveur alors qu'il tombe en panne.
Une grande caractéristique de LISP est son système de "condition", plus flexible que les systèmes de traitement d'exception d'autres langages.
Cela va au delà du traitement des erreurs: on peut utiliser des conditions pour émettre un message d'alerte sans arrêter le programme.
Pour affiner ces notions, voyez:
http://dept-info.labri.u-bordeaux.fr/~idurand/enseignement/PFS/Book/node187.html
ou encore (en anglais):
http://www.gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts.html

ASSERT: (macro)

(defun imp (nb)
  (assert (numberp nb))
  (print nb))

(imp 45)

45
45 NB étant un nombre, il est affiché

(imp 'a)
(NB n'est pas un nombre: ouverture du débogueur)
The assertion (NUMBERP NB) failed with NB = A.
   [Condition of type SIMPLE-ERROR]

SIGNAL:      pour signaler une condition.

(defun imp (nb)
  (if (numberp nb)
      (print nb)
      (signal 'error)))

(imp 56)

56
56 pas d'erreur

(imp 'a)
NIL rend NIL si la condition n'est pas traitée

HANDLER-CASE: (macro) Pour traiter les conditions.

(handler-case (imp 'a)
  (condition () "Il y a un hic."))
"Il y a un hic."    


ERROR: provoque une interruption de la procédure, ouvre le débogueur et affiche le message d'erreur.
le message d'erreur se construit comme avec FORMAT.

(defun imprime (nb)
  (if (numberp nb)
      (print nb)
      (error "Cette valeur ~S n'est pas un nombre." nb)))
IMPRIME

(imprime 45)

45
45

(imprime 'a)

Cette valeur A n'est pas un nombre.
   [Condition of type SIMPLE-ERROR]
         ...etc, et en sortant du débogueur:

; Evaluation aborted on #.
      dans le débogueur ~S est remplacé par la valeur de nb.


IGNORE-ERRORS: permet d'indiquer une erreur sans ouvrir le débogueur.

(ignore-errors (imprime 'a))
NIL
#
      rend deux valeurs si une condition de type error est signalée: NIL et la condition signalée

Remarque: l'utilisation excessive de IGNORE-ERRORS ne permet pas d'améliorer la fiabilité d'un programme, puisque le débogueur n'est pas ouvert.
 A la place de IGNORE-ERRORS, on aurait pu écrire:

(handler-case (imprime 'a)
  (error (condition)
    (values nil condition)))
NIL
#
      on obtient le même résultat
      l'intérêt est qu'on peut spécifier un type de condition particulier plutôt que "error"

Cependant on peut vouloir absolument ignorer une erreur.
Rappeler-vous le prédicat o-ou-n-p, quand on tapait une virgule en guise de réponse on obtenait une erreur du reader:
#<SB-INT:SIMPLE-READER-ERROR "Comma not inside a backquote." {1002C68A63}>
Voici le prédicat corrigé:

(defun o-ou-n-p (question)
   "Affiche une question dont la réponse sera oui ou non, et attend une réponse:
rend T si la réponse est affirmative, NIL sinon."
   (setq question (concatenate 'string question " ")) ;pour la présentation de l'affichage
   (format t "~a" question) ;affichage de la question
   (let ((rép (ignore-errors (read)))) ;attente de la réponse
     (case rép ;traitement de la réponse
       ((o oui yes y ja) t)
       ((n non no nein) nil)
       (t (princ "Répondez par oui ou non svp. ") (o-ou-n-p question))))) ;cas d'une réponse inattendue

Notez que la fonction ignore-errors s'applique à (read) uniquement (et non au LET).
En effet, il s'agit d'une erreur du reader.

CERROR:     permet de proposer dans le débogueur un autre calcul plus correct quand une erreur est signalée.

(defun factorielle (x)
  (cond ((not (typep x 'integer))
 (error "~S n'est pas un entier." x))
((minusp x)
 (let ((opp-x (- x)))
   (cerror "Calculez plutôt: -(~D!)."
                         "La factorielle d'un entier négatif: (-~D)! n'a pas de sens." opp-x)
   (format t "- factorielle de ~D: " opp-x)
   (- (factorielle opp-x))))
((zerop x) 1)
(t (* x (factorielle (- x 1))))))

(factorielle -4)
- factorielle de 4:
-24      le calcul de (-4)! est transformé en -4! après passage par le débogueur

Autre exemple:

(defun divise (dividende diviseur)
  (cond ((or (not (numberp dividende)) (not (numberp diviseur)))
 (error "(Parmi les arguments '~S '~S) au moins un ne convient pas à la fonction DIVISE." dividende diviseur))
((zerop diviseur) (error 'division-by-zero :operator 'divise :operands (list dividende diviseur)))
(t (/ dividende diviseur))))

(divise 'a 5)
ouverture du débogueur:
(Parmi les arguments 'A '5) au moins un ne convient pas à la fonction DIVISE.
   [Condition of type SIMPLE-ERROR]

(divise 5 0)
ouverture du débogueur:
arithmetic error DIVISION-BY-ZERO signalled
   [Condition of type DIVISION-BY-ZERO]

Et, avec HANDLER-CASE:

(handler-case (divise 5 'a)
  (simple-error (condition)
    (values nil condition))
  (arithmetic-error (condition)
    (values nil condition)))
NIL
#

(handler-case (divise 5 0)
  (simple-error (condition)
    (values nil condition))
  (arithmetic-error (condition)
    (values nil condition)))
NIL
#

CATCH:
THROW:

(defun inverse (lst)
  "rend la liste des inverses."
  (catch 'arrêt
    (cond
      ((endp lst) nil)
      ((equal (car lst) 0) (throw 'arrêt "division par zéro interdite."))
      (t (cons (/ 1 (car lst)) (inverse (cdr lst)))))
    ))
INVERSE définit une fonction donnant la liste des inverses des nombres de la liste passée                                     en argument
                avec interruption si le nombre est égal à zéro

(inverse '(1 2 3 4 5))
(1 1/2 1/3 1/4 1/5) la procédure s'est effectuée normalement.

(inverse '(1 2 3 4 0 5))
(1 1/2 1/3 1/4 . "division par zéro interdite.")                  interruption avec un message d'erreur.

A la place d'une interruption et d'un message d'erreur, on peut demander un traitement particulier de la situation:

(defun inverse (lst)
  "rend la liste des inverses."
  (catch 'arrêt
    (cond
      ((endp lst) nil)
      ((equal (car lst) 0) (throw 'arrêt (cons "1/0" (inverse (cdr lst)))))
      (t (cons (/ 1 (car lst)) (inverse (cdr lst)))))))
STYLE-WARNING: redefining COMMON-LISP-USER::INVERSE in DEFUN
INVERSE        la définition de inverse a changé au niveau du THROW

(inverse '(1 2 3 0 4 5))
(1 1/2 1/3 "1/0" 1/4 1/5) on a un résultat insolite dans la liste, mais la récursion se poursuit.

Remarque: THROW peut être placé dans une autre fonction qui traitera les erreurs:

(defun traitement ()
  (throw 'arrêt "division par zéro interdite."))

(defun inverse (lst)
  "rend la liste des inverses."
  (catch 'arrêt
    (cond
      ((endp lst) nil)
      ((equal (car lst) 0) (traitement))
      (t (cons (/ 1 (car lst)) (inverse (cdr lst)))))
    ))

(inverse '(1 2 3 4 0 5))
(1 1/2 1/3 1/4 . "division par zéro interdite.")

Voir aussi BLOCK: et RETURN-FROM:.
A la différence de CATCH-THROW, RETURN-FROM doit être dans le BLOCK.

La prochaine fois: FONCTIONS et FERMETURES.