vendredi 13 mars 2015

19-LES PACKAGES

>>>>
LES PACKAGES: (j'ai préféré conserver le mot package plutôt que d'utiliser les mots paquet ou paquetage)

Un package contient quatre listes:
   la liste Symb-int des symboles internes au package,
   la liste Symb-ext des symboles externes, ou plus exactement exportables du package,
   la liste Symb-cach des symboles cachés,
   la liste des packages utilisés pour ce package.
On verra, dans ce qui suit, que l'on peut autoriser l'exportation de symboles pour utilisation dans un autre package,
l'importation d'un ou plusieurs symboles provenant d'un package particulier ou de plusieurs packages.
On peut, aussi, caché un symbole en provenance d'un ou plusieurs packages utilisés pour éviter un conflit.
Si un symbole provient de deux packages, on peut caché celui qui vient d'un des deux packages.

Des connaissances s'imposent sur les symboles (voir LES SYMBOLES:).

FIND-PACKAGE:

(find-package "COMMON-LISP-USER")
#<PACKAGE "COMMON-LISP-USER">
rend le package dont le nom est COMMON-LISP-USER

Remarquez qu'un nom de package s'écrit en majuscules.
Essayez en minuscules! Vous obtiendrez un message du genre:
"The name ~S does not designate any package."

Réciproquement, on peut chercher le nom d'un package:

(package-name *package*)
"COMMON-LISP-USER" la variable *package* contient le package courant
c'est à dire celui dans lequel on travail

ou bien (mais là, on tourne "en rond"):

(package-name (find-package "COMMON-LISP-USER"))
"COMMON-LISP-USER"        rend le nom du package trouvé

*****
Attention! Si vous tapez au clavier: # ou
si vous faites un copier-coller à partir d'un fichier texte,
vous risquez d'avoir un message d'erreur du genre:
illegal sharp macro character: #\<
Si vous êtes dans ce cas, procédez ainsi:

(defparameter pack (find-package "COMMON-LISP"))
PACK

(package-name pack)
"COMMON-LISP"
*****
sinon, avec de la chance:

(package-name #<PACKAGE "COMMON-LISP">)
"COMMON-LISP" de même avec le package COMMON-LISP

Par contre:

(find-package "MON-APPLI")
NIL rend NIL car le package MON-APPLI n'a pas été défini

Un package possède, parfois un alias (si son nom est trop long, par exemple):

PACKAGE-NICKNAMES:

(package-nicknames *package*)
("CL-USER") rend une liste contenant les alias du package courant (ici, un seul alias)

(find-package "CL-USER")
#<PACKAGE "COMMON-LISP-USER"> on peut utiliser l'alias à la place du nom

SYMBOL-PACKAGE: Pour trouver à quel package appartient un symbole:

(symbol-package 'nil)
#<PACKAGE "COMMON-LISP">
      le symbole NIL appartient au package COMMON-LISP

Remarque: si on tape un symbole, a priori inexistant, il sera déclaré comme appartenant au package courant:

(symbol-package 'toto)
#<PACKAGE "COMMON-LISP-USER">
 le reader n'a pas trouvé TOTO dans les packages déclarés
 TOTO est intégré dans le package courant (COMMON-LISP-USER alias CL-USER ici)

(find-symbol "TOTO" "CL-USER")
TOTO
:INTERNAL TOTO est classé interne dans CL-USER

Pour cette fonction FIND-SYMBOL les arguments sont le nom du symbole et le nom du package dans cet ordre.

(find-symbol "NIL" "COMMON-LISP")
NIL le symbole de nom "NIL" a été trouvé dans le package COMMON-LISP
:EXTERNAL  on voit que c'est un symbole classé externe dans le package COMMON-LISP ceci veut dire qu'il est exportable (et non pas qu'il est extérieur!) COMMON-LISP-USER utilise COMMON-LISP d'ailleurs:

(find-symbol "NIL" "CL-USER") ;CL-USER est l'alias de COMMON-LISP-USER
NIL                      le nom "NIL" a bien été trouvé dans le package COMMON-LISP-USER
:INHERITED on voit que le symbole est hérité (il vient de COMMON-LISP)

On peut se demander si c'est bien le même symbole:

(eq (find-symbol "NIL" "COMMON-LISP") (find-symbol "NIL" "CL-USER"))
T                         oui, c'est le même symbole

LIST-ALL-PACKAGES:

(list-all-packages)
(#<PACKAGE "SWANK-IO-PACKAGE"> #<PACKAGE "SWANK"> #<PACKAGE "SWANK-RPC">
 #<PACKAGE "SWANK-MATCH"> #<PACKAGE "SB-CLTL2"> #<PACKAGE "SB-POSIX">
...     rend la liste actuelle de tous les packages enregistés
(j'ai coupé la liste qui est trop longue)

PACKAGE-USE-LIST:        (l'argument peut être l'objet package ou le nom du package)

(package-use-list *package*)
(#<PACKAGE "COMMON-LISP"> #<PACKAGE "SB-ALIEN"> #<PACKAGE "SB-DEBUG">
 #<PACKAGE "SB-EXT"> #<PACKAGE "SB-GRAY"> #<PACKAGE "SB-PROFILE">)
             rend la liste des packages utilisés par le package COMMON-LISP-USER
            *package* a pour valeur le package courant (ici, COMMON-LISP-USER)

PACKAGE-USED-BY-LIST:   (l'argument peut être l'objet package ou le nom du package)

(package-used-by-list "COMMON-LISP")

     ou bien:

(package-used-by-list (find-package "COMMON-LISP"))
(#<PACKAGE "SWANK"> #<PACKAGE "SWANK-RPC"> #<PACKAGE "SWANK-MATCH">
 #<PACKAGE "SB-CLTL2"> #<PACKAGE "SB-POSIX"> #<PACKAGE "SB-INTROSPECT">
 #<PACKAGE "SB-BSD-SOCKETS"> #<PACKAGE "SB-BSD-SOCKETS-INTERNAL">
 #<PACKAGE "SWANK-BACKEND"> #<PACKAGE "SWANK-LOADER">
 #<PACKAGE "UIOP/COMMON-LISP"> #<PACKAGE "ASDF/PACKAGE">
 #<PACKAGE "UIOP/PACKAGE"> #<PACKAGE "SB-EXT"> #<PACKAGE "SB-INT">
 #<PACKAGE "SB-VM"> #<PACKAGE "SB-KERNEL"> #<PACKAGE "SB-SYS">
 #<PACKAGE "SB-IMPL"> #<PACKAGE "SB-DI"> #<PACKAGE "SB-ALIEN-INTERNALS">
 #<PACKAGE "SB-PCL"> #<PACKAGE "SB-ALIEN"> #<PACKAGE "SB-ASSEM">
 #<PACKAGE "SB-BIGNUM"> #<PACKAGE "SB-C"> #<PACKAGE "SB-DEBUG">
 #<PACKAGE "SB-DISASSEM"> #<PACKAGE "SB-FASL"> #<PACKAGE "SB-FORMAT">
 #<PACKAGE "SB-GRAY"> #<PACKAGE "SB-THREAD"> #<PACKAGE "SB-MOP">
 #<PACKAGE "SB-PRETTY"> #<PACKAGE "SB-PROFILE"> #<PACKAGE "SB-UNIX">
 #<PACKAGE "SB-WALKER"> #<PACKAGE "SB-EVAL"> #<PACKAGE "SB-LOOP">
 #<PACKAGE "COMMON-LISP-USER">)
            rend la liste des packages qui utilisent le package COMMON-LISP
           en particulier, on voit que COMMON-LISP-USER utilise COMMON-LISP

PACKAGE-SHADOWING-SYMBOLS:

(package-shadowing-symbols *package*)
NIL rend la liste des symboles cachés (ici, il n'y en a pas dans le package courant)

DO-SYMBOLS: (macro) permet de faire une itération sur tous les symboles d'un package.

Exemple, une fonction qui rend tous les symboles internes d'un package:

(defun symboles-internes (package)
   (let ((résultat nil)) ;la liste résultat est initialisée à vide (NIL)
     (do-symbols (symb package) ;itération sur tous les symboles du package
       (when (eq (second (multiple-value-list (find-symbol (symbol-name symb) package))) :internal) ;quand la 2ème valeur rendue par FIND-SYMBOL est :internal...
(push symb résultat)))       ;...le symbole est ajouté à la liste résultat
     (delete-duplicates résultat))) ;on supprime les résultats dupliqués

Remarque: les symboles internes peuvent provenir d'un autre package. Si l'itérateur DO-SYMBOLS rencontre un tel symbole, il le prend en compte et va analyser
tous les autres symboles de l'autre package qui sont internes dans le package désigné. Ce symbole est donc rencontré une deuxième fois. Les autres symboles ne sont pas dupliqués. DELETE-DUPLICATES permet d'éviter la duplication dans le résultat.

(symboles-internes *package*)
(SYMB RÉSULTAT TOTO SYMBOLES-INTERNES)
         rend les symboles internes du package courant (COMMON-LISP-USER)

Notez que *PACKAGE* rend effectivement le package courant, mais si vous utilisez le nom du package courant ou son alias:

(symboles-internes "CL-USER")
(SYMB RÉSULTAT TOTO SYMBOLES-INTERNES)
      on obtient la même chose
      l'argument de la fonction SYMBOLES-INTERNES peut être un package ou son nom ou son alias

DEFPACKAGE: macro pour définir ses propres packages

Quand on définit un package, on peut préciser des options:
option::= (:nicknames nickname*)* |  ;préciser les alias
          (:documentation string) |  ;donner une documentation sur le package
          (:use package-name*)* |    ;préciser le nom des packages utilisés
          (:shadow {symbol-name}*)* |  ;indiquer les symboles cachés
          (:shadowing-import-from package-name {symbol-name}*)* |  ;indiquer les symboles cachés importés d'un autre package
          (:import-from package-name {symbol-name}*)* |  ;indiquer les symboles importés d'un autre package
          (:export {symbol-name}*)* |  ;indiquer les symboles qu'on peut exporter qui s'ajoute aux symbole hérités exportables
          (:intern {symbol-name}*)* |  ;indiquer les symboles internes qui s'ajoutent aux symboles hérités internes
  (:size integer)        ;indiquer le nombre approximatif de symboles contenus dans ce package (un simple indice d'efficacité)

Définissons (sans prendre beaucoup de précautions) un nouveau package avec une documentation:

(defpackage "P1" (:documentation "Mon premier package."))
#

Vérifions sa documentation:

(documentation (find-package "P1") 't)
"Mon premier package."          C'est bien cela!

Et "entrons" dans ce nouveau package:

(in-package "P1")
#<COMMON-LISP:PACKAGE "P1">

Observez que le prompt a changé: il était CL-USER>, il devient P1>.
Vérifions si P1 est bien le package courant:

*package*
; Evaluation aborted on #<UNBOUND-VARIABLE *PACKAGE* {B41F479}>.
              l'effet peut varier suivant l'implémentation; avec SLIME:
              une erreur provoque l'entrée dans le débogueur (ici: le message de sortie du débogueur)
             en effet, *PACKAGE* est une variable inconnue dans P1

Heureusement, on peut faire appel à COMMON-LISP-USER (ou son alias) depuis P1 en utilisant la notation "CL-USER::" :

CL-USER::*package*
#<COMMON-LISP:PACKAGE "P1">                 P1 est bien le package courant

Notez qu'on n'utilise pas de parenthèses ci-dessus.
CL-USER:: permet un transfert de package pour appeler la variable *package*.
Mais pour appeler une fonction, il faudra utiliser les parenthèse! (voir ci-dessous)

FBOUNDP: permet de vérifier que le symbole trouvé est bien celui d'une fonction.

(fboundp (find-symbol "SYMBOLES-INTERNES" "CL-USER")) ;pour voir si le symbole "SYMBOLES-INTERNES" est bien dans "CL-USER"
...erreur évidemment, ...

Vous remarquerez, dans ce qui suit, que la notation "CL-USER::" peut s'écrire en minuscules (sans les guillemets).
Par contre, si vous faites appel au package en tant qu'argument, vous écrivez en majuscules: "CL-USER" et avec les guillemets!

(cl-user::fboundp (cl-user::find-symbol "SYMBOLES-INTERNES" "CL-USER"))
COMMON-LISP:T           le symbole SYMBOLES-INTERNES est bien défini dans CL-USER...
 noter que le symbole T, n'existant pas dans P1, a pour préfixe COMMON-LISP:

(cl-user::fboundp (cl-user::find-symbol "SYMBOLES-INTERNES" "P1"))
COMMON-LISP:NIL   ...Mais le symbole SYMBOLES-INTERNES n'est pas défini dans P1

On peut l'importer dans P1:

(cl-user::import 'cl-user::symboles-internes "P1")
COMMON-LISP:T           et maintenant:

(symboles-internes "P1")
(SYMBOLES-INTERNES FIND-SYMBOL FBOUNDP)
  on constate que les symboles FIND-SYMBOL et FBOUNDP sont devenus (par erreur)                        internes dans P1
  on a plus besoin d'utiliser la notation CL-USER:: car:

(cl-user::find-symbol "SYMBOLES-INTERNES" "P1")
SYMBOLES-INTERNES
:INTERNAL ...SYMBOLE-INTERNES est interne à P1

noter que FIND-SYMBOL n'a pas été importé (on utilise donc cl-user::find-symbol).
Et là, il faut bien voir que le symbole interne FIND-SYMBOL qui se trouve dans P1 est un symbole quelconque (il n'est pas le symbole d'une fonction).

Importer de cette façon n'est pas très pratique si on a besoin de beaucoup de symboles.
Nous allons voir qu'on peut arranger cela. Mais d'abord:
Revenons au package COMMON-LISP-USER:

(in-package "CL-USER")
   ... Il fallait s'y attendre, IN-PACKAGE est une fonction inconnue dans P1
   on va donc procéder comme on vient de voir:

(CL-USER::in-package "CL-USER")
#<PACKAGE "COMMON-LISP-USER">
            le prompt redevient CL-USER>

Pour importer tous les symboles de COMMON-LISP dans le package P1, il suffit de l'indiquer comme suit:

(defpackage "P1"
  (:use :common-lisp))
  ...sauf que P1 est déjà défini et il y a un conflit entre ce nouveau P1 et l'ancien
pour redéfinir P1, il faut d'abord le supprimer:

(delete-package "P1")
T

(find-package "P1")
NIL      P1 a bien été supprimé
     on peut, maintenant, redéfinir le package P1:

(defpackage "P1"
   (:documentation "Mon deuxième package.")
   (:use :common-lisp))
  on indique le package utilisé dans P1 par :use
noter, aussi, qu'on a désigné le package utilisé par :common-lisp au lieu "COMMON-LISP"
les mots-clés permettent d'écrire en minuscules (le reader les convertit en majuscules)

Entrons dans ce nouveau package P1:

(in-package :p1)
#<PACKAGE "P1">F; le prompt a bien changé

(fboundp (find-symbol "SYMBOLES-INTERNES" "CL-USER"))
T                  il n'y a plus besoin de CL-USER:: devant FBOUNDP et FIND-SYMBOL

(fboundp (find-symbol "SYMBOLES-INTERNES" "P1"))
NIL           normal: SYMBOL-INTERNE a été défini dans CL-USER, pas dans P1

Ne soyez pas surpris, nous allons faire une série d'erreurs dans ce qui suit:
On décide d'importer SYMBOLE-INTERNES dans P1 (noter qu'il devrait y avoir un "s" à la fin de SYMBOLE)

(import :symbole-internes :p1)
T on peut se demander d'où vient ce symbole importé:
                quand on utilisait CL-USER::, on savait d'où il venait
ce qui est certain, c'est qu'on vient de créer un nouveau symbole interne dans P1

N'ayant pas vu l'erreur, on recherche les symboles internes de P1:

(symboles-internes "P1")
          ...erreur évidemment et on vient de créer un nouveau symbole interne dans P1
  ce symbole n'a rien à voir avec celui de CL-USER

Prenant conscience d'au moins une erreur, on décide de faire appel à CL-USER:: :

(import 'cl-user::symboles-internes "P1")
      le débogueur s'ouvre et nous signale le conflit entre les deux SYMBOLES-INTERNES:

IMPORT COMMON-LISP-USER::SYMBOLES-INTERNES causes name-conflicts
in # between the following symbols:
  COMMON-LISP-USER::SYMBOLES-INTERNES, P1::SYMBOLES-INTERNES
   [Condition of type SB-EXT:NAME-CONFLICT]
See also:
  Common Lisp Hyperspec, 11.1.1.2.5 [:section]

Restarts:
 0: [SHADOWING-IMPORT-IT] Shadowing-import COMMON-LISP-USER::SYMBOLES-INTERNES, uninterning SYMBOLES-INTERNES.
 1: [DONT-IMPORT-IT] Don't import COMMON-LISP-USER::SYMBOLES-INTERNES, keeping SYMBOLES-INTERNES.
 2: [RESOLVE-CONFLICT] Resolve conflict.
 3: [RETRY] Retry SLIME REPL evaluation request.
 4: [*ABORT] Return to SLIME's top level.
 5: [ABORT] Abort thread (#)

       ...On décide de résoudre le conflit: choix 2.
   On se retrouve dans le REPL, avec un nouveau choix à faire:

Select a symbol to be made accessible in package P1:
  1. COMMON-LISP-USER::SYMBOLES-INTERNES
  2. P1::SYMBOLES-INTERNES

Enter an integer (between 1 and 2): 1

T
On choisit 1 pour retrouver le symbole SYMBOLES-INTERNES correspondant à la fonction définie dans CL-USER

(fboundp (find-symbol "SYMBOLES-INTERNES" "P1"))
T      c'est correct

(symboles-internes :p1)
(SYMBOLES-INTERNES :SYMBOLE-INTERNES)
                       on retrouve bien nos deux symboles internes
       on ne l'avait pas remarqué, il y a ":" en préfixe devant celui qui est indésirable
       (je vous laisse chercher pourquoi: faites un petit retour en arrière)

Supprimons celui qui est en trop:

UNINTERN:  permet de supprimer un symbole dans un package optionnel (par défaut, c'est le package courant).

(unintern ':SYMBOLE-INTERNES)
T

(symboles-internes :p1)
(SYMBOLES-INTERNES) il nous reste le bon symbole

S'il y a beaucoup de symboles importés dans le package P1, on peut se demander d'où provient un certain symbole. Alors:

(symbol-package (find-symbol "SYMBOLES-INTERNES" "P1"))
#<PACKAGE "COMMON-LISP-USER">
  on voit que SYMBOLES-INTERNES provient du package COMMON-LISP-USER

Le fait de supprimer un symbole dans un package avec UNINTERN l'efface de la liste des symboles internes du package, mais ne le supprime pas vraiment.
C'est ce que nous allons voir:
Nous sommes toujours dans le package P1. Créons une liste:

(defparameter une-liste '(a b c))
UNE-LISTE     UNE-LISTE est définie

une-liste
(A B C) la liste contient bien (A B C)

(symboles-internes :p1)
(SYMBOLES-INTERNES B UNE-LISTE A C) quatre symboles se sont ajoutés dans P1

(unintern 'a :p1)
T on enlève le symbole A dans P1

(symboles-internes :p1)
(SYMBOLES-INTERNES B UNE-LISTE C) A a bien disparu

une-liste
(#:A B C) mais A persiste dans UNE-LISTE avec ce #: qui indique que A n'est pas un symbole                              interne à P1
        et:

(type-of (car une-liste))
SYMBOL            ...c'est bien un symbole...

(symbol-package (car une-liste))
NIL ...qui n'appartient à aucun package!

(set (car une-liste) 91)
91                       on peut attribuer une valeur à ce curieux symbole #:A
                           au passage, faites bien la différence entre SET et SETF

(car une-liste)
#:A avec SETF on obtiendrait, ici, 91

(symbol-value (car une-liste))
91                        pour avoir la valeur du symbole #:A
     ou bien:

(eval (car une-liste))
91

De même, si on importe un symbole dans un autre package avant de le désinterner du package courant:

(import 'C "CL-USER")
T on importe le symbole C dans CL-USER

(unintern 'C "P1")
T              ...puis on le désinterne du package courant P1

une-liste
(#:A B #:C) effectivement

(symbol-package 'cl-user::c)
NIL le symbole C est bien reconnu (sinon il serait créé)
mais il n'appartient pas à un package, et:

(symboles-internes "CL-USER")
(SYMBOLES-INTERNES COMMON-LISP-USER::SYMB COMMON-LISP-USER::|2013-06-26|
                   COMMON-LISP-USER::RÉSULTAT #:C)
  ...c'est un symbole interne de CL-USER

DO-EXTERNAL-SYMBOLS: permet de faire une itération sur les symboles exportables d'un package.

Définissons une fonction qui rend la liste des symboles "externes" d'un package (dans P1, cette fois):

(defun symboles-externes (package)
       "Rend la liste des symboles exportables du package"
      (let ((résultat nil))    ;variable résultat initialisée à NIL (liste vide)
(do-external-symbols (s package)     ;itération recherchant tous les symboles "external" du package
  (push s résultat))      ;chaque symbole trouvé est placé dans la liste résultat
résultat))      ;la liste résultat est rendue
SYMBOLES-EXTERNES

(symboles-externes :p1)
NIL              il n'y a pas de symboles externes dans P1

(symboles-externes :cl-user)
NIL              ...ni dans CL-USER

(symboles-externes :cl)
(DECLAIM ATAN
         RASSOC
         DEFINE-SYMBOL-MACRO
         FUNCTIONP
... il y a beaucoup de symboles externes dans COMMON-LISP (alias CL)
           (j'ai coupé la liste)

(length *)
978 ...il y en a 978!
* rend la dernière séquence

Voici une fonction qui choisit au hasard un symbole d'un package et affiche sa documentation si elle existe:

(defun aléa-doc-fonction (pack)
   "choisit une fonction exportable du package passé en argument, de façon aléatoire. Puis affiche
la fonction et sa documentation si elle existe (sinon NIL)"
   (let* ((liste (symboles-externes pack)) (symb (nth (random (length liste)) liste))) ;liste contient tous les symboles exportables dont 1 est tiré au hasard (symb)
     (if (fboundp symb)     ;si le symbole est une fonction...
(format t "Doc de la fonction ~a: ~a" symb (documentation symb 'function)) ;...on affiche la fonction et sa documentation
(aléa-doc-fonction pack))))   ;...sinon: appel récursif de la fonction aléa-doc-fonction

exemple:
(aléa-doc-fonction :cl)
Doc de la fonction DECF: The first argument is some location holding a number. This number is
  decremented by the second argument, DELTA, which defaults to 1.
NIL

Pour avoir la documentation sur les variables ou constantes du package:

(defun aléa-doc-var (pack)
   "choisit un symbole du package, passé en argument, de façon aléatoire. Puis affiche
la variable et sa documentation si elle existe (sinon NIL)"
   (let* ((liste (symboles-externes pack)) (symb (nth (random (length liste)) liste)))
     (if (not (fboundp symb))
(format t "Doc de la variable ~a: ~a" symb (documentation symb 'variable)
(aléa-doc-var pack))))

Vous constaterez que cette fonction est moins intéressante car beaucoup de variables ne sont pas documentées: dommage!
On peut regrouper les deux fonctions aléa-doc-fonction et aléa-doc-var en une seule, appelée avec un 2ème argument (requis ou optionnel) indiquant fonction ou variable.
Mais, attention, il y a quelques problèmes à résoudre. Je n'en dis pas plus. Et bon courage!

Tous les symboles exportables trouvés dans COMMON-LISP peuvent être utilisés dans P1 sans ajouter le préfixe CL::.
Ceci parce que, d'une part, ce sont des symboles exportables du package COMMON-LISP (alias CL) et, d'autre part:
le package COMMON-LISP est dans la liste des packages utilisés par P1.

(find-symbol "LABELS" :cl)
LABELS
:EXTERNAL LABELS, par exemple, est exportable dans COMMON-LISP

(package-use-list :p1)
(#<PACKAGE "COMMON-LISP">) package-use-list rend la liste des packages                                                utilisés par le package passé en argument.
Il y en a un seul ici: COMMON-LISP

Définissons un nouveau package P2:

(defpackage "P2")
#<PACKAGE "P2">

Et déplaçons-nous dans ce package:

(in-package :p2)
#<COMMON-LISP:PACKAGE "P2">

Aucunes fonctions crées précédemment ne peut être utilisées dans P2, à cet instant.

(CL-USER::import 'p1::symboles-internes :p2)
COMMON-LISP:T on importe SYMBOLES-INTERNES dans P2
noter qu'on utilise p1:: alors que SYMBOLES-INTERNES a été créé dans CL-USER
noter qu'on utilise CL-USER:: alors que IMPORT se trouve dans COMMON-LISP

(symboles-internes :p2)
(SYMBOLES-INTERNES)
  SYMBOLES-INTERNES peut, maintenant, être utilisé dans P2

Autre méthode qu'on va appliquer à SYMBOLES-EXTERNES:

EXPORT:

(CL::export 'p1::symboles-externes :p1)
COMMON-LISP:T        fait de SYMBOLES-EXTERNES, dans P1, un symbole exportable
                                     notez le préfixe CL:: (rappel: nous sommes dans P2)

(P1::symboles-externes :p1)
(P1:SYMBOLES-EXTERNES)
  obligation d'utiliser P1:: pour voir les symboles externes de P1
noter que P1: serait un préfixe suffisant car le symbole est devenu exportable

USE-PACKAGE:

(CL::use-package :p1 :p2)
COMMON-LISP:T
USE-PACKAGE permet d'utiliser les symboles exportables de P1 dans P2
là encore on utilise CL:: (on est toujours dans P2)
  et on pourrait se contenter de CL: puisque USE-PACKAGE fait partie des symboles                             exportables de COMMON-LISP

(symboles-externes :p1)
(SYMBOLES-EXTERNES) il n'est plus nécessaire d'utiliser P1:: en préfixe

Montrons que les symboles de COMMON-LISP peuvent être utilisés avec le préfixe CL: (et non CL::):

(CL:car P1::une-liste)
#:A effectivement, le premier élément de UNE-LISTE était ce symbole sans package
par contre on utilise P1:: car UNE-LISTE n'a pas été rendu exportable dans P1

Si on ne veut pas de préfixe, il faut imposer à P2 l'utilisation de COMMON-LISP:

(CL:use-package :cl :p2)
T si le débogueur s'ouvre c'est que vous avez utilisé, précédemment, des symboles sans le                       préfixe CL: ou CL::
mais vous savez résoudre ce conflit...

et, maintenant:

(* 2 6)
12 il n'y a pas besoin de CL: devant *

Et si vous ne voulez plus que P2 utilise COMMON-LISP:

(unuse-package :cl :p2)
COMMON-LISP:T

(* 2 6)
              entrée dans le débogueur:
The function P2::* is undefined...
                 le symbole * de COMMON-LISP n'est plus accessible...

(CL:* 2 6)
12     ... sauf en utilisant un préfixe...

(symboles-internes :p2)
(SYMBOLES-INTERNES *)     ... et P2 contient un nouveau symbole interne *

Complétons nos connaissances concernant le statut des symboles (INTERNAL, EXTERNAL, INHERITED ou autre...)
Le mieux est d'ouvrir une nouvelle session utilisateur.
Dans SLIME-REPL "," pour solliciter une commande,
puis "quit" + "entrée" pour quitter SLIME,
puis Alt+X SLIME pour relancer SLIME-REPL

(defpackage "P1"
   (:documentation "Mon troisième P1.")
   (:use :common-lisp))
# on définit un package P1
  avec l'intention d'utiliser les fonctions de COMMON-LISP

(defpackage "P2")
# on définit un package P2

(in-package :p1)
# P1 devient le package courant

Le prompt est bien maintenant P1. Surveillez un peu le prompt dans ce qui suit.
Ceci pour garder à l'esprit dans quel package vous travaillez.
Et sans oublier que P1 utilise CL.

(export (intern "S1" :p2) :p2)
T           on crée un symbole interne dans P2 et on le rend exportable depuis P2

(find-symbol "S1" :p2)
P2:S1      c'est bien un symbole de P2
:EXTERNAL   il est bien exportable (relativement à P2)

(find-symbol "S1" :p1)
NIL
NIL           S1 n'est pas accessible dans P1

(import 'p2:s1 :p1)
T                   on importe le symbole S1 de P2 dans P1

(find-symbol "S1" :p1)
S1
:INTERNAL   S1 a le statut interne dans P1
                       Il n'est pas exportable à partir de P1

(find-symbol "S1" :p2)
S1               le préfixe P2: a disparu: S1 est accessible dans P1
:EXTERNAL  le statut de S1 n'a pas changé par rapport à P2

Recommençons pour un symbole nommé SB, mais on le "désinterne" de P2, après l'avoir importé dans P1:

(export (intern "SB" :p2) :p2)
T

(import 'p2:sb :p1)
T

(unintern 'sb :p2)
T

(find-symbol "SB" :p2)
NIL     le symbole n'existe plus dans P2
NIL     il n'a pas de statut par rapport à P2 (logique s'il n'existe plus)

(find-symbol "SB" :p1)
#:SB          le symbole est toujours présent dans P1 mais n'appartient à aucun package comme le                            montre le préfixe #:
:INTERNAL    son statut est toujours interne par rapport à P1

Cas particulier des symboles du package KEYWORD:

(find-symbol "KEY" :keyword)
:KEY       rend le symbole préfixé par ":" (s'il existe, sinon NIL)
:EXTERNAL   son statut est exportable

KEYWORDP:

(keywordp :rest)
T rend T si le symbole appartient bien au package KEYWORD
ne pas oublier le préfixe ":" devant le symbole

Revenons au symbole S1: interne par rapport à P1 et externe par rapport à P2.

(eq (find-symbol "S1" :p1) (find-symbol "S1" :p2))
T   que ce soit par rapport à P1 ou par rapport à P2, c'est le même symbole!
ce qui fait que si on rend P2 utilisable par P1, il n'y aura pas conflit:

(use-package :p2 :p1)
T             effectivement: aucune ambiguïté à lever

Le statut de S1 a-t-il changé par rapport à P1? (il pourrait devenir hérité - inherited en anglais)

(find-symbol "S1" :p1)
S1
:INTERNAL           non, il a gardé son statut initial: interne

(export (intern "S2" :p2) :p2)
T voici un deuxième symbole S2 dans P2 exportable

(intern "S2" :p1)
S2                 le symbole S2 est accessible via P1 qui utilise P2 (il n'y a pas création d'un                                           nouveau symbole dans P1)
:INHERITED        avec INTERN le symbole n'est pas IMPORTé
               son statut est hérité (inherited)

(eq (find-symbol "S2" :p1) (find-symbol "S2" :p2))
T   là encore, il n'y a qu'un seul symbole S2 et le statut dépend du package

MAKE-SYMBOL:

(make-symbol "S2")
#:S2     permet de créer un nouveau symbole n'appartenant à aucun package
    à ce niveau, il ne peut pas y avoir conflit

    par contre:

(import * :p1)  ;* pointe sur le dernier résultat (donc #:S2)
           entrée dans le débogueur:
IMPORT #1=#:S2 causes name-conflicts in # between
the following symbols:
  #1#, P2:S2
  il y a un conflit entre #:S2 et S2 du package P2 accessible via P1

Revenez au toplevel.
Autres cas de conflit:

(intern "S3" :p1)
S3
NIL          on crée un symbole S3 dans P1...

(intern "S3" :p2)
P2::S3
NIL         ...puis un symbole de même nom dans P2

        on demande à ce que ce dernier soit exportable dans P2:

(export * :p2)
          le débogueur s'ouvre:
EXPORT P2::S3 causes name-conflicts in # between
the following symbols:
  P2::S3, P1::S3
           P1 utilisant P2, on comprend qu'il y a un conflit

Même problème si P1 utilise deux packages contenant respectivement deux symboles exportables de même nom:

(export (intern "S4" :p2) :p2)
T on crée un symbole S4 exportable dans P2...

(export (intern "S4" (defpackage :p3)) :p3)
T ... Puis un symbole de même nom exportable dans P3

On sait que P1 utilise P2 et on veut que P1 utilise P3:

(use-package :p3 :p1)
        le débogueur s'ouvre:
USE-PACKAGE # causes name-conflicts in
# between the following symbols:
  P3:S4, P2:S4
il y a bien conflit de nom

Pour éviter le conflit, il suffisait d'écrire (import "'p2:S4"... au lieu de intern "S4"... pour P3.
Ainsi, les deux symboles de P2 et P3 sont les mêmes.

SHADOW:

(shadow "S5" :p1)
T     définit un symbole S5 occultant dans P1
    ce symbole prend l'avantage sur tout autre symbole de même nom
    il occulte les autres

PACKAGE-SHADOWING-SYMBOLS:

(package-shadowing-symbols :p1)
(S5) rend la liste des symboles occultants dans P1

(find-symbol "S5" :p1)
S5
:INTERNAL      le symbole S5 a, ici, le statut interne (mais on pourrait le rendre externe)

(export (intern "S5" :p2) :p2)
T on définit un symbole de même nom dans P2 et on le rend exportable
rappelons que P1 utilise P2: il devrait y avoir une erreur!
mais S5 de P1 occultant P2:S5, l'erreur est évitée

(eq 's5 'p2:s5)
NIL ... D'ailleurs, les deux symboles ne sont pas égaux.

Revenons sur une erreur précédente concernant le symbole S4:
Rappelez-vous: nous avons créé deux symboles S4 l'un dans P2, l'autre dans dans P3.
Nous les avons rendus exportables dans les deux cas.
Nous avons voulu rendre P3 utilisable par P1.
Sachant que P1 utilise P2, le (use-package :p3 :p1) entraine un conflit.
De même, (import 'p3::s4 :p1) entraine un conflit.
On peut rendre S4 occultant dans P1 en utilisant:

SHADOWING-IMPORT:

pour bien voir les choses, donnons des valeurs à S4:

(setf p2:s4 "Je suis dans le package P2.")

(setf p3:s4 "Je suis dans le package P3.")
                 suivant l'implémentation, on obtient une alerte, mais la valeur est attribuée

(shadowing-import 'p3::s4 :p1)
T  le symbole S4 est importé dans P1 et placé dans la liste des symboles occultants
 il n'y a pas conflit de noms

(find-symbol "S4" :p1)
S4
:INTERNAL            il a le statut interne

(package-shadowing-symbols :p1)
(S4 S5)   il est bien dans la liste des symboles occultants de P1

s4
"Je suis dans le package P3." c'est bien le symbole issu de P3

p2:s4
"Je suis dans le package P2." si nécessaire on peut faire appel au symbole S4 de P2
                          il suffit d'utiliser le préfixe p2:

RENAME-PACKAGE:

(rename-package :p3 "P4" '("EX-P3"))
#
#<PACKAGE "P4">
     le package P3 est renommé en P4 (P3 est supprimé)
     des alias de P4 sont proposés dans une liste en option (quotée évidemment): ici, EX-P3

(package-nicknames :p4)
("EX-P3")    P4 a bien pour alias EX-P3

(find-package "P3")
NIL       P3 n'existe plus...

(find-symbol "S4" "P4")
S4
:EXTERNAL ... mais P4 a conservé les symboles de P3...

s4
"Je suis dans le package P3." certains défauts peuvent apparaître
          (P3 n'existe plus)
__________________________

On relance, ici, une nouvelle session SLIME.
Dans SLIME-REPL "," pour solliciter une commande,
puis "quit" + "entrée" pour quitter SLIME,
puis Alt+X SLIME pour relancer SLIME-REPL

(defpackage :mon.package.courriel-db
  (:use :common-lisp))
#<PACKAGE "MON.PACKAGE.COURRIEL-DB">
définit un package nommé MON.PACKAGE.COURRIEL-DB
on utilise, ici, des mots-clés, ce qui permet d'écrire en minuscules,
le reader les convertissant en majuscules
ou bien:

(defpackage "MON.PACKAGE.COURRIEL-DB"
  (:use "COMMON-LISP"))
les vrais noms de la plupart des symboles et des packages sont en                                                           majuscules

*package*
#<PACKAGE "COMMON-LISP-USER">
nous sommes dans le package COMMON-LISP-USER

(defun hello-world ()
  (format t "Salut du package COMMON-LISP-USER~%"))
HELLO-WORLD                  définition de la fonction HELLO-WORLD dans le package CL-                                                               USER

(hello-world)
Salut du package COMMON-LISP-USER
NIL c'est bien la fonction du package CL-USER

(in-package :mon.package.courriel-db)
#<PACKAGE "MON.PACKAGE.COURRIEL-DB">
                  fait du package nommé le package courant pour y lire le code
                   (dans SLIME le prompt a changé: COURRIEL-DB>)

*package*
#<PACKAGE "MON.PACKAGE.COURRIEL-DB">
      nous sommes, maintenant, dans le package MON.PACKAGE.COURRIEL-DB

(defun hello-world ()
   (format t "Bonjour du package COURRIEL-DB~%"))
HELLO-WORLD             définition de la fonction HELLO-WORLD dans le nouveau package

(hello-world)
Bonjour du package COURRIEL-DB
NIL c'est bien la fonction du package COURRIEL-DB

(in-package :cl-user)
#<PACKAGE "COMMON-LISP-USER">
on retourne dans le package CL-USER

(hello-world)
Salut du package COMMON-LISP-USER
NIL on retrouve la fonction du package CL-USER

En travaillant sur la base de données COURRIEL, on pourrait avoir besoin de fonctions de stockage et d'extraction de texte qui n'ont rien à voir avec le courriel, mais peuvent être utiles pour d'autres programmes. D'où un nouveau package avec exportation de certains noms pour utilisation dans d'autres packages:

(defpackage :mon.package.text-db
  (:use :common-lisp)
  (:export :ouverture-base
   :enregistrer
   :classer))

Les packages utilisant le package TEXT-DB peuvent utiliser ces fonctions: par exemple le package COURRIEL-DB, si on le modifie.

(defpackage :mon.package.courriel-db
  (:use :common-lisp :mon.package.text-db))

Supposons que l'on trouve une bibliothèque (par exemple: COM.BIBLIO.COURRIEL) contenant des fonctions manipulant les messages de courriel. On pourrait indiquer ce package à la suite du mot-clé :use. Mais, supposons, encore, que l'on ait besoin que d'une seule fonction (controle-adresse, par exemple) dans ce package et que les autres symboles entrent en conflit avec les noms déjà utilisés dans notre code. Dans ce cas, on utilise la clause d'importation :import-from.

(defpackage :mon.package.courriel-db
  (:use :common-lisp :mon.package.text-db)
  (:import-from :com.biblio.courriel :controle-adresse))

On peut importer, ainsi, plusieurs symboles provenant d'un même package à la suite du nom du package.
On peut, aussi, écrire plusieurs fois le mot-clé :import-from si on veut importer des symboles provenant de différents packages.

Plaçons-nous dans une situation très différente: on veut utiliser un tas de noms de fonctions et de classes provenant d'un autre package COM.BIBLIO.TEXTE (par exemple), mais un nom entre en conflit dans notre code: INDEXAGE, par exemple.

(defpackage :mon.package.courriel-db
  (:use
   :common-lisp
   :mon.package.text-db
   :com.biblio.texte)
  (:import-from :com.biblio.courriel :controle-adresse)
  (:shadow :indexage)) ;INDEXAGE est caché pour ne pas entrer en conflit

Cette clause :shadow ajoute un nouveau symbole dont le nom est INDEXAGE dans la table de correspondance nom-symbole du package MON.PACKAGE.COURRIEL-DB. Ainsi le reader, en trouvant le nom INDEXAGE, prendra le symbole dans la table de correspondance du package MON.PACKAGE.COURRIEL-DB et non le package dont il hérite (COM.BIBLIO.TEXTE). Il y a, aussi, une liste des symboles cachés pour interdire l'utilisation d'un INDEXAGE provenant d'un nouveau package placé dans la clause :use.
On peut,aussi, avoir le même nom exporté par deux packages. la clause :shadow permet de ne pas utiliser ce nom. Mais si on désire utiliser l'un des deux symboles, on prendra la clause :shadowing-import-from. Supposons que le package COM.BIBLIO.TEXTE contienne le nom ENREGISTRER qu'on prévoit d'utiliser, mais qui entre en conflit avec celui de MON.PACKAGE.TEXT-DB, on pourra écrire:

(defpackage :mon.package.courriel-db
  (:use
   :common-lisp
   :mon.package.text-db
   :com.biblio.texte)
  (:import-from :com.biblio.courriel :controle-adresse)
  (:shadow :indexage)
  (:shadowing-import-from :mon.package.text-db :enregistrer))

Un package doit être défini avant de charger ou de compiler un fichier contenant le IN-PACKAGE commutant vers le package en question. S'il fait référence à d'autres packages, ceux-ci doivent être définis avant.
Par exemple, le DEFPACKAGE de MON.PACKAGE.TEXT-DB doit être évalué avant le DEFPACKAGE de MON.PACKAGE.COURRIEL-DB.
La première chose à faire est de mettre les DEFPACKAGEs dans des fichiers séparés du code qui sera lu dans ces packages.
Certains préfèrent créer un fichier foo-package.lisp pour chaque package, d'autres préfèrent créer un unique fichier packages.lisp qui contient tous les DEFPACKAGEs d'un groupe de packages connexes. Dans le premier cas, il faut prendre soin de charger les fichiers dans l'ordre respectant les dépendances entre packages.
Le premier fichier contenant les DEFPACKAGEs devrait commencer par (in-package "COMMON-LISP-USER"). Les autres fichiers devraient contenir un IN-PACKAGE du package dans lequel le code qui suit doit fonctionner (un seul pour ne pas perturber les utilisateurs et ne pas rendre confus certains outils).
Il peut y avoir des conflits dans les noms de packages, en particulier si on utilise des bibliothèques tierces ou si on diffuse son code. Il est bon, alors, de suivre des conventions comme, par exemple, les noms Java-style.

Le système de package n'est pas compliqué, mais il comporte des épines que la plupart des nouveaux programmeurs Lisp ont du mal à se retirer du pied:

1ère épine:
on essaie une fonction d'une bibliothèque:
(foo)             erreur, on tombe dans le débogueur
      on avait oublié d'utiliser le package de la bibliothèque:
(use-package :foolib)  on retombe dans le débogueur avec le signalement d'un conflit sur le symbole FOO
          que s'est-il passé?
Au premier appel de foo le reader catalogue le nom foo dans CL-USER, puis l'évaluateur découvre que ce nouveau symbole n'est pas une fonction (1ère erreur). ce nouveau symbole entre en conflit avec celui exporté du package FOOLIB.
Il aurait fallu utiliser le package FOOLIB, puis faire appel à foo et le reader aurait lu le symbole hérité de FOOLIB.
A ce stade, rien n'est perdu, car le débogueur propose de continuer en décataloguant foo de COMMON-LISP-USER:
0: [CONTINUE] Unintern the conflicting symbols from the 'COMMON-LISP-USER' package.
Le package CL-USER est placé dans son état antérieur et l'héritage de FOOLIB est autorisé.
Le problème est le même quand on définit un package en oubliant d'indiquer les packages dont il hérite: si on redéfinit le package correctement, l'erreur persiste,mais le débogueur est là pour nous proposer la solution. Il faudra recompiler le code du package pour que les noms hérités soient bien référencés.

2ème épine:
Supposons, maintenant, qu'on veuille écrire du code dans son propre package avec utilisation de FOOLIB pour avoir accès à la fonction foo. FOOLIB exporte, aussi, d'autres symboles, bar, par exemple. Si on utilise bar dans son code, la nouvelle définition recouvrira celle héritée de FOOLIB et lisp ne se plaindra pas. Il n'y aura pas d'erreur, juste un message d'alerte dans certaines implémentations: "redefining BAR, originally defined in ...". Voilà une épine moins visible, dont
il faudra tenir compte. Pour retrouver l'ancienne définition, il faut recharger FOOLIB avec LOAD.

Dernière épine:
On vient de changer de package (pour essayer son nouveau code, par exemple) et on décide de quitter Lisp: on essaie (quit).
Cependant, quit est un nom de package d'implémentation spécifiques parfois utilisé par COMMON-LISP-USER. Il faut revenir à CL-USER pour quitter ou utiliser exit.

Exemple de fichier contenant des utilitaires dans un package:

;;;début du fichier util.lisp
(defpackage "UTILITAIRES" ;le nom du package
   (:use "COMMON-LISP") ;on utilise le package Common-Lisp
   (:nicknames "UTIL")) ;l'alias du package est UTIL
(in-package :utilitaires) ;changement de package
;;;fin du fichier

Si vous chargez un tel fichier avec (load "...), vous aurez la surprise de vous retrouver
dans le package CL-USER, et non dans "UTILITAIRES".
C'est comme quand on referme un LET: ce qui est dedans a disparu.
Pour vous en persuader, rajouter quelques lignes à la fin du fichier:

;;;début du fichier util.lisp
(defpackage "UTILITAIRES" ;le nom du package
   (:use "COMMON-LISP") ;on utilise le package Common-Lisp
   (:nicknames "UTIL")) ;l'alias du package est UTIL
(in-package :utilitaires) ;changement de package
(format t "Je suis dans le package UTIL.~%")
(format t "Il n'y a pas de prompt.~%")
(format t "Une commande (read) me demande d'entrer un nombre: ~%")
(defparameter nb (read))
(in-package :cl-user)
(format t "je suis dans le package CL-USER.~%")
;;;fin du fichier

Notez au passage la variable NB qui prend la valeur lue par (read).
Enregistrez ce fichier avec le nom "util.lisp".
Retournez dans SLIME-RPEL et tapez:

(load "chemin/util.lisp") ;bien sûr, chemin doit indiquer la suite des répertoires pour arriver au fichier.

Vous devriez obtenir un affichage du genre:

Je suis dans le package UTIL.
Il n'y a pas de prompt.
Une commande (read) me demande d'entrer un nombre: ;vous tapez 16 ENTREE par exemple.
16
je suis dans le package CL-USER.
T

Au prompt, tapez nb.
Vous obtenez une erreur car NB est inconnu du package CL-USER.
Allez dans le package UTIL:

(in-package :util)

Le prompt UTIL apparaît.
Puis tapez nb:
Vous obtenez 16, valeur que vous aviez tapée.
Le symbole NB est donc reconnu dans le package UTIL.


LOOP et les packages:

Pour voir tous les symboles du package actuel (ici: COMMON-LISP-USER):

(loop for s being the symbols in *package*
     do (format t "~a - " s))
BAR - FINALLY - X - V - REPEAT - K - VALEUR - I - FOR - GUS - S - G - FOO - HASH-VALUE - DOWNTO - F - FROM - COLLECTING - DOWNFROM - THEN - H - TOTO - ABOVE - OF - CL-USER - EACH - ACROSS - IN - BAZ - E - BY - N - 2011-10-19 - BEING - B - A - CLÉ - BELOW - EXIT - HASH-KEY - USING - TO - HASH-KEYS - SLIME - D - SYMBOLS - COLLECT - J - HASH-VALUES - *H* - ITEM - UPTO - C - ON - ...
NIL                     le résultat (trop long) a été coupé

Le résultat, ci-dessus, donne tous les symboles venant de "COMMON-LISP" plus tout ceux qui ont été utilisés dans la session "CL-USER".

(loop for s being the present-symbols in *package*
     do (format t "~a - " s))
BAR - FINALLY - X - V - REPEAT - K - VALEUR - I - FOR - GUS - PRESENT-SYMBOLS - S - G - FOO - HASH-VALUE - DOWNTO - F - FROM - COLLECTING - DOWNFROM - THEN - H - TOTO - ABOVE - OF - CL-USER - EACH - ACROSS - IN - BAZ - E - BY - N - 2011-10-19 - BEING - B - A - CLÉ - BELOW - EXIT - HASH-KEY - USING - TO - HASH-KEYS - SLIME - D - SYMBOLS - COLLECT - J - HASH-VALUES - *H* - ITEM - UPTO - C - ON - FIBO -
NIL

S'affichent ,cette fois, tous les symboles utilisés pendant la session dans "CL-USER" à l'exception des symboles hérités. Donc ceux qui sont INTERNES au package "CL-USER".

(loop for s being the external-symbols in *package*
     do (format t "~a - " s))
NIL

Il n'y a aucun symbol exportable dans "CL-USER".
Sauf si on décide de faire:

(export 'S :cl-user)
T
par exemple.

Chapitre suivant: LES ERREURS et LES INTERRUPTIONS.

mardi 3 mars 2015

17-LES TYPES * Retour sur LES FERMETURES et LES BLOCKS * LES DOCUMENTATIONS.

>>>>
LES TYPES:

(defun foo (x)
  (declare (type float x))
  (format t "Voici un nombre flottant: ~a." x)
  'fin)
FOO définit une fonction contenant une déclaration de type
l'argument de foo doit être un nombre flottant

(foo pi)
Voici un nombre flottant: 3.141592653589793d0.
FIN pi est bien un nombre flottant

(foo 3/2)
; Evaluation aborted on #. 3/2 est un rationnel, donc erreur

(type-of s)
ESSAI S est du type ESSAI (ESSAI étant précédemment définit comme une structure)

(type-of 3)
(INTEGER 0 536870911) 3 est un entier

(type-of #C(3 2))
(COMPLEX (INTEGER 2 3)) #C(3 2) est un nombre complexe et les parties réelles et imaginaires sont des entiers.

(type-of #C(3 2.0))
(COMPLEX (SINGLE-FLOAT 2.0 3.0)) #C(3 2.0) est un nombre complexe et les parties réelles (après conversion) et imaginaires sont des flottants simples.

(type-of 2/3)
RATIO 2/3 est un rationnel non entier
RATIO est un type propre au langage LISP qui permet de donner un résultat                                         sous sa forme exacte

Donc TYPE-OF rend le type de l'objet passé en argument.

TYPEP:

(typep *x* 'array)
T rend vrai car *x* a été défini comme un tableau précédemment
noter le quote ' devant array, sinon array est pris comme une variable, d'où: erreur
rend NIL si *x* n'est pas un tableau

Suivant le cas, pour contrôler le type on utilise les mots (quotés) suivants:
array   float           package          sequence           bit-vector                 function
pathname short-float   character   hash-table         random-state            single-float
complex        integer        ratio stream               condition   long-float
rational        string          cons                 null                    readtable symbol     double-float
number       restart        vector               fixnum              t

LES FERMETURES:

Quelques exemples:

(let ((a 0))
  (defun compte ()
    (format t "a = ~a~%" (incf a)))
  (defun réinitialisation (&optional (néo-a 0))
    (setf a néo-a)
    (format t "a = ~a~%" a)))
RÉINITIALISATION a est une variable locale et les fonctions compte et réinitialisation sont des fermetures car définies dans un LET. la variable locale est évidemment inaccessible.

(compte)
a = 1
NIL au 1er appel à compte, A est incrémenté et le résultat affiché

(compte)
a = 2
NIL idem

(réinitialisation -1)
a = -1
NIL à l'appel de la fonction réinitialisation, la valeur optionnelle de a est -1 (0 par défaut),
valeur ensuite affichée

(compte)
a = 0
NIL à l'appel de compte, a est incrémenté avant d'être affiché

__________________________________________________________________________

Exemple d'utilisation de fermetures pour montrer le fonctionnement de BLOCK et RETURN-FROM:

(defparameter a 0)              a doit être définie lors de l'appel à la fonction anonyme invoquée par
       FUNCALL dans ce qui suit:

(let ((a 0))
  (format t "Entrée dans LET~%")
  (defun compte () ;1ère fermeture (utilisée pour incrémenter a)
    (format t "a = ~a~%" (incf a)))
  (defun réinitialisation (&optional (néo-a 0)) ;2ème fermeture (pour réinitialiser a)
    (setf a néo-a)
    (format t "a = ~a~%" a))
  (defun foo () ;3ème fermeture pour voir les blocs
    (if (> a 4) (réinitialisation)) ;a repasse à 0 si a>4
    (format t " Entrée dans FOO~%")
    (block k ;un bloc k dans la fonction foo
      (format t "  Entrée dans le bloc k~%")
      (bar #'(lambda () (if (= a 3) (progn (format t "  Sortie conditionnelle du bloc k~%") (return-from k)))))
      (format t "  Sortie du bloc k~%"))
    (format t " Sortie de FOO~%")
    (compte))
  (format t "Sortie du LET~%"))

(defun bar (fn) ;cette fonction est appelée dans la fermeture foo
  (format t "   Entrée dans BAR~%")
  (block l ;un bloc l dans la fonction bar
    (format t "    Entrée dans le bloc l~%")
    (baz #'(lambda () (if (= a 2)
  (progn (format t "    Sortie conditionnelle du bloc l~%") (return-from l))
  (funcall fn))))   ;invocation de lambda (fn) par FUNCALL, si a différent de 2
    (format t "    Sortie du bloc l~%"))
  (format t "   Sortie de BAR~%"))

(defun baz (fn) ;cette fonction est appelée dans la fonction bar
  (format t "     Entrée dans BAZ~%")
  (block m ;un bloc m dans la fonction baz
    (format t "      Entrée dans le bloc m~%")
    (funcall fn) ;invocation inconditionnelle de lambda (fn) par FUNCALL
    (format t "      Sortie du bloc m~%"))
  (format t "     Sortie de BAZ~%"))

Pour a = 3, on obtient à l'appel de foo (en utilisant réinitialisation et compte préalablement):

(foo)
 Entrée dans FOO
  Entrée dans le bloc k
   Entrée dans BAR
    Entrée dans le bloc l
     Entrée dans BAZ
      Entrée dans le bloc m
  Sortie conditionnelle du bloc k on voit, ici, que BAR, bloc l, BAZ et bloc m sortent, également,                                                               de la pile
 Sortie de FOO
NIL
________________________________________________________________________________________________________________________
DOCUMENTATION: sur les fonctions et les variables.

(defun la-fonction (x)
  "rend le carré du nombre passé en argument" ;documentation sur la fonction
  (if (numberp x)
      (print (* x x))
      "L'argument doit être un nombre"))
LA-FONCTION

(defvar *la-variable* 15 "Une variable globale")
*LA-VARIABLE*                   définition d'une variable globale avec documentation

(apropos 'la-)
*LA-VARIABLE* (bound)
LA-
LA-FONCTION (fbound)
SB-IMPL::VANILLA-OPEN-ERROR
; No value donne des indications sur les variables et fonctions commençants par la-

(documentation 'la-fonction 'function)
"rend le carré du nombre passé en argument" rend la documentation de la-fonction

(documentation '*la-variable* 'variable)
"Une variable globale" rend la documentation de *la-variable*

Le 2ème argument (quoté) passé à DOCUMENTATION peut être function, variable, structure, type, setf et T.

Pour connaître la documentation de plusieurs fonctions contenue dans une liste:

(dolist (x '(cons car cdr)) (print x) (print (documentation x 'function)))
CONS
"Return a list with SE1 as the CAR and SE2 as the CDR."
CAR
"Return the 1st object in a list."
CDR
"Return all but the first object in a list."
NIL

DESCRIBE:

(describe 'la-fonction)
COMMON-LISP-USER::LA-FONCTION
  [symbol]

LA-FONCTION names a compiled function:
  Lambda-list: (X)
  Derived type: (FUNCTION (T)
                 (VALUES (OR NUMBER (SIMPLE-ARRAY CHARACTER (30)))
                         &OPTIONAL))
  Documentation:
    rend le carré du nombre passé en argument
  Source form:
    (SB-INT:NAMED-LAMBDA LA-FONCTION
        (X)
      (BLOCK LA-FONCTION
        (IF (NUMBERP X)
            (PRINT (* X X))
            "L'argument doit être un nombre")))
rend une description de la fonction et de toutes les informations associées


INSPECT: pour inspecter les propriétés d'un objet.

(inspect 'car)

The object is a SYMBOL.
0. Name: "CAR"
1. Package: #
2. Value: "unbound"
3. Function: #
4. Plist: NIL
> 0                             ici, on demande des renseignements sur le nom

The object is a VECTOR of length 3.
0. #\C
1. #\A
2. #\R
> 1

The object is an ATOM:
  #\A
> q                            on quitte INSPECT

; No value

Voici quelques raccourcis utiles, sous SLIME, liés à la documentation sur un symbole:

Placer le curseur sur le symbole d'abord, puis:
C-c C-d d donne la même chose que DESCRIBE et l'affiche dans un tampon (EMACS).
C-c C-D f décrit la fonction dans un tampon.
C-c C-d h ouvre l'hyperspec dans un navigateur à la page correspondant au symbole.
C-c C-d a recherche un apropos concernant le terme demandé dans le mini-buffer.

C-c I lance SLIME inspector. Entrer l'expression à inspecter (ex: (fac 5)), et valider.
    se déplacer sur l'objet à détailler et valider. l pour revenir en arrière.
q pour quitter l'inspecteur.

Prochainement:  LES SYMBOLES.