[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

package.mss.108



    Date: Wed, 11 May 1983  00:46 EDT
    From: Scott E. Fahlman <Fahlman@CMU-CS-C>

    The use of a symbol argument (not a string) in FIND-ALL-SYMBOLS was
    deliberate.  It didn't seem to me that interning a symbol (internally in
    the user package or wherever the user is typing) was something
    particularly to be avoided.  With a string, you get all the issues of
    case, and with a symbol argument you automatically get the intuitive
    thing.  But I've got no real problem with allowing either a string or a
    symbol here.

Just in case you didn't catch it the first time around, my point here was
that if you want to find out "are there any symbols named X, and if so
where are they", asking the question shouldn't perturb the environment in a
way that changes the answer, by creating a possibly-new symbol named X.  In
particular it's quite a useful response when FIND-ALL-SYMBOLS tells you
"no, there aren't any symbols named X, anywhere".  Let's just make it
accept either a symbol or a string.  Users calling it directly will pass
symbols, probably, while user-interface tools will tend to pass strings.

        I'm not completely happy with declare-package and the :export keyword.  I
        don't like the idea of package A exporting symbols from package B; what if
        there is a typographical error, a conceptual mistake, or an obsolete symbol
        in the list supplied by package A, which is a duplicate of package B's
        list?

    There is, of course, the problem of these lists getting out of synch,
    but I think that this problem is not likely to be a serious one, since
    the list in B is supposed to be a subset of the list in A -- B just
    declares those symbols from A that it actually uses....
    I don't see how the "owner vs. user" error checking that you advocate
    can work unless you require that the exporting package be loaded before
    the using package (in which case the user doesn't need to declare the
    package at all).

Let me be extra explicit here.

"Loading" a package is what the program that "owns" that package does.

"Declaring" a package is something anyone who uses it can do, if they don't
require that the package's program be loaded before they are.  A package
may be declared any number of times, both before and after it has been
loaded.

A package contains a bit that is set when it is "loaded".

Declaring a package after it has been loaded checks that the symbols exported
by the declaration are a subset of the package's externals, native or inherited.

Declaring a package before it exists creates the data structure but doesn't
mark it loaded.  Declaring a package before it is loaded creates externals
according to the export list and does inheriting according to the include
list, and doesn't do error checking.

Loading a package that already exists, because it was declared one or more
times before it was loaded, checks that the externals and inclusions the
package has are subsets of those declared by the guy who is now loading it,
who is the only one who knows what is really right.

The rule that is implemented is that what the declarer says about the
package must be a subset of what the loader says about it.  This is checked
as soon as they both have spoken.  If the rule is violated, a continuable
error is signalled and continuing acts as if the declarer knew what he was
doing better than the loader.

Note that declaring a package that is constructed by inclusion of other
packages requires that the declarer, unmodularly, understand this structure.
Maybe you don't use declaring much for that kind of package.

[This paragraph has nothing to do with the subject at hand, and applies
equally well to any package system, or any system that has globally named
objects.]
We arrange for something reasonable to happen if a package is loaded
twice; the issue here is figuring out whether it was loaded by the same
program, which was loaded twice for some reason, or whether two different
programmers chose the same package name, unbeknownst to each other.  This
is probably done in an implementation-dependent way (the Lisp machine will
base it on the name of the file containing the code that loaded the
package).  Some implementations will choose not to detect this kind of name
conflict at all, while others will choose always to signal an error when a
package is loaded twice, assuming that if the user is loading a program
twice he knows about it and will continue from the error.  (Previously I
called this "making" a package, but that could be taken to mean the
primitive that creates the package data structure.)

        In your writeup, name clashes are only discussed under IMPORT....

    What I object to very strongly (I think) is the notion that any of these
    operations, when performed in package X, have to worry about what's
    going on down in the descendant packages.  It's tricky enough to make
    this work looking up the tree, and I think it's impossible to sort it
    out looking down.  All sorts of things might have been shadowed and moved
    around by explicit agreement, and trying to decide after the fact what
    is to be an error would be impossible.

I don't know what direction is "up" for you and what direction is "down".
But no matter which way I interpret it I don't believe it is impossible, or
even hard.  It is useless to do name clash checking if you don't do it
completely.  More below.

My previous proposal said that every package contained a list of the symbols
in that package that had been shadowed, which was specifically remembered
for the use of name-clash error checking.  Indeed if you don't remember
what the user said to shadow you can't do name-clash checking.

    Once again, we are talking about adding a LOT of conceptual hair to
    handle a problem that will seldom occur and that will probably not screw
    anyone if it does occur.

In practice, this is the problem that makes people frequently curse the day 
packages were ever invented.  The point here is that while it may seem rare
and unlikely to screw anyone to you, from your present abstract viewpoint,
when a name clash occurs it is so unpredictable, and so pernicious in its
effects, that it is very important to have iron clad assurance that these
things can't happen.  If I may make a strained analogy, suppose we came up
with a really neat efficient garbage collection algorithm, based on hashing,
which worked really well except that one in a million times, or say one
in 2^32 times, it would throw away something it should have kept.  Well,
probably no one will ever notice, it's pretty rare, right?

Incidentally I don't believe that this adds any conceptual hair whatsoever.
It adds a small amount of implementation hair, in that you need backpointers
from packages to their inheritors, but that is not conceptual hair.  You
already have the concept of shadowing.  The rule "no two symbols with the
same name may be accessible from the same package, unless one of them was
explicitly declared via shadowing" seems the soul of simplicity to me.
I'm then just proposing that the rule be enforced, i.e. it be "signals
an error" rather than "is an error."
Much better than the same rule, plus "except when include-package is used
to more than one level" or "except when export is called after intern has
already been called" or "except when debugging late at night and typing
spastically."