First complete Org draft.
This commit is contained in:
parent
ef2b97c65a
commit
04f2cf0826
@ -2045,3 +2045,346 @@
|
|||||||
following toy example. We start a new Lisp session, allocate a package
|
following toy example. We start a new Lisp session, allocate a package
|
||||||
="P"= and experiment with symbols named ="A"= and ="B"= in the packages
|
="P"= and experiment with symbols named ="A"= and ="B"= in the packages
|
||||||
="CL-USER"= and ="P"=.
|
="CL-USER"= and ="P"=.
|
||||||
|
|
||||||
|
#+BEGIN_SRC lisp :session pcl :package "P1" :exports both :results value verbatim
|
||||||
|
(in-package "CL-USER")
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
#+BEGIN_SRC lisp :session pcl :exports both :results value verbatim
|
||||||
|
(defpackage "P" (:use :cl))
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
#+BEGIN_SRC lisp :session pcl :exports both :results value verbatim
|
||||||
|
(setf my-list '(a b p::a p::b))
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
#+BEGIN_SRC lisp :session pcl :exports both :results value verbatim
|
||||||
|
(shadow 'p::a "CL-USER")
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
#+BEGIN_SRC lisp :session pcl :exports both :results value verbatim
|
||||||
|
(shadowing-import 'p::b "CL-USER")
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
#+BEGIN_SRC lisp :session pcl :exports both :results value verbatim
|
||||||
|
(package-shadowing-symbols "CL-USER")
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
#+BEGIN_SRC lisp :session pcl :exports both :results value verbatim
|
||||||
|
(eq 'a 'p::a)
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
#+BEGIN_SRC lisp :session pcl :exports both :results value verbatim
|
||||||
|
(eq 'b 'p::b)
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
#+BEGIN_SRC lisp :session pcl :exports both :results value verbatim
|
||||||
|
my-list
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
If we =shadow= the symbol =p::a= in ="CL-USER"=, the symbol =a= remains
|
||||||
|
different from the symbol =p::a=. In contrast, if we =shadowing-import= the
|
||||||
|
symbol =p::b= in ="CL-USER"=, then the symbols =b= and =p::b= are equal. In
|
||||||
|
a sense, the expression =(shadow ’p::a "CL-USER")= is cheating because the
|
||||||
|
presence of ='p::'= has no role whatsoever, for only the name of the symbol
|
||||||
|
=p::a= is used and this name is the same as the name ="A"= of =a=, that is,
|
||||||
|
because of the default package, =cl-user::a=. The symbol defined by ="A"= via
|
||||||
|
the package ="CL-USER"= remains unchanged, in particular, it remains
|
||||||
|
different from the symbol =p::a=.
|
||||||
|
|
||||||
|
In contrast, if =shadowing-import= is used, an =import= operation is induced,
|
||||||
|
so that the symbol =p::b= becomes directly accessible via ="CL-USER"=. The
|
||||||
|
previous symbol $\rho:($="CL-USER"=$)($="B"=$)$ has therefore been
|
||||||
|
/uninterned/ and is now unreachable by =find-symbol=. However, it is still
|
||||||
|
visible in the list named =list= as the homeless symbol =#:B=.
|
||||||
|
|
||||||
|
The conclusion of our test is this:
|
||||||
|
|
||||||
|
- The symbol =cl-user::a= is unchanged but added to $ShgS($ ="USER"= $)$.
|
||||||
|
- The symbol =cl-user::b= is almost unchanged, but no longer accessible via
|
||||||
|
="CL-USER"=. The only change is its =symbol-package= becomes =nil=, because
|
||||||
|
the symbol has been uninterned from its home-package.
|
||||||
|
- The symbol =p::a= remains unchanged; only its name has been used for
|
||||||
|
something which does not really concern it.
|
||||||
|
- The symbol =p::b= remains unchanged, except that it is now directly
|
||||||
|
accessible via ="CL-USER"= and now printed as =B= in the value of =list=.
|
||||||
|
|
||||||
|
** (use-package $q$ $p$)
|
||||||
|
|
||||||
|
Unless $q$ is already in $U(p)$, the package $q$ is added to the
|
||||||
|
=package-use-list= of $p.$
|
||||||
|
|
||||||
|
Lisp checks beforehand for possible name conflicts. A symbol
|
||||||
|
$sm \in PrS(p)$ could collide with a /different/ symbol $sm'\in ExS(q)$ if
|
||||||
|
they have the same name $sn(sm) =sn(sm')$. If $sm = sm'$, no name conflict
|
||||||
|
is generated. One cannot resolve such a name conflict by adding $sm'$ to
|
||||||
|
$ShgS(q)$, hoping to give the precedence to the new inherited symbol:
|
||||||
|
shadowing operates in a uni-directional manner, from a using package toward
|
||||||
|
a used package. If one does so by a =shadowing-import= of $sm'$ into $p$,
|
||||||
|
the symbol $sm$ would be coherently uninterned, erased from $PrS(p)$, and
|
||||||
|
in a sense replaced by $sm'$. One can prepare the precedence of $sm$ over
|
||||||
|
$sm'$ before the =use-package= by previously =shadow=ing $sm$ in $p.$ Also
|
||||||
|
Lisp on its own can propose to /unintern/ the symbol $sm$ in a /continuable/
|
||||||
|
error in order to resolve such an observed name conflict.
|
||||||
|
|
||||||
|
Another type of name conflict could be raised if another package $q'$ is
|
||||||
|
already used by $p.$ It is then possible that two /different/ symbols
|
||||||
|
$sm \neq sm'$ with the same name $st$ are external, respectively in $q$ and
|
||||||
|
$q'$, in which case $\rho(p)(st)$ is ambiguous. If so, evaluating the
|
||||||
|
=use-package= expression would generate an error. On the other hand, the
|
||||||
|
intersection $ExS(q) \cap ExS(q')$ may be non-empty: a symbol external in
|
||||||
|
both packages does not generate any ambiguity via $p$. As usual, it is
|
||||||
|
always important to remember the lists such as $ExS(q)$ and $ExS(q')$ are
|
||||||
|
lists of /symbols/, not strings. A name conflict can be generated only by
|
||||||
|
/different/ symbols having the same =symbol-name=.
|
||||||
|
|
||||||
|
If no name conflict is detected, the package $q$ is added to $U(p)$ so that
|
||||||
|
the new set of accessible symbols via $p$ is:
|
||||||
|
|
||||||
|
$$
|
||||||
|
\mathrm{new-}AccS(p) = \mathrm{old-}AccS(p) \cup (ExS(q) − ShdS(q, p))
|
||||||
|
$$
|
||||||
|
|
||||||
|
** (unuse-package $q$ $p$)
|
||||||
|
|
||||||
|
If the package $q$ is in $U(p)$, it is removed from this list.
|
||||||
|
|
||||||
|
** A bug in package-internal-symbols?
|
||||||
|
|
||||||
|
While preparing these notes, the author thought for a moment to have
|
||||||
|
encountered a mysterious bug. The minimal model for this bug goes as
|
||||||
|
follows:
|
||||||
|
|
||||||
|
#+BEGIN_SRC lisp :session pcl :exports both :results value verbatim
|
||||||
|
(unuse-package "P2" "P1")
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
#+BEGIN_SRC lisp :session pcl :exports both :results value verbatim
|
||||||
|
(unuse-package "P1" "P2")
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
#+BEGIN_SRC lisp :session pcl :exports both :results value verbatim
|
||||||
|
(delete-package "P1")
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
#+BEGIN_SRC lisp :session pcl :exports both :results value verbatim
|
||||||
|
(delete-package "P2")
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
#+BEGIN_SRC lisp :session pcl :exports both :results value verbatim
|
||||||
|
(defpackage "P1" (:use :cl))
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
#+BEGIN_SRC lisp :session pcl :exports both :results value verbatim
|
||||||
|
(defpackage "P2" (:use :cl))
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
#+BEGIN_SRC lisp :session pcl :exports both :results value verbatim
|
||||||
|
(use-package "P2" "P1")
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
#+BEGIN_SRC lisp :session pcl :exports both :results value verbatim
|
||||||
|
(export 'p2::a "P2")
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
#+BEGIN_SRC lisp :session pcl :exports both :results value verbatim
|
||||||
|
(import 'p2::a "P1")
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
#+BEGIN_SRC lisp :session pcl :exports both :results value verbatim
|
||||||
|
(package-internal-symbols "P1")
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
The point is that the standard Lisp function =do-symbols= runs through all
|
||||||
|
the symbols /accessible/ via ="P1"=, but it does not check for possible
|
||||||
|
duplicates. Really? It seems possible that a symbol can appear more than once
|
||||||
|
in $AccS($="P1"=$)$!? This does not seem compatible with the exclusion of
|
||||||
|
duplicates in general in $InS(p)$ and $ExS(p)$, and also the rule
|
||||||
|
$InS(p) \cap ExS(p) = \emptyset$. Yes, but a – one !– symbol can be
|
||||||
|
simultaneously in $InS(p)$ and $ExS(q)$ for a package $q$ used by $p$, which
|
||||||
|
is the case here for our symbol =p1::a= = =p2:a=.
|
||||||
|
|
||||||
|
The evaluation of =(do-symbols (s p) ...)= makes =s= run through all the
|
||||||
|
internal symbols of $p$, then through all the external symbols of $p$, and
|
||||||
|
finally through all the external symbols of $q$ for every package $q$ used
|
||||||
|
by $p$. It happens that our symbol is in $InS($="P1"=$)$ because it was
|
||||||
|
imported there, and also in $ExS($="P2"=$)$ where it was initially defined.
|
||||||
|
Didn't the programmer of =package-internal-symbols= check the status of the
|
||||||
|
symbol, so that the (fake) second symbol should have been rejected? No, not
|
||||||
|
really, and it is interesting to see the path followed by Lisp: only the
|
||||||
|
symbol-name and the examined package are used, so that when Lisp computes
|
||||||
|
=(find-symbol (symbol-name s) package)=, it obtains /the/ symbol, in fact
|
||||||
|
the only one in this story, together with its status with respect to the
|
||||||
|
=package= argument. In our case that is the package ="P1"= and the symbol's
|
||||||
|
status (with respect to ="P1"=) is =:internal=, even if at the same time
|
||||||
|
$s \in ExS($ ="P2"= $)$.
|
||||||
|
|
||||||
|
This illustrates some of the hidden traps and pitfalls that one might
|
||||||
|
encounter as a result of different packages seeing the same symbol in various
|
||||||
|
ways. Remember: no name conflict can be caused by one symbol alone. A name
|
||||||
|
conflict happens only if /two/ different symbols with the same name are
|
||||||
|
"visible" by the same package. Nothing of this sort happened here, where
|
||||||
|
/one/ symbol is visible by several packages.
|
||||||
|
|
||||||
|
To overcome this weakness in the implementation of
|
||||||
|
=package-internal-symbols=, using the Lisp function =delete-duplicates= is
|
||||||
|
sufficient.
|
||||||
|
|
||||||
|
#+BEGIN_SRC lisp :session pcl :exports both :results value verbatim
|
||||||
|
(defun package-internal-symbols-dedup (package)
|
||||||
|
(let ((rslt))
|
||||||
|
(do-symbols (s package)
|
||||||
|
(when (eq (second
|
||||||
|
(multiple-value-list
|
||||||
|
(find-symbol (symbol-name s) package)))
|
||||||
|
:internal)
|
||||||
|
(push s rslt)))
|
||||||
|
(delete-duplicates rslt)))
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
#+BEGIN_SRC lisp :session pcl :exports both :results value verbatim
|
||||||
|
(package-internal-symbols-dedup "P1")
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
** Symbol-package revisited.
|
||||||
|
|
||||||
|
Throughout this tutorial, we kept insisting that this function is not really
|
||||||
|
interesting. To be complete, we prove that a symbol
|
||||||
|
$sm \in PrS($ =(symbol-package = $sm$ =)= $).$ In other words, if the symbol
|
||||||
|
-package of the symbol $sm$ is $p$, then $sm$ is present in $p$, either
|
||||||
|
internal or external. When a symbol is allocated (necessarily via =intern=),
|
||||||
|
it is in particular interned in the package which is the second argument of
|
||||||
|
=intern= so that it becomes a member of $InS(p)$. It may be exported in this
|
||||||
|
package, which would make it a member of $ExS(p)$, but it remains present.
|
||||||
|
The symbol can be added to $InS(q)$ for other packages $q$ via =import= or
|
||||||
|
=shadowing-import=. It can also then be "upgraded" to $ExS(q)$ in those
|
||||||
|
packages, but the membership in the initial package $PrS(p)$ remains
|
||||||
|
unaffected.
|
||||||
|
|
||||||
|
The only way to change this situation is using =unintern= with the package
|
||||||
|
argument being $p.$ Then the symbol becomes homeless.
|
||||||
|
In turn, this can be changed with =import= and =shadowing-import=, in which
|
||||||
|
case the symbol is interned in a possibly new package, and the story
|
||||||
|
continues along similar lines. Finally, unless a symbol is homeless, it is
|
||||||
|
always present in its home-package.
|
||||||
|
|
||||||
|
This also shows that, as was mentioned in the introduction, the
|
||||||
|
=symbol-package= qualifier of a symbol does divide the set of /interned/
|
||||||
|
symbols into packages. It is impossible to have two different interned
|
||||||
|
symbols with the same symbol-name and also the same symbol-package, for they
|
||||||
|
would be interned in the same package, with the same name, and then the set
|
||||||
|
of packages would not be /coherent/.
|
||||||
|
|
||||||
|
An interned symbol is internal or external in its symbol-package, and
|
||||||
|
certainly visible via this package. However, it may be visible from many
|
||||||
|
other packages, and this information is not stored in the internal
|
||||||
|
representation of the symbol. If it is external, it is accessible via the
|
||||||
|
packages which use its package. The list of these packages can be determined
|
||||||
|
by the standard Lisp function =package-used-by-list=. But a symbol can also
|
||||||
|
be imported by other packages, which in turn may decide to make (the pointer
|
||||||
|
to) this symbol external, and so visible from other packages.
|
||||||
|
|
||||||
|
*Exercise:* Try to define a function =symbol-accessible-from= with one
|
||||||
|
argument, a symbol, that returns the list of all packages where this symbol
|
||||||
|
is accessible. Do not forget the contorted particular case of
|
||||||
|
=common-lisp:nil=! Hint: Have a look at the standard Lisp function
|
||||||
|
[[http://clhs.lisp.se/Body/f_list_a.htm#list-all-packages][=list-all-packages=]].
|
||||||
|
|
||||||
|
An arbitrary number of homeless symbols may have the same name, as
|
||||||
|
illustrated by:
|
||||||
|
|
||||||
|
#+BEGIN_SRC lisp :session pcl :exports both :results output
|
||||||
|
(eq (print (make-symbol "Z"))
|
||||||
|
(print (make-symbol "Z")))
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
When =make-symbol= is called, Lisp allocates a /new/ homeless symbol with the
|
||||||
|
prescribed name, even if another one with the same name has been already
|
||||||
|
allocated. You see /both/ homeless symbols allocated above are printed in the
|
||||||
|
same way, yet they are /different/.
|
||||||
|
|
||||||
|
** Practical uses of the packaging system.
|
||||||
|
|
||||||
|
Many of the somewhat esoteric technicalities detailed in this tutorial have
|
||||||
|
no direct use in concrete programming. On the other hand, they can help to
|
||||||
|
understand and demystify the behaviour of your Lisp system.
|
||||||
|
|
||||||
|
A particularly irritating fact exhibited by some systems is the following:
|
||||||
|
When you merely install, not load, a source file in the editor of your Lisp
|
||||||
|
IDE, these systems immediately scan for symbols appearing in these files and
|
||||||
|
intern them in a separate package. Sometimes this is the package indicated
|
||||||
|
in an inital =(in-package "XXX")= of the file, or still more
|
||||||
|
inappropriately, the standard ="CL-USER"= package, or also the current
|
||||||
|
default package, even if the initial =in-package= references another
|
||||||
|
package. Often this later leads to symbol collisions which are sometimes
|
||||||
|
hard to identify. A developer who meets this kind of mysterious symbol
|
||||||
|
collisions is advised to search first in this direction. In this case, you
|
||||||
|
should make independent micro-experiments to study the actual behavior of
|
||||||
|
your IDE when you install a file in your development environment. Probably
|
||||||
|
in the early days of Lisp, this was done to improve the efficiency, but on
|
||||||
|
modern systems, it is difficult to see compelling reasons to justify such a
|
||||||
|
strategy.
|
||||||
|
|
||||||
|
Most symbols of a Lisp file in fact are symbols with lexical scope, a scope
|
||||||
|
limited to a small part of the code, typically /inside/ a function, a =let=
|
||||||
|
form, a =do= form and so on. These references are converted by the compiler
|
||||||
|
into raw machine addresses, and these symbols have no real life at runtime.
|
||||||
|
|
||||||
|
The most important symbols that have a real life at runtime are those that
|
||||||
|
name functions, those defined through a =defun= form and the like. If a
|
||||||
|
large program is divided in several files, then some decisions must be taken
|
||||||
|
about the status of such a symbol.
|
||||||
|
|
||||||
|
Usually, a correspondence "file $\leftrightarrow$ package" is maintained.
|
||||||
|
Several Lisp systems require that a file begins with an appropriate
|
||||||
|
=(in-package ...)= expression, declaring that all the symbols used in this
|
||||||
|
file are to be understood as present in this package, except when something
|
||||||
|
else is a consequence of decisions taken outside this file or even in this
|
||||||
|
file.
|
||||||
|
|
||||||
|
In the ANSI definition of [[http://clhs.lisp.se/Body/m_in_pkg.htm][=in-package=]], the referenced package must already
|
||||||
|
exist in the environment. Beginning a file by:
|
||||||
|
|
||||||
|
#+BEGIN_EXAMPLE
|
||||||
|
(defpackage "MY-PACKAGE")
|
||||||
|
(in-package "MY-PACKAGE")
|
||||||
|
#+END_EXAMPLE
|
||||||
|
|
||||||
|
is tempting but does not obey the requirement about the first instruction of
|
||||||
|
a file being the appropriate =(in-package ...)=.
|
||||||
|
|
||||||
|
The solution is to have a separate file in the project which is called aptly
|
||||||
|
=packages.lisp= and which contains all the necessary =(defpackage ...)=
|
||||||
|
expressions for the project. The =defpackage= form is flexible and can
|
||||||
|
contain also all the main information about a package, in particular the
|
||||||
|
list of its external symbols and its used packages. (See the ANSI definition
|
||||||
|
of [[http://clhs.lisp.se/Body/m_defpkg.htm#defpackage][=defpackage=]].) This file is a good foundation for structuring the
|
||||||
|
"public" documentation, explaining the role of these packages and their
|
||||||
|
external symbols.
|
||||||
|
|
||||||
|
Along the same lines, it might be worthwhile to have an ="EXTERNAL"= package
|
||||||
|
which contains all the external symbols of the application. In this way, the
|
||||||
|
other packages may use this package systematically, giving to this package a
|
||||||
|
status similar to the status of the ="COMMON-LISP"= package. This does not
|
||||||
|
prevent another package to import some symbols of other packages, symbols
|
||||||
|
whose role is too specific to justify to make them "globally" external.
|
||||||
|
|
||||||
|
** Shortcuts.
|
||||||
|
|
||||||
|
There are several ways for the developer to shorten the Lisp expressions
|
||||||
|
dealing with the packaging system.
|
||||||
|
|
||||||
|
To be rigorous in our explanations and close to the deep structure of Lisp,
|
||||||
|
in this tutorial, we used a string identifier everywhere for a package. A
|
||||||
|
symbol could be used instead, its name then being used as the intended
|
||||||
|
string. For example, =(defpackage :my-package)= is equivalent to
|
||||||
|
=(defpackage "MY-PACKAGE")=, for the name of the keyword =:my-package= is
|
||||||
|
used, and because of the capitalization rule of the symbols applied by the
|
||||||
|
Lisp reader. The name of =:my-package= is ="MY-PACKAGE"=, which through the
|
||||||
|
=defpackage= expression becomes a package-name by following the standard
|
||||||
|
rule which asks for an uppercase name.
|
||||||
|
|
||||||
|
Also, each time a package argument is needed, frequently the second one, it
|
||||||
|
can be omitted if this package is the current default package, which
|
||||||
|
justifies this terminology. When using these shortcuts, do not forget about
|
||||||
|
maintaining maximum readability of the source code!
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user