jeudi 13 novembre 2014

2-Les fonctions Common Lisp


Ce fichier sera complété peu à peu.
Il constitue un copyright de même que le fichier complet.
Vous pouvez l'utiliser pour un usage personnel (sans exploitation commerciale.).
Pour une bonne utilisation, il faut concaténer tous les articles dans un seul fichier.
... Et appliquer les recommandations ci-dessous.

Contact.
__________________

Merci à:
Peter Seibel (Practical Common Lisp).
Paul Graham  (ANSI Common Lisp).
Irène Durand (traité de programmation en Common Lisp).
Harald Wertz (LISP Une Introduction à la Programmation).
Guy L. Steele (Common Lisp the Language).
Francis Sergeraert (Packaging)


____________________
>>>>
Les fonctions Common Lisp.

Remarques pour l'utilisation de ce fichier:
C'est plutôt un aide mémoire pour débutant. Pour lire et rechercher dans ce fichier, il est conseillé de le charger dans EMACS (mode fundamental).
Concaténez chaque chapitre dans un seul fichier.
Il est utile de séparer chaque chapitre par: >>>>, pour faciliter les recherches (voir plus loin).
Et n'oubliez pas de mettre à jour vos fichiers si vous utilisez des copies de mes pages (elles peuvent évoluer!)

Pour écrire, copier-coller et essayer des fonctions, ouvrez un autre tampon sous EMACS:
Ctrl+x 2, puis Alt+x slime, pour être dans le mode REPL Autodoc.
Ctrl+x o, pour passer d'une fenêtre à l'autre.
(La dernière version publiée de SLIME est accessible sur le site: http://www.common-lisp.net/project/slime/)

Remarque: il se peut, après une mise à jour de Linux, que SLIME soit considéré comme obsolète et il ne sera pas chargé dans la liste des packages d'Emacs.
Je vous conseille alors de télécharger "Portacle" (pour PORTAble Common Lisp Environnement) à l'adresse suivante: https://portacle.github.io/
Lisez bien les consignes.
Après extraction du fichier téléchargé dans le répertoire perso qui vous agrée, vous allez dans portacle/lin/bin  et vous lancez le fichier portacle.
Emacs s'ouvre avec deux fenêtres: *scratch* et *slime-repl sbcl*.

Si vous voulez fermer une session SLIME-REPL, tapez une virgule, cela vous envoie dans le mini tampon.
Celui-ci attend une commande: tapez "quit", et le tampon SLIME se ferme. cela libère de la mémoire.
Mais attention, vous perdez tout votre travail dans SLIME!
Alors, faites vos sauvegardes avant:
Recopier vos fonctions (vos trouvailles préférées) dans un fichier-texte nom.lisp que vous pourrez recharger
avec (load "nom.lisp") à la prochaine session.

Voici quelques commandes d'EMACS:

Les commandes suivantes: permettent:

Ctrl+v        d'avancer d'une page
Alt+v         de reculer d'une page
Ctrl+n        de descendre d'une ligne
Ctrl+p        de remonter d'une ligne
Ctrl+s + vector         de faire une recherche descendante du mot "vector", et:
      Ctrl+s (ou Ctrl+r pour remonter)    d'aller sur une autre occurrence du mot "vector"
Ctrl+r + string          de faire une recherche remontante du mot "string", et:
      Ctrl+r (ou Ctrl+s pour redescendre)    d'aller sur une autre occurrence du mot "string"
Alt+< ou Ctrl+(Fn)+Début       d'aller au début du tampon
Ctrl+(Fn)+Fin d'aller à la fin du tampon ((Fn) est la touche de fonction sur certains ordinateurs)

Le mot à rechercher s'inscrit dans la ligne du bas (mini-buffer) et les occurrences peuvent être nombreuses.
Aussi, il peut être utile de taper ":" à la fin du mot recherché pour aller à la première définition de la fonction ou de la macro. Exemple: Ctrl+s DO:

Ce fichier contient de nombreux chapitres (une vingtaine): ils sont distingués par quatre symboles ">" à suivre (dans Emacs!).

Faites une recherche ainsi: Ctrl+s >>>> jusqu'à ce que vous trouviez le chapitre vous intéressant. Tapez alors ENTER pour rester ou travailler à cet endroit.

Pour retourner au tout début du texte, tapez: Ctrl+(Fn)+Début .
Vous pouvez, aussi, placer le fichier dans un traitement de texte muni d'une fonction "recherche".
Le contenu n'étant pas exhaustif, il est très recommandé de faire des recherches sur Internet.
En particulier, dans la fenêtre *slime-repl sbcl*, si vous positionnez le curseur sur le nom d'une fonction, puis tapez Ctrl+c Ctrl+d h, vous ouvrez la page HyperSpec concernant la dite fonction.
(Ceci ne semble pas fonctionner avec Portacle)

Dès à présent, vous vous ouvrez une des portes vers l'IA.
A ce propos: Si vous vous lancez dans ce domaine (IA), pensez en priorité à l'éthique.
Une bonne IA doit tenir compte de l'éthique qui varie d'un pays à l'autre et dans le temps.
C'est un incontournable si vous voulez que votre IA évolue avec "son temps", avec les Etats et leurs lois.
(Lire Aurélie Jean: éthique et intelligence artificielle sont elles compatibles?)
Si vous travaillez en entreprise dans l'IA, pour l'appliquer en Europe, renseignez-vous sur le RGPD (règlement général sur la protection des données).
https://www.economie.gouv.fr/entreprises/reglement-general-sur-protection-des-donnees-rgpd

https://www.cnil.fr/fr/principes-cles/rgpd-se-preparer-en-6-etapes
_____________________
POUR COMMENCER:

Voici une liste de symboles rencontrés dans ce chapitre:


&ALLOW-OTHER-KEY &KEY &OPTIONAL &REST 1+ 1- = AND APPEND APPLY BLOCK BOUNDP BUTLAST C-SUPPLIED-P CAR CASE
CDR CHAR= COERCE COND CONS COPY-LIST COPY-SEQ DECF DEFCONSTANT DEFPARAMETER DEFUN DEFVAR DO DOLIST DOTIMES
ECASE ENDP EQ EQL EQUAL EQUALP EVAL FIRST FLET FUNCALL FUNCTION GETF GO IF INCF LABELS LAMBDA LAST LDIFF
LENGTH LET LET* LIST LIST* LIST-LENGTH MACROLET MAKE-LIST MULTIPLE-VALUE-BIND MULTIPLE-VALUE-CALL
MULTIPLE-VALUE-LIST MULTIPLE-VALUE-PROG1 MULTIPLE-VALUES-LIMIT NBUTLAST NCONC NIL NOT NRECONC
NTH NTHCDR OR POP PROG1 PROG2 PROGN PUSH PUSHNEW QUOTE READ READ-CHAR REST RETURN-FROM REVAPPEND
REVERSE ROTATEF RPLACA RPLACD SET SETF SETQ SHIFTF STRING-EQUAL T TAGBODY TYPECASE UNLESS
UNWIND-PROTECT VALUES VALUES-LIST VECTOR VECTORP WHEN WRITE

Je me répète un peu: vous chercher quelques infos sur BOUNDP, tapez Ctrl+s et "boundp:" (sans les guillemets").
Ne pas oublier le ":" à la fin du symbole!
Si d'autres occurences de "boundp" existent, vous pouvez retaper Ctrl+s (ou Ctrl+r pour remonter dans le texte)

Vous pouvez aussi double-cliquer sur un mot de la liste ci-dessus (ou sur un trait d'union s'il en possède un).
Ensuite vous tapez: M-w C-s C-y et :
M-w permet de copier le mot sélectionné (ou Alt-w suivant le type de clavier)
C-s commande une recherche descendante.
C-y colle le mot dans le mini-tampon.
: permet d'aller plus rapidemment à la définition qui nous intéresse.
(répéter C-s s'il y a plusieurs occurences, ou C-r pour remonter dans le texte)
C'est, peut-être, difficile quand on débute, mais on s'y fait très vite! C'est très pratique.

Certaines fonctions définies plus loin ne traitent pas toutes les erreurs possibles, mais il ne faut pas tout compliquer dès le début.

QUOTE: 1er opérateur primitif
(il y a 7 opérateurs primitifs: quote, atom, eq, car, cdr, cons et cond. Et il est recommandé de faire une recherche sur ces opérateurs:  Ctrl+s)

(quote a)
a      retourne a sans évaluation

Notez que lorsque vous commencez à taper (quote dans le REPL une aide apparait dans le mini-tampon:
(quote thing)
quote est une fonction qui "attend" quelque chose: un et un seul argument!
La chose (l'argument) que vous tapez peut être quelconque (un symbole, un caractère, une chaîne de caractères, une liste, un vexteur,...)

(quote (1 2 3))
(1 2 3) retourne la liste sans l'évaluer

'(1 2 3)
(1 2 3) idem

L'opérateur quote sert tellement qu'on lui a inventé un raccourci: '.
Mais il n'y a pas d'aide dans le mini-tampon: vous êtes sensé savoir ce que vous faites.

D'ailleurs (1 2 3), sans quote, renvoie une erreur car Clisp évalue d'abord le 1er terme de la liste qui doit être une fonction.
1 n'est pas une fonction donc erreur.
De même pour (a b c), sans quote, si a n'est pas défini comme une fonction, on obtient une erreur.

Attention: si vous faites un copier-coller contenant ' à partir d'un fichier-texte, vous pouvez tomber sur un symbole proche qui ne sera pas interprété correctement dans clisp.
Vous devrez effacer et retaper le symbole dans SLIME.

NIL:
():
T:
() ou nil exprime la liste vide. C'est aussi une valeur de vérité: faux.
tout ce qui n'est pas nil est T. T est aussi une valeur de vérité: vrai.
voir le prédicat NULL (null:)
voir aussi l'opérateur logique NOT (not:)
t est aussi un type (même NIL est du type t)

(typep nil 't)
T
Voir: Ctrl+s typep: (chapitre 17)

'()
NIL

't
T



EVAL:

(eval '(+ 1 2 3))
6     force l'évaluation si possible, sinon erreur

(eval '(1 2 3)) renvoie, donc, une erreur.

Remarque: faites la différence entre (eval '(+ 1 2 3)) et (eval (+ 1 2 3)).
Le premier évalue la s-expression (+ 1 2 3).
le deuxième évalue 6 (le résultat de la s-expression)

CONS: opérateur primitif n°6
construit un doublet de champs (appelé cellule cons ou paire).

(cons 'c 'd)
(C . D) rend une liste pointée ou doublet dont le 1er champ contient C et le 2ème contient D

(cons 'a '(b c d))
(A B C D) rend une liste construite avec un élément (1er champ) et une liste (2ème champ)

(cons 'a nil)
(A)   cas particulier du cas précédent où la liste est nil ou ()

(cons 'b (cons 'c 'd))
(B C . D) ou bien:

LIST*:

(list* 'b 'c 'd)
(B C . D)      rend une liste dont le dernier champ du dernier doublet est D et non NIL
                     LENGTH n'est pas définie pour ce type de liste (voir plus loin LENGTH:)

Les deux arguments de CONS  peuvent être  des atom(s) ou des listes (quotées).
Les atomes sont des symboles (quotés), des caractères ou des chaînes de caractères (pas nécessairement quotées), des vecteurs (pas nécessairement quotés)...
Essayez pour voir!

CAR: 4ème opérateur primitif
pointe sur le 1er champ d'une cellule et rend son contenu.
(Contenu Adress Registre)
FIRST:    comme CAR.

(car '(a . b))
A       rend le 1er élément de la liste pointée
remarquez la présence de ' avant la liste (sinon erreur-voir "quote")

(car '(a b c))
A rend le 1er élément de la liste

(first '(a b c))
A

(car nil)
NIL rend NIL si la liste est vide

CDR: opérateur primitif n°5
(prononcer "queudeur") pointe sur le 2ème champ d'une cellule et rend son contenu.
On peut dire qu'une cellule comporte deux champs, un CAR et un CDR que l'on sépare par un point: (CAR.CDR)
(Contenu Decrement Registre)
REST:     comme CDR

(cdr '(A . B))
B rend le 2ème élément de la cellule (ou liste pointée)

(cdr '(a b c))
(B C) rend la liste sans le 1er élément

(rest '(a b c))
(B C) idem

(cdr nil)
NIL rend NIL si la liste est vide

MAKE-LIST:

(make-list 3 :initial-element 'ha)
(HA HA HA) liste de 3 éléments initialisés avec HA

APPEND:
NCONC:

(append '(a b c) '(1 2))
(A B C 1 2) Concatène les listes passées en arguments sans destruction

Pour des raisons d'efficacité (récupération de cellules), on préfère utiliser NCONC qui fait la même chose avec destruction des listes:

(defparameter *liste* '(a b c))
*LISTE*      définit une liste

*liste*
(A B C) rend la valeur de la liste selon la définition précédente

(nconc *liste* '(d e f))
(A B C D E F)     concatène les listes, comme APPEND, mais:

*liste*
(A B C D E F)        la liste a été détruite
                         dans la programmation, il faudra s'assurer qu'on n'a plus besoin de                                        cette liste initiale

L'effet destructeur des fonctions peut aller jusqu'à transformer une fonction.
Exemple:

(defun echo () '(e c h o))
ECHO            cette fonction devrait toujours rendre la liste (E C H O)

(echo)
(E C H O) comme la fonction rend une liste, on pourrait faire:

(nconc (echo) '(c h o))
(E C H O C H O)

(echo)

(E C H O C H O) La fonction a été transformée!

Il est préférable, voire fortement conseillé, de ne pas utiliser QUOTE (') dans la définition d'une fonction.
on écrira plutôt:

(defun echo () (list 'e 'c 'h 'o))
       Ainsi, la fonction ECHO ne sera pas transformée en utilisant NCONC. Essayez!

COPY-LIST:

(copy-list '(a b c))
(A B C)   rend une liste equal, mais pas eq
          les pointeurs ne sont pas les mêmes

LIST:

(list 1 2 3)
(1 2 3)      crée une liste (les nombres sont auto-évalués)...

(list 'a 'b 'c)
(A B C)     ...pas les lettres

En fait, une liste (A B C) est l'écriture simplifiée d'une suite de cellules: ( A . (B . (C . nil)))
où (C . nil) est équivalent à (C).

(list '(a b c) '(1 2) 'd)
((A B C) (1 2) D)       liste de listes ou d'atomes.
                                   (voir atom:)

(list :a 1 :b 2)
(:a 1 :b 2)      crée une plist
                         (voir alist:)

LENGTH:
LIST-LENGTH:

(list-length '(0 1 2 3))
4                                       renvoie NIL si la liste est circulaire

(length '(0 1 2 3))
4                                      cette fonction boucle si la liste est circulaire

(length "arbre")
5

(length #(a b c d))
4


Les listes construites avec LIST sont appelées listes "propres" (appropriée, vraie).
Une liste propre est soit NIL, soit un doublet (cellule cons) dont le CDR est une liste propre.
Avec cette définition, on peut proposer un prédicat pour vérifier si une liste est propre:

(defun liste-propre? (l)
  "rend T si l'argument est une liste propre, NIL sinon."
  (or (null l)
      (and (consp l)
   (liste-propre? (cdr l)))))

(liste-propre? '(a b c))
T (a b c) est bien une liste propre

(liste-propre? '(a b . c))
NIL (a b . c) n'est pas une liste propre

Attention! Le prédicat précédent ne marche pas pour les listes circulaires.
Au lieu de répondre NIL, il ne s'arrêtera pas.
Un doublet qui n'est pas une liste propre est dit "liste pointée" par abus de langage.
En effet:

'(a . (b . (c . nil)))
(A B C) ceci est bien une liste propre
      LISP fait systématiquement la conversion

(liste-propre? '((a . b) (c . d) (e . f)))
T              liste propre dont les éléments sont des doublets simples
une liste propre peut contenir des listes non-propres
(voir alist:)

(defun liste-point (lst)
  "Développe une liste propre sous la forme pointée."
  (if (liste-propre? lst)
      (let ((tab (make-list (length lst) :initial-element ")")))
(do ((i 0 (1+ i)))
    ((>= i (length lst)))
  (if (atom (nth i lst))
      (format t "(~a . " (nth i lst))
      (progn (format t "(") (liste-point (nth i lst)) (format t " . "))))
(format t "NIL")
(do ((j 0 (1+ j)))
    ((>= j (length lst)))
  (format t "~a" (nth j tab))))
      (error "La liste ou une sous-liste n'est pas propre.")))

GETF:

(getf (list :a 1 :b 3) :b)
3        retourne la valeur de l'enregistrement :b

ENDP:           (souvent utilisé à la place de NULL pour savoir si on arrive en fin de liste)

(endp '(1 2 3))
NIL

(endp ())
T

VECTOR:

(vector 1 2 3)
#(1 2 3)      rend un vecteur dont les éléments sont les 3 arguments

VECTORP:

(vectorp #(1 2 3))
T rend T si l'argument est bien un vecteur

COERCE:

(coerce '(a b c) 'vector)
#(A B C)      transforme une liste en vecteur

(coerce #(a b c) 'list)
(A B C)       transforme un vecteur en liste

(coerce 4/3 'float)
1.3333334 transforme un rationnel en flottant
ou (float 4/3)

(coerce 4/3 'double-float)

1.3333333333333333d0

(coerce "a" 'character)
#\a       ... Mais tout n'est pas possible!

Pour ce qui suit: (voir le chapitre sur les symboles)

(defun liste-symboles-p (liste)
   "Vérifie si tous les éléments de liste sont des symboles."
   (if (endp liste) t
       (if (symbolp (car liste))
   (liste-symboles-p (cdr liste))
   nil)))

(liste-symboles-p '(foo bar 2))
NIL

(liste-symboles-p '(foo bar deux))
T

Les nombres ne sont pas des symboles, sauf s'ils sont écris en toutes lettres.

...Et élargir le champ d'action de coerce:

(defun coerce-list-chaîne (liste)
   "Rend une chaîne formée des noms des symboles d'une liste."
   (if (liste-symboles-p liste)
       (labels ((concat (l)
       (if (endp l) ""
   (concatenate 'string (symbol-name (car l)) " " (concat (cdr l))))))
       (string-capitalize (string-downcase (concat liste)) :start 0 :end 1))
       (error "Votre liste n'est pas une liste de symboles.")))

(coerce-list-chaîne '(les arbres sont bien verts cet été|,| mais ils vont jaunir en automne.))
"Les arbres sont bien verts cet été, mais ils vont jaunir en automne. "

Notez que l'argument est une liste de symboles et comme la virgule joue un rôle particulier dans les macros,
elle est encadrée par deux barres verticales. Le point ne pose pas de problème: il fait partie du symbole 'automne.' .
Il reste un petit problème: l'espace entre le point final et le guillemet.
Je vous propose cette seconde version:

(defun coerce-list-chaîne (liste)
   "Rend une chaîne formée des noms des symboles d'une liste."
   (if (liste-symboles-p liste)
       (labels ((concat (l)
       (if (endp l) ""
   (concatenate 'string (symbol-name (car l))
(if (endp (cdr l)) "."
    " ")
(concat (cdr l))))))
       (string-capitalize (string-downcase (concat liste)) :start 0 :end 1))
       (error "Votre liste n'est pas une liste de symboles.")))

(coerce-list-chaîne '(les arbres sont bien verts cet été|,| mais il vont jaunir en automne))
"LLes arbres sont bien verts cet été, mais il vont jaunir en automne."

Il n'y a plus d'espace et le point final est ajouté alors qu'il n'est pas dans la liste.

VALUES:

(values 1 2 3 'a :b "coucou")
1
2
3
A
:B
"coucou"          rend les valeurs des arguments à suivre

((lambda () ((lambda () (values 'a 'b)))))
A
B les valeurs passées par VALUES passent à travers les retours de fonctions
ici: return implicite dans LAMBDA

Mais:

(let ((x (values 'a 'b))) x)
A     quand il n'y a qu'une valeur demandée, seule la première valeur passe

Il est possible de ne rendre aucune valeur:

(values)
; No value

(let ((x (values))) x)
NIL      dans ce cas la fonction rend NIL

VALUES-LIST:

(values-list '(1 2))
1
2            rend les éléments de la liste passée en argument à suivre

MULTIPLE-VALUE-LIST:

(multiple-value-list (values 1 2))
(1 2)        rend la liste des éléments passés avec VALUES

(multiple-value-list (values-list '(1 2 3)))
(1 2 3)      de même avec toute fonction rendant une suite de résultats

(multiple-value-list (get-decoded-time))
(18 29 19 4 6 2014 2 T -1)
                 rend la liste des valeurs rendues par GET-DECODED-TIME:
                       (secondes minutes heures jour mois année jour/sem été? zone)

MULTIPLE-VALUE-BIND:

(multiple-value-bind (x y z) (values 2 3 4)
  (/ (+ x y) z))
5/4       définit localement 3 variables dont les valeurs sont passées par une fonction
donnant des valeurs multiples
puis évalue l'expression qui est fonction des 3 variables et rend le résultat

(multiple-value-bind (x y z) (values 2 3)
  (/ (+ x y) z))
; in: MULTIPLE-VALUE-BIND (X Y Z)
;     (/ (+ X Y) Z)
;
; caught WARNING:
;   Derived type of Z is
;     (VALUES NULL &OPTIONAL),
;   conflicting with its asserted type
;     NUMBER.     il n'y a que deux valeurs passées pour trois variables définies:
      z prend la valeur NIL qui n'est pas un nombre

L'erreur ne vient que du calcul demandé car on ne peut pas diviser par NIL.
En fait multiple-value-bind prend bien en compte toutes les valeurs passées à values:

(multiple-value-bind (x y z) (values 2 3)
           (list x y z))
(2 3 NIL)

S'il y a plus de valeurs que de variables, les valeurs en trop sont ignorées.
Et les variables correspondantes prennent la valeur NIL.

MULTIPLE-VALUES-LIMIT: variable indiquant la limite du nombre de valeurs multiples possibles.

multiple-values-limit
4611686018427387903     C'est beaucoup! N'est-ce-pas?

DUREES:

Dans ce qui suit, *chemin* est une variable qui doit contenir le chemin et le nom du fichier.

(decode-universal-time (file-write-date *chemin*)) decode le temps passé en argument
16         secondes
28 minutes
18 heures
19 date
3 mois
2014 année
2 jour de la semaine (ici, mercredi: 0 pour lundi)
NIL ce n'est pas l'heure d'été
-1 numéro de zone horaire

Autre exemple d'utilisation de MULTIPLE-VALUE-BIND:

(defun date-du-lieu ()
   "rend des informations concernant la date et l'heure du lieu où se trouve l'ordinateur"
   (let ((alist '((0 . "lundi") (1 . "mardi") (2 . "mercredi") (3 . "jeudi") (4 . "vendredi") (5 . "samedi") (6 . "dimanche"))))
     (multiple-value-bind
       (secondes minutes heures date mois année jour été zone)
     (get-decoded-time)
   (format t "~&Nous sommes le ~a/~a/~a, il est ~a heure(s), ~a minute(s), ~a seconde(s).~%Jour: ~a.~%Heure d'été: ~:[non~;oui~].~%Zone horaire: ~a." date mois année heures minutes secondes (cdr (assoc jour alist)) été zone))))
DATE-DU-LIEU

(date-du-lieu)
Nous sommes le 18/5/2014, il est 15 heure(s), 36 minute(s), 3 seconde(s).
Jour: dimanche.
Heure d'été: oui.
Zone horaire: -1.
NIL

MULTIPLE-VALUE-CALL:

(multiple-value-call #'* (values 2 3 5))
30     fournit à la fonction passée en 1er argument les valeurs multiples d'une autre fonction
    puis rend le résultat

Remarque: MULTIPLE-VALUE-CALL avec #'list en 1er argument, c'est MULTIPLE-VALUE-LIST.

REVERSE:

(reverse '(a b c))
(C B A)       rend la liste du dernier élément au premier

(reverse "abcde")
"edcba"        la chaîne est inversée

(reverse #(1 2 3))
#(3 2 1)        le vecteur est inversé

REVAPPEND:

(revappend '(1 2 3) '(a b c))
(3 2 1 A B C)      équivaut à (append (reverse '()) '()) en plus efficace
                                       si x est la première liste, elle n'est pas détruite

NRECONC:

(nreconc '(1 2 3) '(a b c))
(3 2 1 A B C)       même chose, avec destruction et effet de bord indésirable

COPY-SEQ:

(copy-seq '(a b c))
(A B C)          rend une copie de la séquence

NTH:

(nth 0 '(a b c))
A            On compte à partir de zéro (identique à (first '( a b c))
                          ou à (car '(a b c))

(nth 1 '(a b c))
B           identique à (second '(a b c)) ou à (cadr '(a b c))

(nth 2 '(a b c))
C         ...third...caddr...

(nth 3 '(a b c))
NIL      Il n'y a pas de 4ième élément

Il y a aussi: fourth, fifth, sixth, seventh, eighth, ninth et tenth.

LAST:  (retourne le dernier doublet de la liste)

(last '(a b c))
(C)        attention, c'est une liste

(last '(a b c . d))
(C . D) quand le deuxième champ du dernier doublet n'est pas NIL

(car (last '(a b c)))
C         dernier élément

LAST peut avoir un deuxième argument:
(last '(a b c) 2)
(B C)       rend la liste des deux derniers éléments
                (le deuxième argument peut être plus grand, mais s'il est plus grand que la longueur de la liste, la liste entière est rendue)
                              attention, ce n'est pas la même chose que:

(cdr '(a b c))
(B C)       rend la liste sans le premier élément (idem: (rest '(a b c)))

(eq (last '(a b c) 2) (cdr '(a b c))) >>    NIL par contre:
(defparameter *l* '(a b c))          >>    *L* pour ce qui suit:
(eq (last *l* 2) (cdr *l*))       >>    T

NTHCDR:

(nthcdr 6 '(a b c d e f g h i))
(G H I)      rend le 6ième cdr de la liste

(car (nthcdr 6 '(a b c d e f g h i)))
G           rend le 7ième élément de la liste (rang 6)

(nthcdr 0 '(a b c d))
(A B C D)          rend la même liste

RPLACA:  RemPLAce à la position définie par le 1er argument, le CAr de ce 1er argument (liste) par le 2ème argument. Puis rend ce 1er argument modifié.

(rplaca (cdr *l*) 'g)
(G C) rend le CDR de la liste *L* (1er argument) en remplaçant, toutefois, son premier élément par le 2ème argument. Si *l* valait (A B C), cette liste devient (A G C). Le 1er argument indique la position où a lieu le changement.

*l*
(A G C)

RPLACD:  RemPLAce à la position définie par le 1er argument, le CDr de ce 1er argument (liste) par le 2ème argument. Puis rend ce 1er argument modifié.

(rplacd (cdr *l*) (list 'e 'f))
(G E F) rend le CDR de *l* (1er argument), en remplaçant, toutefois, son CDR par le 2ème argument. Si *l* valait (A G C), cette liste devient (A G E F). Le 1er argument indique la position où a lieu le changement.

*l*
(A G E F)

(rplacd (last L)(list 'd))
(C D)     si L = (A B C), L devient (A B C D). Donc ajoute un élément
                             à la fin d'une liste
(voir aussi setf:)

Voici une fonction qui introduit un élément à une position donnée dans une liste fournie.
La position, l'élément et la liste sont fournies en arguments.

(defun poser (n el lst)
  "rend une liste en insérant le 2ème argument dans la liste du 3ème argument à la position indiquée par le 1er argument.La liste initiale est conservée."
  (if (integerp n)
      (if (listp lst)
          (if (and (<= n (length lst)) (>= n 0))
              (append (ldiff lst (nthcdr n lst)) (cons el (nthcdr n lst)))
              nil)
          (error "Le troisième argument doit être une liste."))
      (error "Le premier argument doit être un nombre entier.")))

(poser 2 'x '(a b c d e))
(A B X C D E) X est introduit en place 2 de la liste (A B C D E)

Une petite modification de la fonction précédente permet de remplacer un élément de la liste par un autre à une position donnée:
(defun rplac (n el lst)
       ...
       cherchez ce qu'il faut modifier et essayez!

LET:
LET*:

(let (x y z)
  ...) définit des variables x, y, z, qui prendront leur valeur plus loin dans le LET.

(let ((x 3) y)
  (setf y (* 5 x)))
15   x est initialisée à 3, y est définie, mais prend sa valeur plus loin dans le LET
  voir aussi LET*

(let ((x 10)) x )
10 définit une variable x et l'initialise à la valeur 10,
même si x a une autre valeur à l'extérieur du let

(let* ((x 10)
(y (+ x 10)))
   (list x y))
(10 20)             les arguments dans LET* peuvent faire référence aux
                    arguments précédents (y fonction de x). Pas avec LET.

La même expression avec LET n'a de sens que si x est définit avant:

(defparameter x 30)

(let ((x 10)
(y (+ x 10)))
  (list x y))
(10 40)   y est calculé avec la valeur de x en dehors du LET!

Compte-tenu de ce qui vient d'être dit, amusons-nous un peu:

(let ((x 3) (y (+ x 10)))        x et y sont tous les deux définis dans le let.
       (print (list x y)) y est calculé avec la valeur de x à l'extérieur du let. Print affiche la liste (x y).
       (setf y (+ x 10)) y est calculé avec la valeur de x dans le let.
       (print (list x y))) Print affiche la liste (x y).

(3 40)        x=3 et y=30+10
(3 13)        x=3 et y=3+10

(3 13)        Lisp affiche la valeur de la dernière forme calculée

Dans le cas des LET imbriqués, la valeur liée à une variable est celle définie par le LET "intérieur" le plus proche.
Considérons, par exemple, trois variables A, B et C auxquelles sont liées des valeurs dans les LET imbriqués suivants:

(let ((a 1) (b 2) (c 3))
  (let ((a 4) (b 5))
    (let ((a 6))
      (print (list a b c)))
    (print (list a b c)))
  (print (list a b c)))

(6 5 3)    A prend sa valeur dans le LET le plus intérieur (le 3ème), B dans le LET de niveau                       supérieur (le 2ème) et C dans le 1er 
(4 5 3)  à la sortie du 3ème LET, A et B prennent leurs valeurs dans le 2ème LET, actuellement le                  plus intérieur et C dans le 1er
(1 2 3)  à la sortie du 2ème LET, A, B et C prennent leurs valeurs dans le 1er LET
(1 2 3)  LISP rend la valeur de la dernière forme calculée dans la procédure

       PRINT, qui sera vu plus loin, sert, ici, à afficher les résultats intermédiaires

=:
char=:
eq:
eql:
string-equal:

(= 3 3 3)
T          teste l'égalité des nombres passés en argument

(char= #\a #\a #\a)
T         teste l'égalité des caractères passés en argument

(eq objet1 objet2)
T ou NIL        teste si les objets sont identiques (le comportement
                         peut être différent suivant l'implémentation.

eq est l'opérateur primitif n°3.

En fait EQ rend T si ses deux arguments sont le même atome ou pointent sur la même liste, et rend NIL si ce sont des atomes différents ou s'ils ne pointent pas sur la même liste:

(eq 3 3)
T teste si 2 nombres sont égaux, mais:

(eq 1 1.0)
NIL ici, les deux nombres ne sont pas du même type (donc 2 atomes différents)
voir aussi le cas où les nombres deviennent des BIGNUM

(eq 'a 'a)
T les deux atomes sont les mêmes

(eq '(a b) '(a b))
NIL       rend NIL si l'un des arguments, au moins, est une liste
      avec la nuance suivante:

(defparameter *l* '(b c))
*L*      définition d'une liste *L* pour ce qui suit:

(eq *l* *l*)
T les deux arguments pointent sur la même liste

(eq 'b (car *l*))
T rend T, car (car *l*) est bien un atome égal à l'atome B

(defparameter *l1* (cons 'a *l*))
*L1*      définition d'une liste *L1* (où A est "conser" avec la liste *L*) pour ce qui                         suit:

(eq (car *l*) (cadr *l1*))
T       (car *l*) et (cadr *l1*) rendent deux atomes égaux

(eq *l* (cdr *l1*))

T *l* et (cdr *l1*) pointent sur la même liste, mais:

(eq '(b c) (cdr *l1*))
NIL   EQ ne compare pas les listes et rend NIL même si:

(cdr *l1*)
(B C) en fait, '(B C) et (cdr *l1*) ne pointent pas sur la même liste

Par contre:
(cdr *l1*) pointe sur *l*, puisque le premier élément de *l1* a été consé à *l*:

(eq (cdr *l1*) *l*)
T


et aussi:

(equal '(b c) (cdr *l1*))

T            les deux listes ont le même contenu (voir plus loin equal:)

(eql 1 1.0)
NIL             teste si les arguments sont de la même classe et de
                    mêmes valeurs

(eql '(1 2 3) (cdr '(4 1 2 3)))
NIL          le contenu est le même, pas la structure.

(string-equal "abc" "abc")
T teste l'égalité de deux chaines

(string-equal 'x "x")
T admet la conversion de symboles sans tenir compte de la casse

Exemple: voici deux fonctions. La première calcule la factorielle d'un nombre entier positif, et la deuxième calcule la valeur de l'entier n pour laquelle la factorielle devient un bignum.

(defun fact (n)
  "rend la factorielle de l'entier naturel n"
  (if (and (integerp n) (>= n 0))
      (if (< n 2)
  1
  (* n (fact (1- n))))
      (error "l'argument de la fonction FACT doit être un entier naturel positif")))

(defun fact-first-bignum (n)
  "rend le plus petit entier dont la factorielle est un bignum"
  (if (eq (fact n) (fact n)) (fact-first-bignum (1+ n))
      n))

Appel:

(fact-first-bignum 0)
21 (fact 21) est un bignum (suivant votre ordinateur, le résultat peut être différent)

(defun nb-bit-prim (n)
  "Rend le nombre de bits utilisés dans l'implémentation des entiers primitifs."
  (if (eq (expt 2 n) (expt 2 n)) (nb-bit-prim (1+ n))
      n))

Appel:

(nb-bit-prim 0)
62 Les entiers primitifs sont implémentés sur 62 bits
       (attention! 2^61 est représenté sur 62 bits)
          (suivant votre ordinateur, le résultat peut être différent)

Autre version: (voir ash:)

(defun nb-bits-prim (n)
  "Rend le nombre de bits utilisés dans l'implémentation des entiers primitifs."
  (if (eq (ash 1 n) (ash 1 n)) (nb-bits-prim (1+ n))
      n))

Appel:

(nb-bits-prim 0)
62 Les entiers primitifs sont implémentés sur 62 bits
      (attention! 2^61 est représenté sur 62 bits)
         (suivant votre ordinateur, le résultat peut être différent)

Remarque: (expt 2 0) vaut 1 et (ash 1 0) vaut 1 (ici n=0 et le résultat est sur 1 bit. (ash 1 62) est un bignum, mais (ash 1 61) est sur 62 bits)

EQUAL:           teste l'évaluation des 2 arguments.
EQUALP:

____________________
On pourrait définir EQUAL à partir de EQ de la façon suivante:

(1ère version)
(defun equal1 (arg1 arg2)
  (if (atom arg1)
      (eq arg1 arg2)
      (if (atom arg2)
  nil
  (if (eq (car arg1) (car arg2))
      (equal1 (cdr arg1) (cdr arg2))
      nil))))

(equal1 '(a b c d) '(a b c d))
T rend T s'il n'y a que des atomes dans les 2 listes, mais:

(equal1 '(a b (c d)) '(a b (c d)))
NIL rend NIL car il y a des sous-listes et à cause du test (eq (car arg1) (car arg2)) qui rend NIL
dès que (car arg1) est une liste.
Il suffit de remplacer ce test par: (equal1 (car arg1) (car arg2))
donc un 2ème appel récursif, d'où:

(version 2)
(defun equal2 (arg1 arg2)
  (if (atom arg1)
      (eq arg1 arg2)
      (if (atom arg2)
  nil
  (if (equal2 (car arg1) (car arg2))
      (equal2 (cdr arg1) (cdr arg2))
      nil))))

(equal2 '(a b (c d)) '(a b (c d)))
T cette fois les 2 listes (avec sous-listes) sont EQUAL

Les IFs imbriqués peuvent être, avantageusement, remplacés par la macro COND (voir cond: )
D'où:

(version 3)
(defun equal3 (arg1 arg2)
  (cond
    ((atom arg1) (eq arg1 arg2))
    ((atom arg2) nil)
    ((equal3 (car arg1) (car arg2)) (equal3 (cdr arg1) (cdr arg2)))))

____________________

(equal '(1 2 3) (cdr '(4 1 2 3)))
T                 teste l'identité de façon moins stricte que EQL

(equal 1 1.0)
NIL            les 2 nombres ne sont pas de la même classe

(equalp 1 1.0)
T           les 2 nombres sont équivalents, même s'ils ne sont pas
               de la même classe (moins strict que equal)

(equal (vector 1 2) (vector 1 2))
NIL           les 2 vecteurs ne sont pas "equal"...
(equalp (vector 1 2) (vector 1 2))
T       ...mais ils sont "equalp"

(equal '(1 . 2) '(1 . 2))
T égalité de deux cellules allouées par read

(equal (cons 1 2) (cons 1 2))
T égalité de deux cellules allouées par eval

(equal (cons 1 2) '(1 . 2))
T égalité de deux cellules allouées par eval et read

Bien voir la différence entre eql (test physique) et equal (test logique):
Comparons deux doublets construits de façon identiques.

(eql (cons 'a nil) (cons 'a nil))
NIL Les deux doublets sont construits successivement
Les pointeurs sur A sont donc différents, de même sur NIL.
les doublets sont donc physiquement différents.

Par contre:

(equal (cons 'a nil) (cons 'a nil))
T On compare, ici, uniquement les valeurs des deux doublets
Ils sont logiquement égaux.

DEFUN: (macro)
&optional:
c-supplied-p:

(defun nom (param)...
NOM          permet de définir une fonction appelée NOM.

Rem: suivant l'implémentation lisp, on utilise: defun, define, def ou de.

DEFUN rend le nom de la fonction.

DEFUN associe (ou modifie une association) un symbole (nom) à une fonction.

(defun foo (a b &optional (c 10) d) (list a b c d))
FOO          les paramètres a et b sont requis, à l'appel,
                    c et d sont optionnels

(foo 1 2 3)
(1 2 3 NIL)           exemple, avec la définition précédente

(foo 1 2)
(1 2 10 NIL)        autre exemple où c prend la valeur par défaut.


 La valeur par défaut, peut dépendre des paramètres précédents: dans la définition de FOO, on pourrait mettre (c (+ a (* b b))) à la place de (c 10):

(defun foo (a b &optional (c (+ a (* b b)) c-supplied-p) d)   (list a b c d c-supplied-p))
             c-supplied-p permet de voir si le paramètre c a été fourni

(foo 1 2)
(1 2 5 NIL NIL)       c et d non fournis

(foo 1 2 3)
(1 2 3 NIL T)          c fourni et d non fourni

Remarque: si le paramètre c n'est pas fourni, il prend la valeur par défaut et c-supplied-p prend la valeur NIL.
Si le paramètre c est fourni, c-supplied-p prend la valeur T.
S'il n'y a pas de valeur par défaut, la variable c-supplied-p n'est pas définie et cela provoque une erreur.

Peut-être êtes-vous surpris par l'ordre des indications (d avant c).
Pourtant, d est affiché en 4ème position qu'il soit fourni ou qu'il ne le soit pas (NIL).
Par contre, c (qui a une valeur optionnelle) est toujours affiché en 3ème position:
L'indicateur (T ou NIL) apparait donc en 5ème position.
Il faut donc bien savoir ce que l'on fait.

La valeur par défaut d'un paramètre optionnel peut être un texte.
Exemple:

(defun foo (a b &optional (c "valeur optionnelle non fournie"))
   (list a b c))

(foo 1 2)
(1 2 "valeur optionnelle non fournie")
           Le message est plus explicite que NIL, mais un peu trop bavard.

Dans la fonction suivante les trois parametres requis sont a, b et c. dét est optionnel et dépend de a, b et c.
dét est le déterminant de l'équation du second degré ax²+bx+c=0.

(defun racines (a b c &optional (dét (- (* b b) (* 4 a c))))
   "calcule les racines de l'équation du second degré ax²+bx+c=0"
     (cond
       ((zerop dét) (format t "Une racine double: ~a~%" (/ (- b) (* 2 a))))
       ((minusp dét) (format t "Deux racines complexes: ~%"))
       ((plusp dét) (format t "deux racines réelles: ~%")))
     (if (zerop dét) nil
(format t "1ère racine: ~a~%2ème racine: ~a~%" (/ (- (- b) (sqrt dét)) (* 2 a)) (/ (+ (- b) (sqrt dét)) (* 2 a)))))

(racines 1 -2 1)
Une racine double: 1

(racines -1 -2 -2)
Deux racines complexes:
1ère racine: #C(-1.0 1.0)
2ème racine: #C(-1.0 -1.0)

(racines 1 2 -1)
deux racines réelles:
1ère racine: -2.4142137
2ème racine: 0.41421354

Ne prenez pas le risque d'appeler la fonction avec un 4ème argument: le déterminant ne serait pas correct.
...Et les réponses farfelues.
Ce problème peut être facilement résolu: je vous laisse chercher (LET, LABELS, ...)

La valeur optionnelle passée à une variable n'est pas forcément constante.
Ce peut être une expression lisp quelconque. En voici un exemple:

(defun lance-dé (x &optional (y (+ 1 (random 6))))    ;la valeur requise de x sera 0 et la valeur par défaut de y est calculée
  (setf x (+ x y))                ;y est ajouté à x
  (if (equal y 6)      ;on teste si y = 6 pour rejouer
      (lance-dé x)      ;le dé est relancé
      x))      ;la valeur de x est rendue si y < 6

(lance-dé 0)
8 quelle chance! Le premier lancé a donné 6
Remarque: cette fonction ne rendra jamais la valeur 6 (avec x=0)

(lance-dé 6 6)
15 là, le dé est visiblement pipé!

(lance-dé 0 -5)
-5 on peut même obtenir des valeurs aberrantes avec ce dé!

Cette fonction peut être améliorée, c'est:
Votre mission, si vous l'acceptez...
On peut, par exemple, créer une autre fonction qui appellera (lance-dé 0).
La valeur 6 ne sera jamais rendue, mais elle est prise en compte et c'est le but du jeu!

&rest:

(defun somme (&rest x)
   "rend la somme de la suite de nombres donnés en argument"
   (cond
     ((endp (cdr x)) (car x))
     (t (+ (car x) (apply 'somme (cdr x))))))       ;&rest permet de passer un nombre important d'arguments

(remarque: la fonction + fait déjà le même travail.)

&key:

(defun foo (&key a b c) (list a b c))                  ;&key permet de préciser une valeur par mot-clé:

(foo :b 1)
(NIL 1 NIL)           seul, le 2ème paramètre est donné

(defun foo (&key a ((:bob b)) c) (list a b c))     ;on peut changer le mot-clé (:bob au lieu de :b)

(foo :bob 1)
(NIL 1 NIL)            attention à la double parenthèse: ((:bob b))

Remarque: si on mixe différents types de paramètres, il faut le faire dans l'ordre suivant:
les requis, les &optional, les &rest et les &key.

Après les paramètres mots-clés, on peut placer le symbole &allow-other-keys. Cela permet de passer d'autres mots-clés avec leur valeurs.

(defun une-fonction (x &key y &allow-other-keys)
  (list x y))
UNE-FONCTION

(une-fonction 1 :y 2 :z 3)
(1 2)      la clé ajoutée à été ignorée, mais:

(defun une-autre-fonction (x &rest reste &key y &allow-other-keys)
  (list x y reste))
UNE-AUTRE-FONCTION

(une-autre-fonction 1)
(1 NIL NIL)    il n'y a ni y ni reste (seulement la valeur requise de la variable x

(une-autre-fonction 1 :y 2)
(1 2 (:Y 2))    on obtient la liste contenant la valeur de x, la valeur du mot-clé :y et &rest prend en                             considération ce qui suit

(une-autre-fonction 1 :y 2 :z 3)
(1 2 (:Y 2 :Z 3))   même chose, avec un paramètre mot-clé :z autorisé


RETURN-FROM: (voir aussi BLOCK: plus loin)

(defun foo (n)
   (dotimes (i 10) ;voir DOTIMES: plus loin dans cet article
     (dotimes (j 10)
       (when (> (* i j) n)    ;> est le signe plus grand que
 (return-from foo (list i j)))))) ;return-from permet de sortir de la fonction foo en rendant le résultat (ici: (list i j))

(foo 20)
(3 7)

FUNCTION:
#': (sharp-quote)

Les fonctions sont des objets. Il y a donc un type function. Pour obtenir la fonction-objet, on utilise FUNCTION ou #':
(function foo) ou #'foo
#

FUNCTION ou #' permet d'aller chercher une fonction dans l'espace de nommage des fonctions sans l'évaluer.

C'est très utile pour passer une fonction en argument à une autre fonction.

FUNCTIONP:     prédicat pour vérifier si un objet est une fonction

(functionp #'foo)
T
ici, on passe une fonction en argument au prédicat FUNCTIONP.

(symbol-function 'foo)
#<FUNCTION FOO> rend pour valeur la fonction associée au symbole passé en argument si le symbole est bien celui d'une fonction (erreur sinon)
à rapprocher de SYMBOL-VALUE pour les variables globales (spéciales)
SYMBOL-FUNCTION est setf-able (voir plus loin SETF:) :
Notez bien que, cette fois, c'est un symbole qui est passé à la fonction SYMBOL-FUNCTION (donc 'foo et non #'foo).

(setf (symbol-function '2fois)
      #'(lambda (x) (* 2 x)))
#<FUNCTION (LAMBDA (X)) {C2ACB95}> le symbole 2FOIS est associé à la fonction lambda

Ou, plus précisément, c'est la fonction lambda qui est affectée au champ fonction du symbole 2FOIS.

(2fois 3)
6


FUNCALL:

On utilise FUNCALL pour appeler la fonction à partir de sa fonction-objet (la fonction devient argument):

(funcall #'foo 25)
(3 9)             selon la précédente définition de FOO

Mais pourquoi cette complication pour appeler la fonction puisque (foo 25) rend la même chose?
C'est vrai cela ne sert à rien, car FOO se trouve dans l'espace des noms de fonctions, et l'appel à FOO est bien l'appel à une fonction.
Sauf qu'une variable peut contenir une fonction, et le nom d'une variable se trouve dans l'espace des noms de...variables!
Regardons cela sur un exemple:

(defparameter 3fois #'(lambda (x) (* 3 x)) "3fois est une variable qui contient une fonction")
3FOIS      définit une variable dont la valeur est une fonction

3fois
#<FUNCTION (LAMBDA (X)) {C80C69D}>       la variable contient bien une fonction

(3fois 7)     produit une erreur, car 3FOIS n'est pas dans l'espace des noms de fonctions

(funcall 3fois 7)
21   l'évaluation de 3FOIS donne une fonction et funcall applique cette fonction à l'argument 7
  bien noter qu'il n'y a pas de #' devant 3fois:
  #' ne rend une fonction que si le symbole qui suit est bien dans l'espace des noms de                            fonctions

De même:

(functionp 3fois)
T   permet de vérifier que la variable 3FOIS contient bien une fonction

COMPLEMENT:
Voici une fonction qui prend en argument une fonction-prédicat et qui rend la valeur de vérité opposée à la valeur de vérité rendue par la fonction-prédicat.
Elle ne peut être appelée qu'avec funcall:

(funcall (complement #'<) 3 1)
T il est vrai que 3 n'est pas inférieur à 1

Construisons une fonction qui fait la somme de deux fonctions à une variable pour une valeur donnée de cette variable.
Il faudra donc donner 3 arguments à cette fonction: une 1ère fonction, une 2ème fonction et la valeur de la variable.
Voici:

(setf (symbol-function 'f+g)
       #'(lambda (f g x) (+ (funcall f x) (funcall g x))))
#

L'appel se fait ainsi:

(f+g #'sin #'cos (/ pi 4))
1.414213562373095d0 on reconnait la racine carrée de 2

Rappelons, ici, la formule suivante: sinx + cosx = √2 cos(x-pi/4)
Vous pouvez bien sûr choisir toutes autres fonctions et autre valeur de la variable.

APPLY:

APPLY permet, aussi, d'appeler des fonctions-objets, mais les arguments qui suivent doivent se présenter sous la forme
d'une liste (quotée!). Exemple: (apply #'foo '(25)), APPLY utilise,donc, les fonctions-objets et les données qui sont dans la liste.
La liste ne se réduit pas toujours à un seul élément et peut être une liste calculée par le programme.
La fonction appelée doit, évidemment, supporter le nombre d'arguments.

Sauf que:

(apply #'+ 1 2 '(3 4 5))

15 fonctionne aussi très bien! Il faut, en fait, que le dernier argument soit une liste.
ou encore:

(apply #'+ 1 '(2 3 4 5))
15


FUNCALL et APPLY ne semblent pas poser de problème quand elles font appel à des fonctions avec &optional, &rest et &key.

____________________
Voici une fonction CHOIX (voir plus loin fermetures: et case:) qui utilise FUNCALL:

(let ((+ #'+) (* #'*))
  (defun choix (f)
    (case f
      (+ (princ (funcall f 3 4 5)))
      (* (princ (funcall f 3 4 5)))
      (t (princ "Rien à imprimer")))))
CHOIX    

(choix '+)
12
12

(choix '*)
60
60

(choix '/)
Rien à imprimer
"Rien à imprimer"
____________________

LAMBDA:

Les fonctions anonymes: lambda permet de définir, dans son code, une fonction sans la nommer. Exemples:

(funcall #'(lambda (x y z) (/ (* x y) z)) 1 2 3)
2/3

(apply #'(lambda (x y z) (/ (* x y) z)) '(1 2 3))
2/3

Il est inutile de nommer une telle fonction dans son code (ce serait trop lourd!).
De plus la fonction lambda est souvent passée en paramètre d'une autre fonction (ici: FUNCALL ou APPLY).

Mais l'appel à une fonction peut être une lambda-expression placée en premier élément:

((lambda (x) (+ 100 x)) 10)
110

((lambda (x) (and (zerop (rem x 3)) x)) (random 10))
6 ne rend que les multiples de 3 entre 0 (compris) et 10 (non compris). Sinon NIL.

Construisons un graphe avec les notions précédentes:

(defun graphe (fn min max pas)
   (loop for i from min to max by pas do
(loop repeat (funcall fn i) do (format t " "))
(format t "*~%")))

(graphe #'(lambda (x) (* x x)) -6 6 1)
                                    *
                         *
                *
         *
    *
 *
*
 *
    *
         *
                *
                         *
                                    *
NIL
Nous obtenons une belle parabole, graphe de la fonction f(x)=x².

Création de variables globales:

DEFPARAMETER:
DEFVAR:

(defparameter *nom* valeur "mettre, ici, une documentation") ou (defvar *nom* valeur "mettre, ici, une documentation")
      Il semblerait qu'en cas de recompilation du code après modification, la variable définie avec DEFVAR pourrait conserver son ancienne valeur, tandis qu'on devrait utiliser DEFPARAMETER pour réinitialiser la nouvelle valeur:

(defvar *v* 1)
*V* *V* prend la valeur 1

(setq *v* 2)
2 setq permet de modifier la valeur de *V*.
(Voir SETQ plus bas).

*v*
2 en effet!

(defvar *v* 3)
*V* defvar ne permet pas de modifier *V*

*v*
2 en effet!


defparameter permet de modifier la valeur de la variable. Essayez!

Création de "variables" constantes:

DEFCONSTANT:

(defconstant nom valeur "mettre, ici, une documentation")

(defconstant cste 57.295 "valeur approchée d'un radian.")
CSTE

(constantp cste)
T CONSTANTP est un prédicat pour vérifier si une "variable" est bien une constante.

(constantp '*v*)
NIL Les variables globales définies avec DEFVAR ou DEFPARAMETER ne sont pas des constantes!

(documentation 'nom 'variable)
      rend la documentation sur "nom" quelque soit sa définition
      par defvar, defparameter ou defconstant

(documentation '3fois 'variable)
"3fois est une variable qui contient une fonction"     selon la définition de 3fois précédente.

Voir aussi: DOCUMENTATION: pour les fonctions.

BOUNDP:    

(defparameter *x* 10)
*X*

(boundp '*x*)
T rend T si le symbole quoté est une variable globale ou une constante
c'est le cas si le symbole est défini par defvar, defparameter ou defconstant

Affectation d'une valeur à une variable:

SET:
SETQ:

(set 'var1 15)
15     permet de donner une valeur à une variable (ici un nombre)
          bien noter que la variable est quotée (elle ne doit pas être évaluée... Ici)
       notez qu'on peut vouloir l'évaluer dans certain cas: c'est alors le résultat de cette évaluation qui prend la valeur suivante.

var1
15   15 est bien la valeur de la variable var1

SETQ        permet de faire la même chose en évitant de quoter la variable:

(setq var1 'var2)
;
; caught WARNING:
;   undefined variable: VAR1
;
; compilation unit finished
;   Undefined variable:
;     VAR1
;   caught 1 WARNING condition
VAR2   var1 prend pour valeur var2 cette fois
           mais il y a un message d'erreur (suivant l'implémentation), var1 n'ayant pas été définie comme variable pour éviter cela:

(defparameter var1 ())
VAR1    définit une variable

(setq var1 'var2)
VAR2     var2 est la valeur de var1
  var2 n'a pas un statut de variable (pas encore): c'est un symbole

(boundp 'var2)
NIL   mais:
A cet instant, en appliquant SET à var1 (non quoté)...

(set var1 44)
44     ...var1 est évaluée, c'est donc var2 qui prend la valeur 44
 Vérification:

(boundp 'var2)
T

var1
VAR2 var2 est toujours la valeur de var1

var2
44         44 est la valeur de var2

Remarque: SETQ peut prendre un nombre pair d'arguments sous la forme: (setq var1 val1 var2 val2 ...).
Mais, var1, var2,... doivent être déclarées comme variables globales si on veut éviter le message "Undefined variable:...".
Pour cela on utilise DEFPARAMETER ou DEFVAR.
On peut aussi les déclarer comme variables locales avec LET, LET*, MULIPLE-VALUE-BIND, DESTRUCTURING-BIND:

(defparameter var-liste '(jour mois année))
VAR-LISTE     définition d'un paramètre var-liste dont la valeur est une liste (jour mois année)

(mapcar #'(lambda (x) (set x nil)) var-liste)
(NIL NIL NIL) les éléments de la liste sont initialisés avec NIL

(multiple-value-bind (jour mois année) (values 8 "septembre" 1948) (format t "Je suis né le ~a ~a ~a." jour mois année))
Je suis né le 8 septembre 1948.
NIL              les variables jour, mois, année sont locales et prennent, momentanément, les valeurs 8,                        "septembre", 1948.

(format t "Je suis né le ~a ~a ~a." jour mois année)
Je suis né le NIL NIL NIL.     On retrouve les valeurs des éléments de la liste var-liste


SETF:    (macro)

(setf place valeur)         setf est une macro qui cherche dans place l'endroit pour affecter la valeur. Exemples:

(setf (second liste) 5)
5
                si liste valait (a b c d):

liste
(a 5 c d)

setf utilise donc l'index de son 1er argument pour y placer 5

(notez que (second liste) devrait rendre B, mais setf n'utilise pas B mais sa place.
On dit que la fonction SECOND est setf-able.

(setf (cdr liste) '(1 2 3 4 5))
(1 2 3 4 5)

liste
(A 1 2 3 4 5)

le CDR de la liste (donc (5 C D)) a été remplacé par (1 2 3 4 5).

Quand on définit une fonction, elle n'est pas,a priori, setf-able.
Mais il y a moyen de la rendre setf-able, comme le montre l'exemple suivant.
Définissons la fonction DER qui rend le dernier élément d'une liste:

(defun der (lst)
  (car (last lst)))
DER

(der '(a b c d))
D vérification

(let ((x '(1 2 3 4)))
  (setf (der x) 5))
On obtient, ici, un message d'erreur disant que (setf der) est une fonction non-définie.
Il faut donc définir (setf der) de la façon suivante:

(defun (setf der) (val lst)
  (setf (car (last lst)) val))
(SETF DER)      rend la fonction DER setf-able

(let ((x '(1 2 3 4)))
  (setf (der x) 5)
  x)
(1 2 3 5) vérification: le dernier élément qui était 4 a pris la nouvelle valeur 5

L'expression (setf (der x) 5) fait appel à ((setf der) 5 x).
Si (setf der) n'existe pas on obtient une erreur.
Mais, ici, elle existe puisqu'on vient de la définir.
Elle évalue (setf (car (last x)) 5)) et ceci est toujours possible.
D'où le résultat escompté.

(setf x 10)
10              pour affecter 10 à la variable x
         même s'il y a un message d'avertissement annonçant
 que x n'est pas une variable définie

(setf x 1 y 2)
2                au lieu de (setf x 1) suivi (setf y 2)

(setf x (setf y (random 10)))
?              aléatoire, affecte la même valeur à x et y

en effet: (random 10) rend, par exemple, 7,
(setf y 7) affecte ce 7 à y et rend 7,
(setf x 7) affecte 7 à x.
____________________________________________________________

(defparameter *s* nil)
*S*

(defparameter *t* nil)
*T*                      définition de 2 variables

(setf (values *s* *t*) (floor (/ 17 3)))
5
2/3                les 2 résultats de FLOOR sont placés dans *s* et *t*

*s*
5

*t*
2/3               vérification
____________________________________________________________
1+:
INCF:
1-:
DECF:

(defvar *x* 10)
*X* définit une variable *X* et l'initialise avec la valeur 10, pour ce qui suit:

(1+ *x*)
11 rend la valeur de *x* + 1, sans changer *x*

(incf *x*)
11 incrémente *x* (*x* passe de 10 à 11). INCF est une macro.

(1- *x*)
10 rend la valeur de *x* - 1, sans changer *x*

(decf *x*)
10 décrémente *x* (*x* passe de 11 à 10). DECF est une macro.

(incf *x* 10)
20 incrémente *x* de 10

INCF et DECF sont des macro-modifiantes. Comme pour SETF, on peut écrire (incf place valeur).Idem avec DECF.

(defvar *lst* '(1 2 3))
*LST*        définition et initialisation d'une liste pour ce qui suit:

(incf (cadr *lst*) 10)
12           le 2ème élément de la liste est incrémenté de 10

*lst*
(1 12 3)              (vérification)

PUSH: (macro)
POP: (macro)
PUSHNEW: (macro)

(setf l1 '(a b c))
(A B C)         puis:

(pop l1)
A                  rend le premier élément de la liste

l1
(B C)           la liste ne contient plus le premier élément

(push 'a l1)
(A B C)       l'élément A est placé en début de liste. Plus généralement:

(push item place)
(item ...)              à condition que place corresponde à une liste. item est un objet lisp quelconque.

(setq x '(a (b c) d))
(A (B C) D)        puis:

(pushnew 5 (cadr x))
(5 B C)           (cadr x) doit être une liste. 5 est placé en début de cette liste

x
(A (5 B C) D)          x est modifiée

(pushnew 'b (cadr x))
(5 B C)         inchangé car B est déjà contenu dans la liste-cible  et x est inchangé

Remarque: PUSH, POP, PUSHNEW sont des macros, car on ne peut pas changer une variable avec une fonction.
Imaginons cependant une fonction POUSSE qui ajoute un élément à une pile:

(defun pousse (x l)
  (setf l (cons x l)))
  x est l'élément à pousser au début de la liste l

(defparameter *pile* (list 'b 'a))
*PILE*      définit une pile qui contient les éléments B et A ...

*pile*
(B A) ...effectivement

(pousse 'c *pile*)
(C B A) La fonction pousse rend bien une liste où l'élément C est ajouté

*pile*
(B A) mais la variable *pile*, qui n'est pas dans l'environnement de la fonction, n'a pas                                 changé

Voir MACRO: plus loin.

BUTLAST:
NBUTLAST:

(butlast '(a b c d))
(A B C)             rend la liste sans son dernier élément

(butlast '(a b c d) 2)
(A B)                rend la liste sans ses deux derniers éléments

BUTLAST n'est pas destructrice:

(setf *x* (list 1 2 3 4 5))
(1 2 3 4 5)

(butlast *x*)
(1 2 3 4)           rend la liste sans son dernier élément

*x*
(1 2 3 4 5)          *x* est inchangé

Ce n'est pas le cas de NBUTLAST:

(nbutlast *x*)
(1 2 3 4)              rend la liste sans son dernier élément

*x*
(1 2 3 4)             *x* est changé (recyclé)

LDIFF:

(setq x '(a b c d e))
(A B C D E)         puis:

(ldiff x (cdddr x))
(A B C)              enlève la fin de la liste, et x est inchangé. mais:

(ldiff x '(d e))
(A B C D E)       car '(D E) n'est pas EQ à une partie de x

ROTATEF:

(rotatef x y)
NIL                  échange les valeurs de x et y

SHIFTF:

(shiftf x y 10)
anc. valeur de x         x prend la valeur de y et y la valeur 10.

ROTATEF et SHIFTF sont des macro-modifiantes et on peut écrire (rotatef place valeur).
On peut leur passer autant d'arguments que l'on veut: (rotatef x y z t), (shiftf x y z t 15).

LES CONDITIONS:

IF: (forme spéciale)

(if (test) (format t "oui") (format t "non"))
oui si (test) rend T
non si (test) rend NIL


(if (> 2 3) "OK" "Non")
"Non"         IF est une fonction spéciale qui évalue la condition avant le reste de l'expression

(if (> 2 3) "OK")
NIL

(if (> 3 2) "OK" "Non")
"OK"

PROGN: (forme spéciale)
WHEN(macro - voir "MACRO:" plus loin)
UNLESS: (macro)
PROG1: (macro)

PROG2: (macro)

Si on veut évaluer plusieurs choses à la suite du IF, on peut utiliser PROGN:
(if (condition) (progn (action1) (action2))), ou mieux:

(WHEN (condition) (action1) (action2))        Effectue action1 puis action2 si la condition est vérifiée.
                      Rend NIL sinon. Macro utile qui a pu être définie par:
(defmacro when (condition &rest body)
  `(if ,condition (progn ,@body)))        attention à l'utilisation du backquote, de la virgule et de ,@ !
De même:
(UNLESS (condition) (action1) (action2))        effectue action1 puis action2 si la condition n'est pas vérifiée. Rend NIL sinon.

Attention! IF permet d'évaluer une autre action si la condition n'est pas vérifiée.
WHEN ne le permet pas et rend simplement NIL (ce qui n'est pas forcément mieux!).
PROGN rend le résultat de la dernière action

(progn (+ 1 2) (* 2 3))
6

Même si la dernière action rend un résultat à valeurs multiples:

(progn (+ 1 2) (values (* 2 3) (/ 2 3)))
6
2/3

PROG1 rend le résultat de la première action

(prog1 (+ 1 2) (* 2 3))
3

Par contre:

(prog1 (values (* 2 3) (/ 2 3)) (+ 1 2))
6             PROG1 ne rend pas les valeurs multiples du premier résultat

Pour cela, il faut utiliser:

MULTIPLE-VALUE-PROG1:

(multiple-value-prog1 (values (* 2 3) (/ 2 3)) (+ 1 2))
6
2/3

PROG2 rend le résultat de la deuxième action

COND: 7ème opérateur primitif (macro)

(COND (condition1 (action1-1) (action1-2) (...) ...)
      (condition2 (action2-1) (action2-2) (...) ...)
      ...
      (t (actionn-1) (actionn-2) (...) ...)) Pour des conditions à branches multiples (genre de IF imbriqués mais avec des actions multiples dans chaque branche

Exemple:
(defun x-dans-i (x min max)
  "rend T si x est dans l'intervalle fermé [min,max]."
  (cond
    ((< x min) (format t "~a est dans l'intervalle ]-ꝏ ~a[." x min) nil)
    ((> x max) (format t "~a est dans l'intervalle ]~a ꝏ[." x max) nil)
    (t (format t "~a est bien dans l'intervalle [~a ~a]." x min max) t)))
    définit une fonction, également prédicat, qui indique
si le premier argument est dans l'intervalle fermé [min max].

(x-dans-i 2 -3 6.2)
2 est bien dans l'intervalle [-3 6.2].
T       2 réponses: une information et T (NIL si x n'est pas dans l'intervalle fermé.

(if (x-dans-i 2 -3 6.2)
    "vrai"
    "faux")
2 est bien dans l'intervalle [-3 6.2].
"vrai"   on vérifie que x-dans-i est bien aussi un prédicat

(if (x-dans-i -4 -3 6.2)
    "vrai"
    "faux")
-4 est dans l'intervalle ]-ꝏ -3[.
"faux"     cas où le résultat est NIL.

CASE:

(case (+ 4 5)
   (8 (princ "Résultat : 8."))
   (9 (princ "Résultat : 9."))
   (10 (princ "Résultat : 10."))
   (t (princ "Un autre résultat.")))
Résultat : 9.
"Résultat : 9."

Parmi les fonctions-prédicats lisp il y a la fonction y-or-n-p.
Je vous propose celle-ci qui lui ressemble un peu:

(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 (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

Remarquez la possibilité de mettre plusieurs symboles pour une même réponse.
La fonction admet un argument: une chaîne de caractère (en général la question à laquelle il faut répondre par oui ou non.).
Ce prédicat ne traite pas toutes les erreurs possibles: essayez de taper une virgule comme réponse.
y-or-n-p ne pose pas de problème. Voyez: ignore-errors: dans le chapitre LES ERREURS et LES INTERRUPTIONS:

(defun heure-événement ()
  "rend l'heure qu'il sera dans un certain nombre d'heures."
  (format t "Dans combien d'heures doit se produire l'événement?")
  (let ((h (read)))
    (multiple-value-bind (secondes minutes heures date mois année jour été zone) (get-decoded-time)  ;calcule l'heure du lieu
      (setf heures (mod (+ heures h) 24))
      (case heures
((23 0 1 2 3 4 5 6) (format t "Il fera nuit et vous dormirez probablement!"))
(7 (format t "Debout! Il sera 7 heures ~a minutes ~a secondes." minutes secondes))
((8 9 10 11) (format t "Il sera ~a heures ~a minutes ~a secondes." heures minutes secondes)) ;on pourrait ne pas écrire cette ligne
(12 (format t "Il est temps d'aller déjeuner!"))
(otherwise (format t "Il sera ~a heures ~a minutes ~a secondes." heures minutes secondes))))))

Remarques: les clauses qui suivent le premier paramètre du CASE peuvent être une liste d'objets ou un seul objet, (8 9 10 11) ou 12.
  le prédicat d'égalité dans CASE est EQL (donc attention au type d'objet utilisé).
  otherwise peut-être remplacé par t. Ces symboles constituent la dernière clause obligatoirement.
  Sinon les mettre entre parenthèses: (t) ou (otherwise)
  Dans le calcul de l'heure du lieu, il y a des variables non utilisées ici (d'où les messages d'alerte).

(heure-événement)
Dans combien d'heures doit se produire l'événement?140
Il sera 14 heures 47 minutes 8 secondes.
      Le résultat dépend du temps présent au lieu où vous vous trouvez

ECASE: comme CASE, mais rend une erreur si aucune clause ne convient (ici, pas de clauses t ou otherwise).

GET-INTERNAL-REAL-TIME: renvoie sous forme d'entier l'heure actuelle en unités de temps internes, par       rapport à une base de temps arbitraire.

(get-internal-real-time)
345562687

GET-INTERNAL-RUN-TIME: Le nombre d'unités de temps internes en une seconde.

(get-internal-run-time)
1763

SLEEP: suspend l'exécution pendant le temps en secondes donné en argument.
       puis reprend l'exécution.

(sleep 10)
NIL
rend NIL au bout de 10 secondes.

GET-UNIVERSAL-TIME: renvoie l'heure actuelle, représentée comme une heure universelle.

(get-universal-time)
3803366384

TYPECASE: (condition suivant le type)

(defun somme* (x y) ;cette fonction n'a que deux arguments
   (typecase x
     (number (+ x y)) ;rend la somme de x et y si y est du type number,sinon erreur
     (character (format nil "~c~c" x y)) ;rend les deux caractères concatenés
     (string (concatenate 'string x y)) ;rend les deux chaînes concaténées
     (list (append x y))        ;rend la liste des éléments de x et de y
     (vector (coerce (mapcar #'+ (coerce x 'list) (coerce y 'list)) 'vector))   ;rend la somme des vecteurs si les composantes sont numériques, sinon erreur
     (symbol (intern (format nil "~a-~a" x y))) ;rend un symbole formé des symboles x et y séparés par un -
     (t (list x y))))             ;rend une liste formée des éléments x et y dans tous les autre cas
SOMME*

typecase ne contrôlant que le premier argument, il y a de nombreux cas d'erreur.
Je vous laisse affiner cette fonction en ajoutant des contrôles sur le 2ème argument.

(somme* 5 4)
9

(somme* #(1 2) #(3 4))
#(4 6)
Rend la somme des vecteurs si les composantes sont des nombres, sinon erreur.

(somme* 5 "a")
; Evaluation aborted on #
                   si y n'est pas du même type que x: erreur

(somme* "clic" "clac")
"clicclac"

(somme* "clic" #\a)
; Evaluation aborted on # &ltTYPE-ERROR expected-type: SEQUENCE datum: #\a&gt

#\a n'est pas du type séquence, mais:

(somme* "clic" '(#\a))
"clica"
On peut ajouter une liste de caractères.(voir concatenate:, plus loin)

Ect...

LES SAUTS:

BLOCK: opérateur spécial utilisé avec RETURN-FROM
       comprend un nom et des formes à évaluer dans l'ordre. La valeur de la dernière forme est rendue, sauf si un
RETURN-FROM: est utilisé pour sortir du block plus tôt.
La fonction suivante compte jusqu'à n, mais sans jamais dépasser 100:

((defun saut-de-bloc (n)
           (let ((x 0))
             (block mon-bloc
               (dotimes (i 100)
                 (setf x (1+ x))
                 (progn (if (> x n) (return-from mon-bloc)) (format t "~a-" x)))
               (if (> n 100) (format t "~&Je ne sais compter que jusqu'à 100!")))
             (format t "~&Je suis sorti du bloc \"mon-bloc\"."))
           (format t "~&Je suis sorti du let."))


(saut-de-bloc 15)
1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-
Je suis sorti du bloc "mon-bloc".
Je suis sorti du let.
NIL
        Il y a sortie immédiate du bloc dès que x atteint n.

(saut-de-bloc 150)
1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16-17-18-19-20-21-22-23-24-25-26-27-28-29-30-31-32-33-34-35-36-37-38-39-40-41-42-43-44-45-46-47-48-49-50-51-52-53-54-55-56-57-58-59-60-61-62-63-64-65-66-67-68-69-70-71-72-73-74-75-76-77-78-79-80-81-82-83-84-85-86-87-88-89-90-91-92-93-94-95-96-97-98-99-100-
Je ne sais compter que jusqu'à 100!
Je suis sorti du bloc "mon-bloc".
Je suis sorti du let.
NIL
              compte jusqu'à 100 (fin de l'itération de DOTIMES): il n'y a pas de sortie du block immédiatement puisque le texte "Je ne sais compter que jusqu'à 100" se trouve dans le bloc! (voyez l'indentation pour le vérifier). Ensuite, bien sûr, on sort de la fonction.

Supposons qu'on veuille afficher la somme des nombres entiers précédemment comptés en sortant du DOTIMES:

(defun saut-de-bloc (n)
  (let ((x 0) (s 0))
    (block mon-bloc
      (dotimes (i 100)
(setf x (1+ x))
(progn (if (> x n) (return-from mon-bloc)) (format t "~a, " x) (incf s x)))
      (format t "la somme de ces nombres entiers est: ~a.~%" s))))  ;on ajoute cette ligne après le bloc DOTIMES

(saut-de-bloc 10)
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
NIL      cela ne marche pas!

   En fait, RETURN-FROM nous fait sortir du bloc "mon-bloc" (saut non local) et les expressions restantes de "mon-bloc" ne sont pas évaluées
   on utilise, alors, un opérateur spécial:

UNWIND-PROTECT:

(defun saut-de-bloc (n)
  (let ((x 0) (s 0))
    (block mon-bloc
      (unwind-protect
   (dotimes (i 100)
     (setf x (1+ x))
     (progn (if (> x n) (return-from mon-bloc)) (format t "~a, " x) (incf s x)))
(format t "~&la somme de ces nombres entiers est: ~a.~%" s))
      (if (> n 100) (format t "~&Je ne sais compter que jusqu'à 100!")))))

(saut-de-bloc 10)
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
la somme de ces nombres entiers est: 55.
NIL
         Cette fois, c'est bon!

Je vous demande, ici, un peu d'attention.
Regardez bien l'indentation du code de la fonction SAUT-DE-BLOC dans sa dernière version ci-dessus.
Le texte "la somme des nombres entiers est:..." se trouve dans le bloc (UNWIND-PROTECT...).
Ce texte est donc "protégé" et est affiché à chaque sortie du bloc "mon-bloc".
On s'est permis d'ajouter la phrase déjà rencontrée: "Je ne sais compter que jusqu'à 100!".
Celle-ci ne se trouve pas dans le bloc (UNWIND-PROTECT...) mais, tout de même, dans le bloc "mon-bloc".
Elle est donc affichée à la sortie du bloc DOTIMES (c'est à dire quand n est plus grand que 100).

(saut-de-bloc 150)
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100,
la somme de ces nombres entiers est: 5050.
Je ne sais compter que jusqu'à 100!

En fait, l'expansion de DOTIMES contient un BLOCK nommé NIL, ce qui nous autorise à utiliser RETURN (correspondant à
RETURN-FROM NIL):

(dotimes (i 10)
   (let ((réponse (random 100)))
     (print réponse)
     (if (> réponse 50) (return))))

10
48
79
           quand la réponse devient supérieure à 50, il y a sortie du block (de DOTIMES)

Il en est de même avec DO et DOLIST.

TAGBODY:
GO:

(tagbody top
    (print 'hello)
    (when (plusp (random 10)) (go top)))

HELLO
HELLO
NIL           (random 10) a pris la valeur 0 au troisième essai: le saut n'a pas lieu

Il peut y avoir plusieurs tags dans TAGBODY:

(tagbody
    a (print 'a) (if (zerop (random 2)) (go c))
    b (print 'b) (if (zerop (random 2)) (go a))
    c (print 'c) (if (zerop (random 2)) (go b)))

TAGBODY et GO sont surtout utilisés pour convertir du FORTRAN en Common Lisp: ce qui ouvre l'accès aux bibliothèques du FORTRAN

OPERATEURS LOGIQUES:

NOT:
AND:
OR:

(not nil)
T              NOT: opérateur logique booléen

(not (= 1 1))
NIL          change la valeur logique

(and (= 1 2) (= 3 3))
NIL         ET logique

(and t (+ 3 1))
4 rend la dernière valeur calculée (donc vrai)

(or (= 1 2) (= 3 3))
T OU logique

(or nil (+ 5 3))
8 rend 8, donc vrai

Reconstruisons la fonction NON (équivalente à NOT):

(defun non (x)
   (cond (x nil)
(t t)))

(non t)
NIL

(non nil)
T

Reconstruisons la fonction ET (équivalente à AND):

(defun et (x y)
   (cond (x (cond (y t)
  (t nil)))
(t nil)))

(et t t)
T
Vérifiez les 3 autres cas.

De même pour OU:

(defun OU (x y)
   (cond (x t)
(y t)
(t nil)))

FLET: permet de redéfinir localement des fonctions globales:

(flet ((safesqrt (x) (sqrt (abs x))))
   (safesqrt (apply #'+ (map 'list #'safesqrt '(1 -2 3)))))
2.036238                        la fonction locale safesqrt est une redéfinition de sqrt car elle ne s'applique qu'aux valeurs absolues de x. Elle est, ensuite, appliquée deux fois dans le corps du FLET
(map 'list #'safesqrt '(1 -2 3)) rend la liste des racines carrées des valeurs absolues de la liste (1 -2 3),
(apply #'+ ... en fait la somme dont la racine carrée est calculée par la fonction locale.

LABELS: permet de définir des fonctions locales dans une autre fonction et, de plus, ces fonctions locales peuvent être mutuellement récursives: (voir récursivité: plus loin)

La fonction "longueur" suivante rend la longueur d'une liste passée en argument.
Je l'ai placé là, pour mettre en valeur la fonction "labels" d'une façon simple.
Ce n'est pas la fonction "length" qui peut s'appliquer à diverses séquences (chaînes de caractères, vecteurs,...).
Même défaut probablement: à ne pas utiliser avec les listes circulaires.

(defun longueur (liste)
           (labels ((long (l n) ;noter le double parenthèsage après le mot "labels"
                      (if (null l) n ;rend n quand la liste l est vide (null ou endp)
                          (long (cdr l) (1+ n)))))    ;on incrémente n
             (long liste 0))) ;l'appel à la fonction locale "long" se fait dans la fonction "labels" (avant dernière parenthèse)

Remarque: si vous placez ce texte dans Emacs, je vous conseille d'utiliser l'option "Highlight Matching Parentheses" dans le menu "Options". En plaçant le curseur sur une parenthèse ou juste après, vous verrez la parenthèse (ouvrante ou fermante) correspondante plus facilement.
Notez que, lors de l'appel à la fonction "longueur", l'argument doit être une liste. La fonction ne le vérifie pas, mais vous pouvez l'améliorer.

(longueur '(a b c d))
4

(defun effeuillage (arbre)
  (let ((feuilles ())) ;la liste des feuilles est vide au début
    (labels ((avance (arbre)
(cond
 ((null arbre)) ;arrêt en bout de branche
 ((atom arbre) (push arbre feuilles)) ;l'atome rencontré est placé en début de liste
 (t (avance (car arbre)) ;1ère récursion sur le CAR de ARBRE
    (avance (cdr arbre)))))) ;2ème récursion sur le CDR de ARBRE
      (avance arbre))       ;rend la liste des feuilles en ordre inversé
    (nreverse feuilles))) ;rend la liste des feuilles dans l'ordre rencontré dans ARBRE
EFFEUILLAGE définit une fonction qui récupère les feuilles d'un arbre lisp

(effeuillage '(a (d (f g h) e) b c))
(A D F G H E B C)             les feuilles sont collectées dans une liste

Autre exemple:

(defun compte-occurences (obj lsts)
  (labels ((occurences (lst)
     (if (consp lst)
 (+ (if (eq (car lst) obj) 1 0)
    (occurences (cdr lst)))
 0)))
    (mapcar #'occurences lsts)))
COMPTE-OCCURENCES

(compte-occurences 'a '((b c) (e a k a c a) (e a b) (a d a)))
(0 3 1 2)

Remarque: ce qui est local dans une fonction définie dans LABEL c'est, en fait, le lien entre son nom et la fonction proprement dite!
Exemple: définissons la fonction factorielle dans un LABELS:

(defparameter *fn* nil)
*FN*      pour éviter le message d'erreur dans la définition suivante:

(labels ((factorielle (n)
   (if (< n 2)
1
(* n (factorielle (1- n))))))
  (setf *fn* #'factorielle)  ;le paramètre *fn* prend pour valeur la fonction dont le nom est factorielle
  (factorielle 5))  ;exemple de calcul
120                c'est bien factorielle 5

(funcall *fn* 5)      ;on appelle la valeur du paramètre *fn* et on lui passe l'argument 5
120 même résultat, donc factorielle est bien une fonction globale

MACROLET: permet de définir des macros locales (utilise le même format que DEFMACRO, voir plus loin)

LES ITÉRATIONS:

LA RÉCURSIVITÉ:

(defun décompte (n)
           (format t "n=~a " n)
           (sleep 2)
           (if (<= n 0)
               (format t "Boum~%")
               (décompte (1- n))))      ;appel à décompte dans sa propre définition
DÉCOMPTE

(décompte 10)
n=10 n=9 n=8 n=7 n=6 n=5 n=4 n=3 n=2 n=1 n=0 Boum
NIL

PROG:

On peut faire la même chose, sans utiliser la récursivité:

(defun décompte1 (n)
   (prog (r) ;prog permet de construire l'itération
      itér ;tag indiquant le début de l'itération
      (and (equal n 0) (return (format t "n=~a Terminé~%" n))) ;test marquant la fin de l'itération et sortie du prog avec return
      (setq r n n (- n 1))     ;évolution des variables locales du PROG
      (format t "n=~a " r)     ;affichage de la valeur calculée
      (sleep 1)         ;une pause d'une seconde
      (go itér)))             ;saut vers le tag itér pour continuer l'itération

Note: la variable locale r n'est pas absoluement nécessaire ici.
Voyez vous-même ce qu'il faut changer pour avoir le même résultat.
(Pensez à remplacer (prog (r) ... par (prog () ... . C'est à dire que prog est suivi de la liste vide)

(décompte1 10)
n=10 n=9 n=8 n=7 n=6 n=5 n=4 n=3 n=2 n=1 n=0 Terminé

Dans ce qui précède, la valeur de n change effectivement.
Voici une autre version où c'est la variable locale r qui change (pas n):

(defun décompte2 (n)
   (prog (r)
      (setq r n)
      itér
      (and (equal r 0) (return (format t "n=~a Terminé~%" r)))
      (format t "n=~a " r)
      (setq r (- r 1))
      (go itér)) n) ;on ajoute n à la sortie du PROG pour l'afficher

(décompte2 10)
n=10 n=9 n=8 n=7 n=6 n=5 n=4 n=3 n=2 n=1 n=0 Terminé
10   la valeur de n a été conservée

Sans doute avez-vous apprécié, au passage, la récursivité.

DOLIST: Macro

(dolist (x '(1 2 3)) (print x))
1
2
3
NIL              agit, de façon itérative, sur chaque élément de la liste

Il peut y avoir plusieurs action à suivre:

(dolist (x '(1 2 3)) (print x) (if (evenp x) (return)))
ici, la deuxième action permet, avec RETURN, de sortir de l'itération avant la fin de la boucle

La fonction suivante rend la liste des carrés des nombres de la liste passée en argument:

(defun liste-carrés (*ll*)
   (let ((*l* nil))
     (dolist (x *ll*) ((lambda (y) (setf *l* (cons (* y y) *l*))) x))
     (reverse *l*)))

(liste-carrés '(1 2 3 4 5 6 7))
(1 4 9 16 25 36 49)

DOTIMES: Macro

(dotimes (i 3) (print i))
0
1
2
NIL                i prend les valeurs 0, 1, 2, donc inférieures à 3

(dotimes (i 5) (print i) (if (> i 2) (return)))
                      même remarque que pour DOLIST (au dessus)

DOLIST et DOTIMES peuvent avoir un 3ème élément dans la liste du 1er argument.
Cet élément représente le résultat rendu par DOLIST ou DOTIMES:

(defun somme-n (n)
           "affiche la somme des n premiers nombres entiers et rend le résultat à la ligne."
           (let ((s 0))               ;variable locale initialisée à 0
             (dotimes (i (1+ n) s)     ;i varie de 0 à n+1, s est le résultat rendu par DOTIMES
               (if (< i n)                  ;si i est strictement inférieur à n...
                   (format t "~a + " i)     ;...on écrit 0 + 1 + 2 +...Sinon...
                   (format t "~a =" i))     ;...on écrit n =
               (incf s i))))     ;i est ajouté à s à chaque itération
SOMME-N

(somme-n 9)
0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 =
45
On retrouve bien la fameuse formule: 9x(9+1)/2 (ce qui serait plus simple à programmer!)

(defun somme-inverses (n)
   "affiche la somme des inverses des n premiers nombres entiers (>0) et rend le résultat à la ligne."
   (let ((s 0))
     (dotimes (i (1+ n) s)
       (cond
((= i 0)) ;on ne fait rien 0 n'admet pas d'inverse
((< i n) (let ((k (/ 1 i))) (format t "~a + " k) (incf s k)))
(t (let ((k (/ 1 i))) (format t "~a =" k) (incf s k))))) ;présentation du calcul demandé
      (print (coerce s 'float))))      ;on transforme la somme en flottant et on l'imprime

(somme-inverses 9)
1 + 1/2 + 1/3 + 1/4 + 1/5 + 1/6 + 1/7 + 1/8 + 1/9 =
2.8289683
2.8289683

DOLIST et DOTIMES peuvent s'imbriquer:

(dotimes (x 10)
   (dotimes (y 10)
     (format t "~a x ~a = ~3d " (1+ x) (1+ y) (* (1+ x) (1+ y))))
   (format t "~%"))
                               affiche la table de multiplication jusqu'à 10 x 10

DO:

(do ((i 0 (1+ i))) ;(i 0 (1+ i)) représente la variable i, sa valeur initiale et son incrémentation
    ((>= i 4) 'fin) ;ici se trouve le test d'arrêt qui peut être suivi d'un résultat 'fin par exemple
  (format t "i=~a " i)) ;Puis on affiche i à chaque itération (cela constitue le body du DO).
On obtient, ici, le résultat suivant:
  i=0 i=1 i=2 i=3
  FIN
FIN est rendu s'il n'y a pas d'autres formes après la sortie du DO.

La définition de la variable contient le nom de la variable, sa valeur de départ et l'expression du saut de valeur.
Il peut y avoir plusieurs définitions de variable. A chaque itération, le saut de valeur est évaluer pour chaque variable et une nouvelle valeur est attribuée à chaque variable. S'il n'y a pas de saut de valeur, la variable garde sa valeur initiale.
Exemple: calcul du 10ième nombre de la suite de Fibonacci (ou 11ième si on considère l'élément n° zéro)
Dans le DO suivant, il y a 3 variables définies: n, cur et next:

(do ((n 0 (1+ n))                ; n représente le rang du nombre de Fibonacci
     (cur 0 next)                   ;cur est la valeur courante du nombre de Fibonacci
     (next 1 (+ cur next)))    ;next est la prochaine valeur du nombre de Fibonacci
    ((= 10 n) cur))                ;10 est le rang de la 11ième valeur. (= 10 n) est donc le test d'arrêt et le résultat est la valeur courante cur (la 12ième valeur est alors déjà calculée
  dans next.

(do ((l nil) (i 1 (1+ i)))           ;2 définitions de variables: une liste l et un nombre i
     ((> i 10) (nreverse l))   ;1 test d'arrêt et une inversion de liste
   (push i l))                            ; i est ajouté à la liste (devant)
(1 2 3 4 5 6 7 8 9 10)

Remarque: il existe une macro DO* et la différence avec DO est la même qu'entre LET et LET*.
Les deux expressions suivantes montrent cette nuance:

(do ((i 1 (1+ i))
     (j 1 i))
    ((> i 5) (format t "i = ~a et j = ~a." i j))
  (format t "(~a ~a)  " i j))
(1 1)  (2 1)  (3 2)  (4 3)  (5 4)  i = 6 et j = 5.
NIL          après avoir pris sa valeur initiale 1, j prend, à chaque itération, 
     la valeur de i de l'itération précédente

(do* ((i 1 (1+ i))
     (j 1 i))
    ((> i 5) (format t "i = ~a et j = ~a." i j))
  (format t "(~a ~a)  " i j))
(1 1)  (2 2)  (3 3)  (4 4)  (5 5)  i = 6 et j = 6.
NIL          après avoir pris sa valeur initiale, j prend la valeur de i de l'itération courante

Si vous avez un peu de mal avec la macro DO, quand on débute, c'est bien normal!
Vous pouvez, alors, utiliser une fonction locale équivalente définie avec LABELS.
Prenons un exemple: soit un rectangle de périmètre constant p (ou demi-périmètre constant dp).
On fait varier sa largeur x et sa longueur y, et on étudie son aire.
Soit x = 1 et y = 9 au départ. Donc dp reste égal à 10.
DO nous permet de calculer l'aire par itération en augmentant x et en diminuant y:

(do ((x 1 (+ 1 x))
     (y 9 (- y 1)))
    ((>= x y) (format t "Résultat final: ~a x ~a = ~a ." x y (* x y)))
  (format t "~a x ~a = ~a ," x y (* x y)))
1 x 9 = 9 ,2 x 8 = 16 ,3 x 7 = 21 ,4 x 6 = 24 ,Résultat final: 5 x 5 = 25 .
NIL

Remarque: en choisissant des valeurs initiales entières pour x et y et un
demi-périmètre de 10, on est sûr d'arriver à: x = y = 5.
Ce n'est plus le cas si x = 1 et y = 10, ou bien si x et y ont des valeurs décimales.
Je vous laisse améliorer la procédure: c'est un exercice intéressant.
Mais voyons voir la fonction locale équivalente avec LABELS:

(labels ((surf-rect (x y)
           (cond ((>= x y)
         (format t "Résultat final: ~a x ~a = ~a ." x y (* x y)))
(t
 (format t "~a x ~a = ~a ," x y (* x y))
 (surf-rect (+ 1 x) (- y 1))))))
  (surf-rect 1 9))
1 x 9 = 9 ,2 x 8 = 16 ,3 x 7 = 21 ,4 x 6 = 24 ,Résultat final: 5 x 5 = 25 .
NIL

READ: (voir aussi les fichiers et les entrées/sorties:)

(read)
abc READ attend une entrée sur le clavier (entrée standard): ici, on tape abc et
ABC le symbole ABC s'affiche sur l'écran (sortie standard)

(read)
#\d READ peut lire un caractère,...
#\d ... qu'il rend au format caractère

READ-CHAR:

(read-char)
e READ-CHAR ne lit qu'un seul symbole ...
#\e ...et le rend au format "caractère"

(read-char)
EdF
#\E les autres symboles ne sont pas lus

(write 'f)
F      l'argument de WRITE est normalement évalué (mais, ici, il est quoté), et envoyé vers l'écran
F      LISP renvoie le résultat de la dernière forme

(defparameter f 34)
F      on donne une valeur à F

(write f)
34     la variable F est évaluée et sa valeur envoyée vers l'écran
34     LISP rend également la valeur

(write #\g)
#\g    un caractère (au format caractère!) est envoyé vers l'écran au format caractère
#\g

(write-char #\h)
h    WRITE-CHAR renvoie vers l'écran un caractère dans un format humainement lisible
#\h    mais LISP, lui, rend un format caractère

(write-char 'h)
   provoque une erreur

Les caractères particuliers suivants peuvent être utilisés:
#\space (espace), #\newline (nouvelle ligne), #\linefeed (idem), #\tab (tabulation)

Pour les petits qui apprennent à compter:
(Faire, éventuellement, ctrl+s FORMAT: dans EMACS)

(defun somme ()
   (format t "Calcule la somme suivante.~%")
   (format t "Puis tape ton résultat, puis ENTREE.~%")
   (let ((x (random 100)) (y (random 100)))
     (format t "~a + ~a = " x y)
     (let ((s (read)))
       (if (= (+ x y) s)
   (format t "~%C'est juste!~%")
   (format t "~%Tu as fait une erreur. Vérifie!~%")))))

Et pour répéter cela 10 fois:

(do ((i 0 (1+ i))) ((>= i 10) 'fin)
   (somme))

Rappel: vous pouvez concaténer tous les articles de ce blog et consulter l'ensemble dans EMACS ou OpenOffice ou tout autre traitement de texte muni d'une fonction de recherche.
La prochaine fois, nous parlerons de la suite de Fibonacci, pour se mettre l'eau à la bouche.

Aucun commentaire:

Enregistrer un commentaire