scott@wiley.uucp (Scott Simpson) (10/17/90)
I posted a message before complaining about the Common Lisp package system and I received some messages that still left me with this vague empty feeling. I have been having trouble accepting the way Common Lisp handles the relationships between object-oriented programming in CLOS and information hiding (which in Common Lisp is chiefly handled by packages). My first suggestion was to make a separate package for each class. My reasons for this were simple: I wished to hide everything except what I explicitly wished to export behind the class wall and only give the user of my class access to to the functions I dictate. This is the essence of data abstraction anyhow. Any support functions that were needed for the functions I exported could not be seen behind the class (i.e., package) wall. Clients of my class could refer to my exported functions as (window:move ...) for example. I like the use of the package prefix and virtually *never* advocate the use of use-package. Likewise, when I used Ada, I virtually never used the "use" clause which is similar to Lisps. Many Ada gurus agree with me. The reason is that with "use" you overpopulate the name space with easily accessible garbage and you cannot find where an entity comes from. If you leave the class prefix on, then you can immediately see where a class comes from. (Lisp allows you to selectively import certain names which is much safer though. Also, a hypertext like browser could alleviate the entity origin problem.) I also liked Lisps ability to conform to Meyer's Principle of Uniform Reference (see "Object-Oriented Software Construction", Bertrand Meyer, a must read) which states that you shouldn't know whether you get a value by stored state or computation. All looked wonderful in Lispland. It was not to be. With each class a separate package, you must painfully create an export clause for each entity exported. While I have no objection to this (for it is done is languages like Eiffel and it is quite nice), I do start to complain when functions such as defclass create a whole bunch of methods behind your back that you wish to have exported. You have to know which methods defclass is going to create and manually export them. This could be done by redefining defclass at the metaclass level but this is a kludge. Also, inheritance becomes a problem because you must not only export all the functions of your current class but also all the functionality you wish exported from your superclasses. There is something disconcerting to me about looking up the inheritance tree to see all the functions that you are inheriting. (C++ gives great control over inheritance control. Perhaps too much. I'm not alone here.) Things were going from bad to worse so I flipped the coin and did what everybody else was advising me; I got rid of packages all together. This had some nice effects. First, I no longer had all those painful export clauses. I also didn't have to look up the tree to see what methods my parents had and export those too. Unfortunately, I also didn't have information hiding. Any support functions that I needed and didn't wish to export were visible. Any variables that I wished local to my class weren't. And worst of all, I started to run into massive amounts of name conflicts. I'm not talking about code created by multiple users; I'm talking about name conflicts between routines I wrote myself. With the package approach, it was nice to be able to call routines with the same name but in different packages such as (unix:lock ...) and (semaphore:lock ...). The colon had a nice "class wall" kind of feel to it. Now I had to say unix-lock and semaphore-lock. I could see myself deevolutionizing to my old emaciated C approach. The dash didn't feel like a class wall; it looked like millions of other kludgy identifiers I had seen before. I had adopted a naming convention because the language didn't give me the power I wanted. CLOS wasn't of any help. If I defined a reader function, I had to give the full name of the reader function rather than defaulting to the class name followed by the slot name (e.g., ":reader lock-name" for the lock class). If I forgot to put the class name as a prefix in some of the :reader clauses ---> symbol conflict! Worse yet, the Lisp system we were using (Mercury from Artificial Intelligence Technologies; an almost CLOS) defaulted to *not* putting the prefix on an accessor method, so any two slots withing the same name in the same package generated a symbol conflict! I have generated a table of the information I have culled from my experimentation with Common Lisp: | with package per class | many classes per package ------|--------------------------------|----------------------------------- |Class prefixes begin with a |Do not manually need to export pro |colon. |superclass methods. |Good information hiding is | |achieved. | --------------------------------------------------------------------------- |Must manually export superclass |You end up with symbol names that con |methods. |have kludgy dash prefixes. |Must manually export methods |You don't have good information |created by defclass. |hiding. --------------------------------------------------------------------------- As an aside on use-package. If you do use it, you know that you must shadow any symbols in the used package if they already exist in the current package. For example, if you have a symbol CLASS in the current package, then you will have to do (shadow 'class) (use-package 'MOS) Now you will refer to USER:CLASS and not MOS:CLASS. I don't like these semantics because you now have two ways to refer to two different symbols with the same name. In the USER case, you can just refer to it as CLASS. In the MOS case, you have to refer to it as MOS:CLASS. I like Ada semantics better. In Ada, you can refer to any symbols used by just using their name unless there is a conflict. If there is a conflict, you must qualify *both* of them. This seems like a more consistent treatment. I am guessing that Lisp does not use this approach because it would have to search the symbol tables of all packages whenever it encountered a symbol in the current package to see if there was a conflict. This would be an unacceptable performance penalty.
pab@lucid.com (Peter Benson) (10/17/90)
In article <271BA6D1.5B83@wilbur.coyote.trw.com> scott@wiley.uucp (Scott Simpson) writes:
I posted a message before complaining about the Common Lisp package
system and I received some messages that still left me with this vague
empty feeling.
The CL package system is pretty hard for most people to deal with and
understand.
. . .
My first suggestion was to make a separate package for each
class. My reasons for this were simple: I wished to hide everything
except what I explicitly wished to export behind the class wall and
only give the user of my class access to to the functions I dictate.
This seems like overkill. It is so easy to get confused with packages that
fewer packages is usually better. A better way to go is to have a
different package for each large conceptual block of your program. You
don't want to have zillions of exports, so you want to modularize well and
then export the (hopefully not too large) interface to each module.
. . .
It was not to be. With each class a separate package, you must
painfully create an export clause for each entity exported. While I
have no objection to this (for it is done is languages like Eiffel and
it is quite nice), I do start to complain when functions such as
defclass create a whole bunch of methods behind your back that you
wish to have exported. You have to know which methods defclass is
going to create and manually export them.
There shouldn't be any method defined behind your back. All the accessors,
readers, and writers must be put in the slot definition explicitly. No make
methods are created. Make-instance handles it all. I don't know anything
about the clos implementation you are using, but you want to try to either
ignore or suppress the automatically created methods and only use the
explicit forms. That way you can port it to another clos implementation.
You can get your hiding done better by writing another macro that expands
into a defclass and some exports.
This could be done by
redefining defclass at the metaclass level but this is a kludge.
Stay away from that if possible. The meta object protocol is still going
through some flux.
Also, inheritance becomes a problem because you must not only
export all the functions of your current class but also all the
functionality you wish exported from your superclasses. There is
something disconcerting to me about looking up the inheritance tree to
see all the functions that you are inheriting.
It can't really work because packages only give one level of "inheritance".
You would always end up looking at the class hierarchy, which is a bad
thing to have to do.
. . .
Things were going from bad to worse so I flipped the coin and
did what everybody else was advising me; I got rid of packages all
together. This had some nice effects. First, I no longer had all those
painful export clauses. I also didn't have to look up the tree to see
what methods my parents had and export those too.
Now you are going too far in the other direction. You want a few packages
and well defined interfaces that you can export without pain.
Unfortunately, I also didn't have information hiding. Any
support functions that I needed and didn't wish to export were
visible. Any variables that I wished local to my class weren't. And
worst of all, I started to run into massive amounts of name conflicts.
I'm not talking about code created by multiple users; I'm talking
about name conflicts between routines I wrote myself. With the package
approach, it was nice to be able to call routines with the same name
but in different packages such as (unix:lock ...) and (semaphore:lock
...). The colon had a nice "class wall" kind of feel to it. Now I had
to say unix-lock and semaphore-lock.
Wait a minute. This is what generic functions are for. The generic
function LOCK should work on unix lock instances and semaphore instances
without any conflict. Also the accessors defined by defclass should be
generic functions, and having a same named accessor for to completely
different classes should be at worst a performance hit.
. . .
language didn't give me the power I wanted. CLOS wasn't of any help.
If I defined a reader function, I had to give the full name of the
reader function rather than defaulting to the class name followed by
the slot name (e.g., ":reader lock-name" for the lock class). If I
forgot to put the class name as a prefix in some of the :reader
clauses ---> symbol conflict! Worse yet, the Lisp system we were using
(Mercury from Artificial Intelligence Technologies; an almost CLOS)
defaulted to *not* putting the prefix on an accessor method, so any
two slots withing the same name in the same package generated a symbol
conflict!
The accessor names must be spelled out completely for a reason. You said
you didn't want defclass making methods behind your back. If you want
automatically created accessors or aaccessor names then write another
macro.
. . .
As an aside on use-package. If you do use it, you know that
you must shadow any symbols in the used package if they already exist
in the current package. For example, if you have a symbol CLASS in
the current package, then you will have to do
(shadow 'class)
(use-package 'MOS)
Now you will refer to USER:CLASS and not MOS:CLASS. I don't like these
semantics because you now have two ways to refer to two different
symbols with the same name. In the USER case, you can just refer to it
as CLASS. In the MOS case, you have to refer to it as MOS:CLASS. I
like Ada semantics better. In Ada, you can refer to any symbols used
by just using their name unless there is a conflict. If there is a
conflict, you must qualify *both* of them. This seems like a more
consistent treatment. I am guessing that Lisp does not use this
approach because it would have to search the symbol tables of all
packages whenever it encountered a symbol in the current package to
see if there was a conflict. This would be an unacceptable performance
penalty.
That's not a bad idea. It's not quite shadowing. You wouldn't believe the
contortions that the reader and loader have to go through anyway for
packages. I don't think this would be that significant of a performance hit.
The problem comes in with compiled files that have symbols in them. There
is usually no way to tell, once a symbol is read, whether or not it was
package qualified. So a compiled file may have a reference to a symbol and
have no information on whether or not it was qualified. Don't even think
about what happens when the package system is different at the time it is
loaded from when it was compiled.
-ptr-
pab@lucid.com
scott@wiley.uucp (Scott Simpson) (10/18/90)
In article <2298@heavens-gate.lucid.com> pab@lucid.com (Peter Benson) writes: >In article <271BA6D1.5B83@wilbur.coyote.trw.com> scott@wiley.uucp (Scott Simpson) writes: > My first suggestion was to make a separate package for each > class. My reasons for this were simple: I wished to hide everything > except what I explicitly wished to export behind the class wall and > only give the user of my class access to to the functions I dictate. > >This seems like overkill. It is so easy to get confused with packages that >fewer packages is usually better. A better way to go is to have a >different package for each large conceptual block of your program. You >don't want to have zillions of exports, so you want to modularize well and >then export the (hopefully not too large) interface to each module. Since I haven't quite figured out how packages can be used with CLOS classes usefully I can't make a judgement on their power and whether what I am doing is overkill or not. I am not convinced that packages even deserved to be given the connotation "powerful". So far I have found them not to be. I do think it is useful though to be able to export some routines from a class and not export other routines. These other non-exported routines are used to support the exported routines but have no business being exported because they don't support the data abstraction. I agree with you that packages in Common Lisp do not work well as my class firewall. There are too many idiosyncracies that go along with them. Suppose I don't use them then and suppose I have two classes A and B and suppose a function X in class A is a support routine for one of the methods of A. Then X should not be exported from A. Also suppose class B is defined in the same package as A. Then the routines of class B can also get to X when they shouldn't be able to. As for manually listing all the routines you wish to export to the outside world, I say why not! How else are you going to specify what behavior should be seen by clients of the class and what behavior is strictly there for implementation purposes. Eiffel forces you to do this. > It was not to be. With each class a separate package, you must > painfully create an export clause for each entity exported. While I > have no objection to this (for it is done is languages like Eiffel and > it is quite nice), I do start to complain when functions such as > defclass create a whole bunch of methods behind your back that you > wish to have exported. You have to know which methods defclass is > going to create and manually export them. > >There shouldn't be any method defined behind your back. All the accessors, >readers, and writers must be put in the slot definition explicitly. No make >methods are created. Make-instance handles it all. I don't know anything >about the clos implementation you are using, but you want to try to either >ignore or suppress the automatically created methods and only use the >explicit forms. That way you can port it to another clos implementation. >You can get your hiding done better by writing another macro that expands >into a defclass and some exports. I screwed up on this one. The pseudo-implementation of CLOS I am using (Mercury) creates a predicate LOCK-P if you create a class LOCK. It also creates an initialization generic called INIT-LOCK that you can assign before and after methods to. Both of these have to be manually exported although I don't create them. CLOS uses TYPEP rather than creating a predicate LOCK-P. However, both Mercury and CLOS must manually export the symbol LOCK or other packages will not be able to say (typep *lock* 'lock:lock). Also, if packages are used as class firewalls, then it would be much easier to have the metaclass create an export clause for each reader and writer automatically. > Things were going from bad to worse so I flipped the coin and > did what everybody else was advising me; I got rid of packages all > together. This had some nice effects. First, I no longer had all those > painful export clauses. I also didn't have to look up the tree to see > what methods my parents had and export those too. > >Now you are going too far in the other direction. You want a few packages >and well defined interfaces that you can export without pain. And if I have multiple classes within one package then I have information hiding problems. > Unfortunately, I also didn't have information hiding. Any > support functions that I needed and didn't wish to export were > visible. Any variables that I wished local to my class weren't. And > worst of all, I started to run into massive amounts of name conflicts. > I'm not talking about code created by multiple users; I'm talking > about name conflicts between routines I wrote myself. With the package > approach, it was nice to be able to call routines with the same name > but in different packages such as (unix:lock ...) and (semaphore:lock > ...). The colon had a nice "class wall" kind of feel to it. Now I had > to say unix-lock and semaphore-lock. > >Wait a minute. This is what generic functions are for. The generic >function LOCK should work on unix lock instances and semaphore instances >without any conflict. Also the accessors defined by defclass should be >generic functions, and having a same named accessor for to completely >different classes should be at worst a performance hit. Yes, generic functions work. Sorry, I should have thought of them. I still like the package prefix though for the reasons I outlined in my previous message. Again, a hypertext like interface would wean me of this preference. > language didn't give me the power I wanted. CLOS wasn't of any help. > If I defined a reader function, I had to give the full name of the > reader function rather than defaulting to the class name followed by > the slot name (e.g., ":reader lock-name" for the lock class). If I > forgot to put the class name as a prefix in some of the :reader > clauses ---> symbol conflict! Worse yet, the Lisp system we were using > (Mercury from Artificial Intelligence Technologies; an almost CLOS) > defaulted to *not* putting the prefix on an accessor method, so any > two slots withing the same name in the same package generated a symbol > conflict! > >The accessor names must be spelled out completely for a reason. You said >you didn't want defclass making methods behind your back. If you want >automatically created accessors or aaccessor names then write another >macro. I don't want automatically created accessors but I do want automatically created accessor names. If accessor names are not to have the package prefix, then I am torn between prefixing the slot name with the class name or not. I don't know which route to pick although <class-name>-<slot-name> currently seems to be the most popular route. (I'm looking at lexical closures too. Perhaps they will be my salvation.) Scott Simpson TRW scott@coyote.trw.com
miller@cam.nist.gov (Bruce R. Miller) (10/18/90)
In article <271CDC30.1E54@wilbur.coyote.trw.com>, Scott Simpson writes: > In article <2298@heavens-gate.lucid.com> pab@lucid.com (Peter Benson) writes: > >In article <271BA6D1.5B83@wilbur.coyote.trw.com> scott@wiley.uucp (Scott Simpson) writes: > > My first suggestion was to make a separate package for each > > class. My reasons for this were simple: I wished to hide everything... > > > >This seems like overkill. ... > > Since I haven't quite figured out how packages can be used with CLOS > classes usefully I can't make a judgement on their power and whether > what I am doing is overkill or not. I am not convinced that packages > even deserved to be given the connotation "powerful". ... (with-disclaimers-and-IMHO () I think that CLOS & Packages are two mechanisms with different scales and intents. And although they both seem to deal with `information hiding' they deal with it in quite different ways. One point is that, in Lisp, nothing is REALLY hidden -- some things are just a little less visible than others. Want a non-exported symbol from a package? Use two colons. Want to get at an unapproved slot from a clos instance? Just use slot-value. On the one hand, I think that packages are (very) useful at a coarse grain: for example at the application level. I've never written an application with more than 2 packages and even those were cases where the one might be used as a separate set of tools. [I do often write `user-level' programs for using an application and define a package for each `user program' which uses the application's package. It eases using the application's tools and avoids clashes between different, similar programs] They really come in useful when you have a well `lived in' lisp environment where there are multiple applications loaded and ready to run (or are running, if you have multi-tasking lisp environment). Of course, you determine which symbols should be used by outsiders by exporting them. If an outsider has to use two colons, he implicitly acknowledges the risk. Anyway, that's how I use packages, but maybe I'm an unsophisticated package user. On the other hand, it is my impression that with CLOS (I haven't done much CLOS yet, but I have done a lot with Flavors --- I think it's the same as far as this discussion is concerned) one differentiates between the `external' and `internal' interface to a class of objects simply by saying so! That is, the developer announces that these are the routines to use. Any other routines are for internal use only. Packages help in so far as it helps (loosely) enforce that distinction to users outside of the application, but doesn't help between modules within the same package. You just dont call the internals by virtue of your strength of will and extraordinary discipline! As you've seen, using too many packages within the same program gets hairy. But, I dont think that CLOS really attempts to enforce any kind of barrier, and packages seems to be at the wrong level for what you want --- lisp itself isn't really into firewalls in any case. A trick I've used with some satisfaction -- as a reminder, NOT a firewall -- is to document, literally, the `documented interface' by giving a documentation string (where appropriate). If I'm in doubt about invoking a function in a particular piece of code, If m-sh-D gives me documentation, I figure it's OK, otherwise probably not. --- OK, it's far from perfect, just an idea. bruce
scott@wiley.uucp (Scott Simpson) (10/19/90)
In article <2865200060@ARTEMIS.cam.nist.gov> miller@cam.nist.gov writes: >I think that CLOS & Packages are two mechanisms with different scales >and intents. And although they both seem to deal with `information >hiding' they deal with it in quite different ways. I agree that the intent that I intended to use packages for and the intent they were designed for are probably quite different. I don't know what packages were designed for. I have been told that they are to be used for separating large subsystems of code. The point I have been trying to make is that the information hiding granularity of packages is way too coarse and no other mechanism in Lisp can give me the granularity I want (i.e., class based) easily. I don't find packages very useful and I wouldn't cry a tear if they were simply removed from the language. I don't know if I agree that CLOS deals with information hiding. How do you mean? >One point is that, in Lisp, nothing is REALLY hidden -- some things are >just a little less visible than others. Want a non-exported symbol from >a package? Use two colons. Want to get at an unapproved slot from a >clos instance? Just use slot-value. Yes. I hate this. The double colon is way too similar to the single colon. I can concede that for efficiency reasons or some such it is non unreasonable to have back doors that can break the abstraction barrier. C++ does this with its ugly friend functions. However, I do think that they should be discouraged and if used, should be quite noticeable. It is said that they put gotos in Ada (especially for automatically generated Ada programs) but that they discourage their use and make them stick out like a sore thumb. Ada's labels look like <<label>>. >On the one hand, I think that packages are (very) useful at a coarse >grain: for example at the application level. I've never written an >application with more than 2 packages and even those were cases where >the one might be used as a separate set of tools. [I do often write If you hardly ever use packages, why do you find them so useful? >On the other hand, it is my impression that with CLOS (I haven't done >much CLOS yet, but I have done a lot with Flavors --- I think it's the >same as far as this discussion is concerned) one differentiates between >the `external' and `internal' interface to a class of objects simply by >saying so! That is, the developer announces that these are the routines >to use. Any other routines are for internal use only. Packages help in >so far as it helps (loosely) enforce that distinction to users outside >of the application, but doesn't help between modules within the same >package. You just dont call the internals by virtue of your strength of >will and extraordinary discipline! Exactly. Strength of will and extraordinary discipline. You hit the nail on the head. This is *exactly* what I am trying to avoid. Besides, if we didn't cherish information hiding, language protection and ease of uses why don't we all go back to programming in assembly language? They all are Turing machine equivalent anyway. Right? I don't think that the difference between external and internal functions should just be that one is documented and the other isn't. We need language features to help in providing protection and ease of use. >As you've seen, using too many packages within the same program gets >hairy. But, I dont think that CLOS really attempts to enforce any kind >of barrier, and packages seems to be at the wrong level for what you >want --- lisp itself isn't really into firewalls in any case. OK. Here you say that CLOS doesn't enforce any barrier. I agree. Also you are right. Lisp isn't into firewalls. This is my complaint. >A trick I've used with some satisfaction -- as a reminder, NOT a >firewall -- is to document, literally, the `documented interface' by >giving a documentation string (where appropriate). If I'm in doubt >about invoking a function in a particular piece of code, If m-sh-D gives >me documentation, I figure it's OK, otherwise probably not. --- OK, >it's far from perfect, just an idea. I think most Lisp users suffer in the same way. Scott Simpson TRW scott@coyote.trw.com
moore%cdr.utah.edu@cs.utah.edu (Tim Moore) (10/19/90)
In article <271E0D40.451E@wilbur.coyote.trw.com> scott@wiley.UUCP (Scott Simpson) writes: >In article <2865200060@ARTEMIS.cam.nist.gov> miller@cam.nist.gov writes: > >>One point is that, in Lisp, nothing is REALLY hidden -- some things are >>just a little less visible than others. Want a non-exported symbol from >>a package? Use two colons. Want to get at an unapproved slot from a >>clos instance? Just use slot-value. > >Yes. I hate this. The double colon is way too similar to the single >colon. I can concede that for efficiency reasons or some such it is >non unreasonable to have back doors that can break the abstraction >barrier. C++ does this with its ugly friend functions. However, I do >think that they should be discouraged and if used, should be quite >noticeable. It is said that they put gotos in Ada (especially for >automatically generated Ada programs) but that they discourage their >use and make them stick out like a sore thumb. Ada's labels look like ><<label>>. I don't see what the problem is. If you don't want to get burned by using internal functions and data structures, don't use them. I think it's great that data structures are exposed in Lisp, if only to satisfy my own curiosity. One of the first things I do when I use a Common Lisp implementation for the first time is type (symbol-plist '+) to see what turns up. (Maybe that comes after (time (fact 1000)) :-) Some Lisp implementations go to great trouble to insure that the source code of any function is available for inspection. That is even better. >>On the one hand, I think that packages are (very) useful at a coarse >>grain: for example at the application level. I've never written an >>application with more than 2 packages and even those were cases where >>the one might be used as a separate set of tools. [I do often write > >If you hardly ever use packages, why do you find them so useful? That's not what he said at all. The basic idea behind packages is that it's not too hard to avoid name collisions in your own code, or even within an "application" (you can use grep, after all), but it is hard to avoid collisions with other random programs that might be loaded in the same Lisp image. For one program, one or two packages is about right. >>[The distinction between "internal" and "external" is in the documentation] >>... >>of the application, but doesn't help between modules within the same >>package. You just dont call the internals by virtue of your strength of >>will and extraordinary discipline! > >Exactly. Strength of will and extraordinary discipline. It doesn't take extraordinary discipline in anyone with a rudimentary understanding of software engineering. >You hit the >nail on the head. This is *exactly* what I am trying to avoid. >Besides, if we didn't cherish information hiding, language protection >and ease of uses why don't we all go back to programming in assembly >language? Because of "ease of use and language protection". Ease of use and information hiding are orthogonal (and in my biased opinion, inversely proportional). >We need language features to help in providing protection and ease of >use. Not in Common Lisp. What other language provides DISASSEMBLE as a standard function? > >OK. Here you say that CLOS doesn't enforce any barrier. I agree. Also >you are right. Lisp isn't into firewalls. This is my complaint. Strict information hiding insures that only the original developers can debug and improve the code. That's not a desirable state of affairs. Tim Moore moore@cs.utah.edu {bellcore,hplabs}!utah-cs!moore "Ah, youth. Ah, statute of limitations." -John Waters
miller@cam.nist.gov (Bruce R. Miller) (10/19/90)
In article <271E0D40.451E@wilbur.coyote.trw.com>, Scott Simpson writes: > In article <2865200060@ARTEMIS.cam.nist.gov> miller@cam.nist.gov writes: > ... > I agree that the intent that I intended to use packages for and the > intent they were designed for are probably quite different. I don't > know what packages were designed for. I have been told that they are > to be used for separating large subsystems of code. As far as I know, that's exactly what they're for. > The point I have > been trying to make is that the information hiding granularity of > packages is way too coarse and no other mechanism in Lisp can give me > the granularity I want (i.e., class based) easily. I don't find > packages very useful and I wouldn't cry a tear if they were simply > removed from the language. Just because packages doesn't solve the particular problem of the moment doesn't mean they're not useful. I would miss them. > I don't know if I agree that CLOS deals > with information hiding. How do you mean? Exactly in this sense: > >One point is that, in Lisp, nothing is REALLY hidden -- some things are > >just a little less visible than others. Want a non-exported symbol from > >a package? Use two colons. Want to get at an unapproved slot from a > >clos instance? Just use slot-value. > > Yes. I hate this. The double colon is way too similar to the single > colon. I can concede that for efficiency reasons or some such it is > non unreasonable to have back doors that can break the abstraction > barrier. ... Well, I dont mind it a bit. Double colons are noticable to me. I surely notice when I type them in! Ditto slot-value. And although given what Lisp is, it would surely introduce inefficiencies to try to introduce firewalls, I seriously doubt that that has anything to do with the lack of them. Who wants them? (besides you I mean :> ) > >On the one hand, I think that packages are (very) useful at a coarse > >grain: for example at the application level. I've never written an > >application with more than 2 packages and even those were cases where > >the one might be used as a separate set of tools. [I do often write > > If you hardly ever use packages, why do you find them so useful? ? I ALWAYS use packages -- I just dont find myself needing to use more than 2 in any given app. I find them useful for just what I said: separating applications and gross layers within an application. > > ... You just dont call the internals by virtue of your strength of > >will and extraordinary discipline! > > Exactly. Strength of will and extraordinary discipline. You hit the > nail on the head. This is *exactly* what I am trying to avoid. Why? Besides, I like being virtuous!:> > Besides, if we didn't cherish information hiding, language protection > and ease of uses why don't we all go back to programming in assembly > language? They all are Turing machine equivalent anyway. Right? I > don't think that the difference between external and internal > functions should just be that one is documented and the other isn't. > We need language features to help in providing protection and ease of > use. FMHT (for my humble tastes), I think you go a bit too far here. > >As you've seen, using too many packages within the same program gets > >hairy. But, I dont think that CLOS really attempts to enforce any kind > >of barrier, and packages seems to be at the wrong level for what you > >want --- lisp itself isn't really into firewalls in any case. > > OK. Here you say that CLOS doesn't enforce any barrier. I agree. Also > you are right. Lisp isn't into firewalls. This is my complaint. Emphasis on ENFORCE. I guess if you REALLY want that much of a barrier, you've just got to use a multitude of packages and a multitude^2 of exports and not USE the packages within each other. You've got to say what is OK and not OK anyway -- what's wrong with export? > I think most Lisp users suffer in the same way. > Scott Simpson TRW scott@coyote.trw.com I'm certainly not `most' but I'm not suffering. bruce - - - - -- - - dumb posting software - - - - - -
mbr@flash.bellcore.com (Mark Rosenstein) (10/19/90)
In article <271E0D40.451E@wilbur.coyote.trw.com> scott@wiley.uucp (Scott Simpson) writes:
From: scott@wiley.uucp (Scott Simpson)
Newsgroups: comp.lang.lisp
Summary: I'm not convinced.
I agree that the intent that I intended to use packages for and the
intent they were designed for are probably quite different. I don't
know what packages were designed for. I have been told that they are
to be used for separating large subsystems of code. The point I have
been trying to make is that the information hiding granularity of
packages is way too coarse and no other mechanism in Lisp can give me
the granularity I want (i.e., class based) easily. I don't find
packages very useful and I wouldn't cry a tear if they were simply
removed from the language.
Well, I'd be major depressed. Ain't you never had name conflicts in C
when you try to load multiple big subsystems? It's sort of hard to
imagine running in a world with flavors, CLOS and PCL all present
without packages, much less CLIM, CLM, CLX, and half a dozen user
packages. They can mostly all coexist, call each other, without much
fuss. [If one object system is good, two must be better, and three
well, you know, the old code that wouldn't die]. I tend to run a given
lisp world for weeks. By the end, I have metering loaded and this and
that, and suprise, suprise they don't interfer with each other. I shudder
to think what would happen if all their symbols were in the user package.
I expect one uses packages to avoid conflict with other subsystems that
you have no control over as well as to segment your program. I don't
know that there is a good treatment, or even if one is possible on how
to segment functionality along package lines. For small systems one will
do and bigger stuff, I've used up to say a dozen, but that might have been
a few too many.
Yes. I hate this. The double colon is way too similar to the single
colon.
They don't seem that similar to me. Everytime I use a double colon,
the good code fairie comes into my office and smacks me on the side
of the head. Unless of course, I'm calling some system function
that isn't documented, but I need. Then I just pay for it in the
next release when it changes under me. But I know. I do it to myself
of my own volition. I honestly have never mistakenly used a double
colon.
I can concede that for efficiency reasons or some such it is
non unreasonable to have back doors that can break the abstraction
barrier. C++ does this with its ugly friend functions. However, I do
think that they should be discouraged and if used, should be quite
noticeable. It is said that they put gotos in Ada (especially for
automatically generated Ada programs) but that they discourage their
use and make them stick out like a sore thumb. Ada's labels look like
<<label>>.
This is lisp, not Pascal. This is why all good little lisp programmers
read Abelson and Sussman or Allen.
Exactly. Strength of will and extraordinary discipline. You hit the
nail on the head. This is *exactly* what I am trying to avoid.
Besides, if we didn't cherish information hiding, language protection
and ease of uses why don't we all go back to programming in assembly
language? They all are Turing machine equivalent anyway. Right? I
don't think that the difference between external and internal
functions should just be that one is documented and the other isn't.
We need language features to help in providing protection and ease of
use.
Scott Simpson TRW scott@coyote.trw.com
Wow. Strength of will and extraordinary discipline. Heck, here I thought
it was plain economics. Well designed code is cheaper over its lifetime
than poorly designed code. [Go ahead do a study on that. Step 1: get
a definition of well designed code. (you don't need step 2, step 1
will never return)].
Ok. I've actually lost the thread of this information hiding stuff. Keene's
book on CLOS has many excellent examples of the use of CLOS to insulate
interfaces from underlying implementation. It isn't a firewall. For
debugging, for quick hacks, for system code, lisp has always given you
at least ten ways to screw yourself to the wall (redefine cons with your
more efficient version, say).
This is not to say that I don't want my compiler or my environment to
warn me when I am doing something unusual (like when two packages
have the same nickname---waaaah), but I never want my lisp to think it
is smarter than me. It can ask "Do you really want to screw yourself?",
but it darn well better let me, when I answer "Yes, thanks." And certainly
runtime checking on array bounds, say, is a major win. I guess fundamentally
good design isn't easy. I want major support from my language and from my
environment, but sometimes a programmer's got to do what a programmer's
got to do.
Mark.
aihaug@AUSTIN.LOCKHEED.COM (Daniel A Haug) (10/19/90)
Last year, we completed a project that involved several hundred thousand lines of CLOS-intensive Lisp code. Just guessing, I'd say that we had around 1000 classes, and perhaps 2000 generic functions. We used the package system to isolate layered functionality. For example, the CLOS-based window system was in one package, the CLOS-based user-interface management system was in another package, the CLOS-based communications system in yet another. You get the idea. In each system, public class slots were exported. So were public interface functions. Private slots and functions were not exported. No big deal. Also, I didn't understand the earlier comment about how defclass generates all of these other hidden functions (?). I do not know what functions outside of the CLOS-specified functions are generated. (defclass a () ((b :initform nil :accessor a-b))) This only generates one new method: a-b. All of the other functions are part of the CLOS spec (e.g. make-instance, initialize-instance, slot-value). Now back to how one would use our system: If I were then writing an application that would use the communications system, the window system, and the UIMS, then I would create my own application package that uses those packages: (make-package :my-application :use '(:common-lisp :clos :window-system :uims :communications)) Then my application package would inherit all of the public slots and interfaces from these subsystems. Then suppose that the window-system has class BASIC-WINDOW with public slot BACKGROUND-COLOR, and that all of my application windows will have blue background colors (back in the old pre-X days of hardcoded display preferences ;-), I would write: (defclass my-app-window (basic-window) ((background-color :initform :blue))) Thus, it is fairly easy to generate subclasses based on public (read: exported) class information. I have not violated object boundaries. My application window class doesn't interfere with other users of :window-system. Now, in practice, since we had such a large system, this approach worked well 99% of the time. Occasionally we had name conflicts arise within the package. No big deal... find it and resolve it. Also, we would have occasional package conflicts, two subsystem packages exporting similar symbol names. Again, no big deal, just choose the desired symbol to inherit, and shadow the others. Or, don't use :USE-PACKAGE at all: (defclass my-app-window (window-system:basic-window) ((window-system:background-color :initform :blue))) To me, there is flexibility to suit many approaches to this. From article <271E0D40.451E@wilbur.coyote.trw.com>, by scott@wiley.uucp (Scott Simpson): > > [...] I don't find > packages very useful and I wouldn't cry a tear if they were simply > removed from the language. I don't know if I agree that CLOS deals > with information hiding. How do you mean? No! We would have DIED in our project without the package system. We probably had 50 packages in the end. I can't begin to imagine life without the package system! Having done a substantial amount of work in C, which has no real namespace support (other than: everyone gets it, or no-one outside of this file gets it), I am very appreciative of languages that provide such a facility (e.g. Lisp, Ada, Modula-II,...). In C, one is forced to use implicit namespace dividers by prepending a pseudo-system name on each symbol (e.g. Xt, X, Xm, Xaw,...) which is pretty ridiculous (IMHO). My only serious gripe about the package system is the inability (within pure CL) to lock a package. In our project, we had a few inexperienced engineers who would develop the window-system side of their application in the window-system package. As their application would be loaded late into the lisp world, their additions to the window-system package would suddenly be inherited by all other users of the window-system, and everyone would indubitably lose. A nice safeguard would have been to lock the window-system package after that system had been loaded. Finally, to summarize my posting: We had success by using packages to isolate subsystems, including class definitions and methods. As we usually had only one or two people working on a system, name-conflicts rarely arose. Public vs. Private class information was accomplished via exporting symbols. dan haug -- Internet: haug@austin.lockheed.com UUCP: ut-emx!lad-shrike!aihaug Phone: (512) 448-5634
lou@cs.rutgers.edu (lou) (10/19/90)
In article <1990Oct18.152453.7100@hellgate.utah.edu> moore%cdr.utah.edu@cs.utah.edu (Tim Moore) writes:
[...] I think it's great that data structures are exposed in Lisp,
if only to satisfy my own curiosity. [...] Strict information
hiding insures that only the original developers can debug and
improve the code.
There is another, more basic, reason that lisp folks have not in
general gone in for strict information hiding, i.e. with no escape
hatches like the double-colon. That is because lisp has been almost
exclusively used with interactive programming environments for quite a
while now. Code trying to access a non-exported variable of some
package may be due to a programmer "cheating" on the modularization of
the program, but it MAY simply be a programmer typing to a
read-eval-print loop trying to figure out what is happening with his
program. The only way a programmer has to interact with his running
system is to cause expressions to be eval'ed. If there is no way for
an *expression* to access something, then there is no way for the
*programmer* to access it during debugging. (I know, accessing stack
frames in Common Lisp appears to be an exception, but that was a
compromise for the sake of allowing a variety of underlying
implementation methods.)
--
Lou Steinberg
uucp: {pretty much any major site}!rutgers!aramis.rutgers.edu!lou
internet: lou@cs.rutgers.edu
jeff@aiai.ed.ac.uk (Jeff Dalton) (10/20/90)
In article <271CDC30.1E54@wilbur.coyote.trw.com> scott@wiley.UUCP (Scott Simpson) writes: >And if I have multiple classes within one package then I have >information hiding problems. Use a package for a "service" (ie, something that "clients" might use). Such facilities might involve several classes that logically go together. (That is, it makes sense to make them be part of the same service) A CLOS implementation would be one example. It contains a number of classes, but will involve only one (or a few) package(s). The information-hiding problems among different classes in the same poackage, when the package embodies a reasonable, logical grouping, are not serious enough to worry about -- at least not in Common Lisp. If you worry about them, then you will find that packages don't work very well for you. If you expect packages to give you the same capabilities you have in Eiffel or Ada, you are bound to be disappointed. -- Jeff
welch@sacral.cis.ohio-state.edu (Arun Welch) (10/20/90)
In article <271CDC30.1E54@wilbur.coyote.trw.com> scott@wiley.uucp writes: >I agree that the intent that I intended to use packages for and the >intent they were designed for are probably quite different. I don't >know what packages were designed for. I have been told that they are >to be used for separating large subsystems of code. Yup, that's it. To give an example of how packages are so usefull, look at Interlisp, which had no packages. The convention that everyone used was, to quote from a truly ancient mail message to 1100Users: >Date: 18 FEB 84 18:31 PST >From: ROACH.PA@PARC-MAXC.ARPA >Subject: RE: Flame topic: Naming Conventions and Packages >To: 1100USERS@SUMEX-AIM.ARPA >cc: ROACH.PA@PARC-MAXC.ARPA > > I would encourage the following convention. Atoms in a package >used for function names, property names, or as global variables should >have the form P.FOO or \P.FOO where P is a nontrivial (>= 3 chars) >prefix. . . . > Slash with "\" any function name, property name, >or global variable whose casual redefinition via DEFINEQ, PUTPROP, or >SETQ (you get the idea) would crash the user. Read module for package above, since the message predates the current use of package. Believe me, having packages is a whole lot better than the other option. Doing an apropos on something like OPEN under Medley yeilds FB.OPEN, TCP.OPEN, CHAT.OPEN, etc. It's interesting to see that some people are even using pseudo-packages in Emac's Elisp, by prefixing their code with package-name:variable (Franz's lisp-emacs interface comes to mind). Most lisps are really whole environments, and you really don't want to clobber yourself by redefining something as innocuous as VERIFY, for example, if it was used by the memory management system. IL programmers generally went to great pains so that casual users didn't redefine important symbols, and having a package boundary with import and export is much simpler. I'd suspect that most IL programmers have trashed the environment at least once by not being careful enough, and having to reload and/or recreate your work isn't fun. ...arun ---------------------------------------------------------------------------- Arun Welch Lisp Systems Programmer, Lab for AI Research, Ohio State University welch@cis.ohio-state.edu
scott@wilbur.coyote.trw.com (Scott Simpson) (10/20/90)
In article <1990Oct18.152453.7100@hellgate.utah.edu> moore%cdr.utah.edu@cs.utah.edu (Tim Moore) writes: >I don't see what the problem is. If you don't want to get burned by >using internal functions and data structures, don't use them. I think >it's great that data structures are exposed in Lisp, if only to >satisfy my own curiosity. One of the first things I do when I use a >Common Lisp implementation for the first time is type (symbol-plist '+) >to see what turns up. (Maybe that comes after (time (fact 1000)) :-) I don't think that back doors aren't useful, I am just concerned about them being the default. >That's not what he said at all. The basic idea behind packages is that >it's not too hard to avoid name collisions in your own code, or even >within an "application" (you can use grep, after all), but it is hard >to avoid collisions with other random programs that might be loaded in >the same Lisp image. For one program, one or two packages is about right. I don't think grep is a great way to discover name collisions! Surely we can come up with something better. Browsers perhaps? >Because of "ease of use and language protection". Ease of use and >information hiding are orthogonal (and in my biased opinion, inversely >proportional). Not necessarily. I see your point but I am not so black and white on the issue. In article <359@skye.ed.ac.uk> jeff@aiai.ed.ac.uk (Jeff Dalton) writes: >If you expect packages to give you the same capabilities you >have in Eiffel or Ada, you are bound to be disappointed. Yes. I have come to this conclusion. Since I don't want to beat a dead horse, I will drop this thread after this message. -- Scott Simpson TRW scott@coyote.trw.com
kers@hplb.hpl.hp.com (Chris Dollin) (10/22/90)
I've been following this with some interest. At least one person has remarked that "they only need one or two packages in an application". Then Daniel A Haug says: Last year, we completed a project that involved several hundred thousand lines of CLOS-intensive Lisp code. Just guessing, I'd say that we had around 1000 classes, and perhaps 2000 generic functions. We used the package system to isolate layered functionality. For example, the ... No! We would have DIED in our project without the package system. We probably had 50 packages in the end. I can't begin to imagine life Several 100K lines of source; 50 packages. When we built Axis (Axis [*1] is an equational-specification language & system, similar in intent to the OBJ family) we had about 27K lines of source (yes, just twenty-seven thousand) and 110 packages (I just counted). Are we that unusual? To us it seemed natural to build our system as a collection of implementations of abstract data-types, and make each module a package; lots of datatypes => lots of packages. We'd wouldn't have done without the package system, although (a) not having structures package names was a pain, (b) not having renamed-imports would be better, (c) packageing seems to be done in the wrong place - as part of the lexicalandsyntactic analysis, not in the compiler [*2]. [*1] See my paper in the Europal 90 proceedings for a brief description of Axis and its implementation. [*2] Or interpreter. For these purposes the difference is irrelevant. -- Regards, Kers. | "You're better off not dreaming of the things to come; Caravan: | Dreams are always ending far too soon."
jeff@aiai.ed.ac.uk (Jeff Dalton) (10/23/90)
In article <271E0D40.451E@wilbur.coyote.trw.com> scott@wiley.UUCP (Scott Simpson) writes: > I don't >know what packages were designed for. I have been told that they are >to be used for separating large subsystems of code. The point I have >been trying to make is that the information hiding granularity of >packages is way too coarse and no other mechanism in Lisp can give me >the granularity I want (i.e., class based) easily. That's right: Common Lisp doesn't have such a mechanism. So what are we supposed to do now? (BTW, you could write a mechanism, but anyone who knew how it worked could probably get around it with some effort. Would that be good enough or do you want more?) >I don't find packages very useful and I wouldn't cry a tear if they >were simply removed from the language. So don't use them. I don't quite understand what you're trying to accomplish. Are you hoping Common Lisp will change, that the Common Lisp designers will be embarrassed at their incompetence, that Common Lisp will tossed in the trash, or what? >>One point is that, in Lisp, nothing is REALLY hidden >Yes. I hate this. Different people hate different things. That's why the world contains more than one programming language, and, indeed, more than one Lisp. >>On the one hand, I think that packages are (very) useful at a coarse >>grain: for example at the application level. I've never written an >>application with more than 2 packages and even those were cases where >>the one might be used as a separate set of tools. [I do often write > >If you hardly ever use packages, why do you find them so useful? He didn't say he hardly ever used them, but rather that he never wrote an application that involved more than two. >>You just dont call the internals by virtue of your strength of >>will and extraordinary discipline! > >Exactly. Strength of will and extraordinary discipline. You hit the >nail on the head. This is *exactly* what I am trying to avoid. >Besides, if we didn't cherish information hiding, language protection >and ease of uses why don't we all go back to programming in assembly >language? They all are Turing machine equivalent anyway. Right? Turing-equivalence is almost useless as a way to compare programming languages. Information-hiding is just one of the things higher-level languages can do, and not necesarily the most important. If it's the most important to you, or for the work you're doing, then don't use Common Lisp. It's as simple as that, isn't it? >Lisp isn't into firewalls. This is my complaint. I would have thought a shorter message would have sufficed for that. -- JD
jeff@aiai.ed.ac.uk (Jeff Dalton) (10/23/90)
In article <WELCH.90Oct19155545@sacral.cis.ohio-state.edu> welch@sacral.cis.ohio-state.edu (Arun Welch) writes: >Yup, that's it. To give an example of how packages are so usefull, >look at Interlisp, which had no packages. The first function I ever defined in InterLisp-D redefined something in the editor. The function name I used was (if I recall correctly) DOT. However, it's worth noting that the designers of other Lisp dialects (eg, Scheme) do not seem to be planning to adopt Common Lisp's packages. Much of the Prolog community has also rejected that approach. (The so-called atom-based proposals for modules get so much criticism from some quarters that I don't think one could ever be adopted as standard.) And it's hard to see many other langauges doing it, because other languages usually don't have symbols as a datatype. Packages do have problems. Just for example: suppose you load in some code but forget to put in a required USE-PACKAGE. Well, all the symbols that ought to be USED-PACKAGE:SYM will be LOCAL-PACKAGE::SYM, and that's very hard to fix once it's happened. However, packages are a fairly good "pragmatic" solution in that they work fairly well and are available now, while the better Scheme solutions are still being developed.
scott@wiley.uucp (Scott Simpson) (10/23/90)
(use-package 'flame) In article <3596@skye.ed.ac.uk> jeff@aiai.UUCP (Jeff Dalton) writes: >So don't use them. I don't quite understand what you're trying to >accomplish. Are you hoping Common Lisp will change, that the Common >Lisp designers will be embarrassed at their incompetence, that Common >Lisp will tossed in the trash, or what? I am trying to get information hiding semantics like Eiffel and Ada in Lisp without too much effort. I have been told by many people I can't. I believe them. I think you are being a tad touchy here. I guess every language news group has its language zealots... >>Exactly. Strength of will and extraordinary discipline. You hit the >>nail on the head. This is *exactly* what I am trying to avoid. >>Besides, if we didn't cherish information hiding, language protection >>and ease of uses why don't we all go back to programming in assembly >>language? They all are Turing machine equivalent anyway. Right? > >Turing-equivalence is almost useless as a way to compare programming >languages. This is exactly what I said! I asked a rhetorical question. Reread the paragraph starting with "Besides". You also edited my paragraph and I fear it could be taken out of context. The last sentence of the above paragraph stated "We need language features to help in providing protection and ease of use." >I would have thought a shorter message would have sufficed for that. My apologies for not being so succinct. P.S. I *really* promise not to post after this! Scott Simpson TRW scott@coyote.trw.com
jeff@aiai.ed.ac.uk (Jeff Dalton) (10/24/90)
In article <272381E7.44AA@wilbur.coyote.trw.com> scott@wiley.UUCP (Scott Simpson) writes: >In article <3596@skye.ed.ac.uk> jeff@aiai.UUCP (Jeff Dalton) writes: >>So don't use them. I don't quite understand what you're trying to >>accomplish. Are you hoping Common Lisp will change, that the Common >>Lisp designers will be embarrassed at their incompetence, that Common >>Lisp will tossed in the trash, or what? > >I am trying to get information hiding semantics like Eiffel and Ada in >Lisp without too much effort. I have been told by many people I >can't. I believe them. I think you are being a tad touchy here. I >guess every language news group has its language zealots... I just think that, after a while, a discussion ought to have some kind of goal. At first I thought you were trying to find the best way to use packages and classes together. Packages are far from perfect, but it makes sense to try to make the best of them. Later, it seemed that what you wanted was a way to get the same facilities as Ada or Eiffel. That too seems reasonable, as far as it goes. It turned out that there wasn't a good way to get such facilities (at least no one has suggested one), but it was worth trying. But then it looked like you'd decided to attack the way Common Lisp did things, pointing out that it was very unsatisfactory from the Ada/Eiffel point of view. Well, fair enough; but at that point it became less clear what practical result you aimed to accomplish. I happen to think that different languages are good for different things and that there isn't much point to "A is better than B" language wars. I probably should have written a milder article, but that's not the way it came out. There's just no way Common Lisp can satisfy everyone in any case. >>>Exactly. Strength of will and extraordinary discipline. You hit the >>>nail on the head. This is *exactly* what I am trying to avoid. >>>Besides, if we didn't cherish information hiding, language protection >>>and ease of uses why don't we all go back to programming in assembly >>>language? They all are Turing machine equivalent anyway. Right? >> >>Turing-equivalence is almost useless as a way to compare programming >>languages. > >This is exactly what I said! I asked a rhetorical question. Reread the >paragraph starting with "Besides". As far as I can tell, what you had in mind was either: 1. If we don't cherish A, B, and C, we may as well use assembler. 2. People who don't cherish A, B, and C don't understand what higher-level languages are for. > You also edited my paragraph and I >fear it could be taken out of context. The last sentence of the above >paragraph stated "We need language features to help in providing >protection and ease of use." I edited the quotes to keep down message size. I didn't ignore the rest and thought what I kept was representative. However, if you think I cut out too much, I accept that and apologize. Common Lisp has at least two ways to make information relatively inaccessible. One is packages. Packages work fairly well for some things, but they don't provide the strong inacessibility that some people want. The other is lexical scoping. Lexical scoping does provide fairly strong inaccessibility, because there are no facilities in the language for looking inside closures. So GENERIC-LABELS would let you hide generic functions, in a sense. >P.S. I *really* promise not to post after this! I hope you do post. Most of what you said about packages was well worth reading. -- Jeff
jeff@aiai.ed.ac.uk (Jeff Dalton) (10/24/90)
In article <KERS.90Oct22090640@cdollin.hpl.hp.com> kers@hplb.hpl.hp.com (Chris Dollin) writes: >(c) packageing seems to be done in the wrong place - as part of the lexical >and syntactic analysis, not in the compiler [*2]. >[*2] Or interpreter. For these purposes the difference is irrelevant. Continuing in my current role as Defender of Common Lisp... Packages are handled by the reader. Thus they apply to data as well as code. This approach has some disadvantages (see one of my earlier articles for an example), but it also has advantages. There are several ways in which data structures can end up being interpreted as code: 1. A symbol that has a global function definition can be used as a function. 2. A list, symbol, or other object can be given to EVAL. 3. A list, symbol, or other object can result from macro expansion. Let's look at the first example. If the package information were not part of the symbol, and the symbol were given as a function to MAPCAR, MAPCAR would have no way to determine which package held the function definition. [Why doesn't MAPCAR know? The symbol doesn't say, and the compiler can't find all calls to MAPCAR and stick the information in at the point of call. The reason the compiler can't find all calls is that some might be done via FUNCALL or APPLY or some function that calls FUNCALL or APPLY. Not to mention EVAL. Moreover, for user-defined functions, the compiler might not be able to determine which arguments were functions (and so needed package information inserted at the call).] Similar problems occur if a list containing function names is given to EVAL or returned as the result of macro expansion. There are several solutions that might be applied to these problems: S-1. Eliminate the offending parts of the language. S-2. Adopt a convention such as: always use the current package. S-3. Have the user explicitly specify the package. S-4. Invent a zowie new mechanism that makes (almost) everything work as we (ought to) expect. Some people favor (S-1) for (1) and (2). That is, eliminate the ability to use symbols as functions and eliminate EVAL. Some people would apply it to (3) as well: no macros. That seems a rather large price to pay. As for S-2, it's difficult to come up with a convention that works in enough cases. On the other hand, it might be argued that we should eliminate (1) and that "use the current package" is good enough for (2) and (3). I think it isn't good enough for (3) [or for (1)]. That is, I should be able to write a macro like this one: (defmacro mac (x) `(some-package:some-function ,x)) or one like this: (defmacro mac (x) `(some-function ,x)) where I expect package information to stick to SOME-FUNCTION. [N.B. I haven't said what "current package" means. I don't think it works well enough to use either the current value of *package* or the current "compilation" package: ie, the package in which the code being compiled is defined. Not does it work to always use the package in which the macro was defined.] S-3 is often used for EVAL: give EVAL an extra "environment" argument. It would be a pain to use it for (1), giving an extra argument to MAPCAR and all the rest. Moreover, it's hard to see how it would work for (3). So that leaves S-4. It is clearly the best solution, but rather difficult to devise. The Scheme world will eventually do it for modules and macros in Scheme. Scheme won't support symbols as functions, and it won't have EVAL. Scheme, then, will offer a reasonable set of solutions. But (a) some regard it as a high price to pay, and (b) the Common Lisp solution, imperfect as it is, is available now and, moreover, was available back when people were writing whatever applications we're using now. -- Jeff
kers@hplb.hpl.hp.com (Chris Dollin) (10/24/90)
In response to my: >(c) packageing seems to be done in the wrong place - as part of the lexical >and syntactic analysis, not in the compiler [*2]. >[*2] Or interpreter. For these purposes the difference is irrelevant. Jeff posts quite a lot, from which I extract below. Packages are handled by the reader. Thus they apply to data as well as code. This approach has some disadvantages (see one of my earlier articles for an example), but it also has advantages. I have found it inconvenient that the pacakging system applies to data. I have resorted to using keywords rather than quoted "plain" symbols in several places just to ensure that packaging doesn't confuse me! [I've not seen Jeff's earlier article. Jeff, could you mail it to me?] There are several ways in which data structures can end up being interpreted as code: I must declare my colours. In general I don't like to treat data as code. This bias will show up below. 1. A symbol that has a global function definition can be used as a function. I would claim that the symbol "should" have been converted to its functional value at the point it was passed. That conversion must, of course, respect packaging. 2. A list, symbol, or other object can be given to EVAL. This would require that sufficient information be given to EVAL for it to resolve packaging in the same way that the compiler (or interpreter) would. That seems reasonable to me. 3. A list, symbol, or other object can result from macro expansion. I don't think that matters; if packaging is a property of the compiler, then macros would have to respect it; they might have to embed additional package *syntax* in their result, but I don't think this is a big deal. Let's look at the first example. If the package information were not part of the symbol, and the symbol were given as a function to MAPCAR, MAPCAR would have no way to determine which package held the function definition. I've hand-waved over that one above. I think if one were to take compiler packaging seriously, then the symbol would have been converted at an earlier appropriate moment. A more serious problem is that the symbol gives you a layer of indirection; between passing the symbol in, and applying it with MAPCAR (strictly, I suppose, FUNCALL or APPLY), the global binding might change. If the "old" value has been captured, but the new one intended, then I'm stuffed. A solution is to allow *variables* as function-objects, and to pass the variable corresponding to the symbol as argument; the package information would be resolved at this point. [In this model, I distinguish between a *symbol*, which is just an interesting data-object with no value, package, or function cells, and a *variable*, which has one value [*1] and possibly name and package cells]. Similar problems occur if a list containing function names is given to EVAL or returned as the result of macro expansion. Not if EVAL has the information that allows packages to be resolved passed in as an additional argument. There are several solutions that might be applied to these problems: S-1. Eliminate the offending parts of the language. S-2. Adopt a convention such as: always use the current package. S-3. Have the user explicitly specify the package. S-4. Invent a zowie new mechanism that makes (almost) everything work as we (ought to) expect. I suspect Jeff would say I've picked S-4. Chopping context ... ... That is, I should be able to write a macro like this one: (defmacro mac (x) `(some-package:some-function ,x)) or one like this: (defmacro mac (x) `(some-function ,x)) Well, I'd write them the same way! But the reader item "package:id" would be translated as (eg) "(inpackage package id)", where "inpackage" is a special form which resolves to the variable called "id" in the package "package". S-3 is often used for EVAL: give EVAL an extra "environment" argument. It would be a pain to use it for (1), giving an extra argument to MAPCAR and all the rest. Moreover, it's hard to see how it would work for (3). I've already indicated my solution for MAPCAR; I agree with Jeff that insisting that all things that take functional arguments that might be symbols (or variables) would be a right pain. So that leaves S-4. It is clearly the best solution, but rather difficult to devise. The Scheme world will eventually do it for modules and macros in Scheme. Scheme won't support symbols as functions, and it won't have EVAL. I think I've sketched an interim solution which would work as effectively as pacakges do now, and gives what I regard as several advantages: eg, data is immune from the package system (so [modulo UNINTERN and its ilk] there's only one symbol with a given printname; one can write macros which do package manipulation, such as "(in-a-package Package Form*)" which compiles the Form's in the context of that package. I'm not seriously suggesting that it's a candidate to go into CL; it's far too late for that. But I think that it demonstrates that there is a rational alternative to read-time packaging. Scheme, then, will offer a reasonable set of solutions. But (a) some regard it as a high price to pay, and (b) the Common Lisp solution, imperfect as it is, is available now and, moreover, was available back when people were writing whatever applications we're using now. Well, I agree with that. I just wish the rational reconstruction was what we'd been given in the first place! -- Jeff [Hey, Jeff, I didn't mention [omitted] once!] -- Regards, Kers. | "You're better off not dreaming of the things to come; Caravan: | Dreams are always ending far too soon."
jeff@aiai.ed.ac.uk (Jeff Dalton) (10/26/90)
In article <KERS.90Oct24101043@cdollin.hpl.hp.com> kers@hplb.hpl.hp.com (Chris Dollin) writes: > >(c) packageing seems to be done in the wrong place - as part of the > >lexical and syntactic analysis, not in the compiler [*2]. > >[*2] Or interpreter. For these purposes the difference is irrelevant. >Jeff posts quite a lot, from which I extract below. > > Packages are handled by the reader. Thus they apply to data > as well as code. This approach has some disadvantages (see > one of my earlier articles for an example), but it also has > advantages. >I have found it inconvenient that the packaging system applies to >data. I have resorted to using keywords rather than quoted "plain" >symbols in several places just to ensure that packaging doesn't >confuse me! You have to arrange for the right data symbols to be imported, exported, etc. There are times when this can be a pain, but there are other times when it would be a pain to have all symbols in one package. > There are several ways in which data structures can end up > being interpreted as code: >I must declare my colours. In general I don't like to treat data as code. >This bias will show up below. Lisp has traditionally been a language in which you can do such things. Scheme has not. I think it's a good thing that both approaches are represented. > 1. A symbol that has a global function definition can be > used as a function. > >I would claim that the symbol "should" have been converted to its functional >value at the point it was passed. That conversion must, of course, respect >packaging. There are, no doubt, ways to do this without losing track of what symbol it was. (Just taking the functional value would be to lose track.) You suggest, below, that the symbol might be converted to a "variable". Maybe that would be a good solution, but it's hard to be sure without implementing it and seeing how it turns out. > 2. A list, symbol, or other object can be given to EVAL. > >This would require that sufficient information be given to EVAL for it to >resolve packaging in the same way that the compiler (or interpreter) would. >That seems reasonable to me. I agree. Indeed, that's how the Scheme implementations that have EVAL tend to handle it. > 3. A list, symbol, or other object can result from macro > expansion. > >I don't think that matters; if packaging is a property of the compiler, then >macros would have to respect it; they might have to embed additional package >*syntax* in their result, but I don't think this is a big deal. It is difficult to make this work in a reasonable way. Suppose, for example, you write a macro that defines a macro. It may need to put in some package information but not be able to tell what package it's being expanded in. Here's an example. DEFINE-FEXPR is used to define a "function" that operates on its unevaluated arguments (as a single list). What it actually defines is a macro that expands to call a function with all the arguments in a QUOTE. (defmacro define-fexpr (name (var) &body forms) (let ((fn-name (gentemp (concatenate 'string (string name) "-FUNCTION")))) `(progn (defun ,fn-name (,var) ,@forms) (defmacro ,name (&rest ,var) `(,',fn-name ',,var))))) And now the question is: how does DEFINE-FEXPR know what module the function is in so that the macro it defines can expand to a call to *that* function. (Despite our GENTEMP, there may later be another function with that name.) You also have to consider how macro calls are written. The caller shouldn't have to add more (explicit) package information than would be needed when calling a function or a built-in special form. There are ways to deal with these problems, but it requires work to figure them out, and some of the mechanisms required look like they will be complicated and hard to understand. > Let's look at the first example. If the package information > were not part of the symbol, and the symbol were given as a > function to MAPCAR, MAPCAR would have no way to determine > which package held the function definition. > >I've hand-waved over that one above. I think if one were to take compiler >packaging seriously, then the symbol would have been converted at an earlier >appropriate moment. I agree. But that would be doing something else rather than making this case work. So I think we'd have to try implementing the something else ans see how well it worked. >A more serious problem is that the symbol gives you a layer of indirection; >between passing the symbol in, and applying it with MAPCAR (strictly, I >suppose, FUNCALL or APPLY), the global binding might change. If the "old" >value has been captured, but the new one intended, then I'm stuffed. > >A solution is to allow *variables* as function-objects, and to pass the >variable corresponding to the symbol as argument; the package >information would be resolved at this point. Note that it's much easier to do this if the user has to write something like (VARIABLE <name>) than if it's supposed to happen automatically. > There are several solutions that might be applied to these problems: > > S-1. Eliminate the offending parts of the language. > S-2. Adopt a convention such as: always use the current package. > S-3. Have the user explicitly specify the package. > S-4. Invent a zowie new mechanism that makes (almost) everything > work as we (ought to) expect. > >I suspect Jeff would say I've picked S-4. Chopping context ... > > ... That is, I should be able to write a macro like this one: > (defmacro mac (x) `(some-package:some-function ,x)) > or one like this: > (defmacro mac (x) `(some-function ,x)) > >Well, I'd write them the same way! But the reader item "package:id" would be >translated as (eg) "(inpackage package id)", where "inpackage" is a special >form which resolves to the variable called "id" in the package "package". That handles the first definition of MAC, but not the second. A number of macros that are easy to express in Common Lisp will have to be cluttered with package information (unless there was a clever implementation that figured it all out automatically). And the macros's author may not know all of the package information when writing the macro. (See above.) You can start adding mechanisms to handle all this, but it's difficult to devise good ones. > So that leaves S-4. It is clearly the best solution, but > rather difficult to devise. The Scheme world will eventually > do it for modules and macros in Scheme. Scheme won't support > symbols as functions, and it won't have EVAL. > >I think I've sketched an interim solution which would work as effectively as >pacakges do now, and gives what I regard as several advantages: eg, data is >immune from the package system (so [modulo UNINTERN and its ilk] there's only >one symbol with a given printname; one can write macros which do package >manipulation, such as "(in-a-package Package Form*)" which compiles the Form's >in the context of that package. I'm not seriously suggesting that it's a >candidate to go into CL; it's far too late for that. But I think that it >demonstrates that there is a rational alternative to read-time packaging. Well, I don't like that solution. I've tried to write macros that way recently, in an "Uncommon Lisp", and it was a pain. I don't think it works as effectively as packages, because packages often get it right for you, and the cases that aren't addressed by packages (certain name capture problems) have been around for so long that people have gotten used to dealing with them. Moreover, I think uninterned symbols take us further from "only one symbol with a given name" than "only one symbol with a given name in a given package" does. (Of course, it depends on which cases one has in mind.) -- Jeff PS -- You asked me to mail you a copy of an earlier message. I don't know which one you want. (I mention this here, because mail doesn't seem to be working very well these days.)