Path: utzoo!utgpu!news-server.csri.toronto.edu!cs.utexas.edu!sun-barr!apple!agate!shelby!neon!lucid.com!pab From: pab@lucid.com (Peter Benson) Newsgroups: comp.lang.lisp Subject: Re: Common Lisp Package System Considered Harmful Message-ID: <2298@heavens-gate.lucid.com> Date: 17 Oct 90 06:00:22 GMT References: <271BA6D1.5B83@wilbur.coyote.trw.com> Organization: Lucid, Inc. Menlo Park, CA Lines: 132 In-reply-to: scott@wiley.uucp's message of 17 Oct 90 00:33:20 GMT 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