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">
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: #
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">
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)
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*
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)
(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 #
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
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">
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">
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
(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))
#
avec l'intention d'utiliser les fonctions de COMMON-LISP
(defpackage "P2")
#
(in-package :p1)
#
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 #
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 #
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 #
#
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))
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*
(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*
(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)
(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.