Path: utzoo!utgpu!news-server.csri.toronto.edu!cs.utexas.edu!uunet!wiley!scott From: scott@wiley.uucp (Scott Simpson) Newsgroups: comp.lang.lisp Subject: Common Lisp Package System Considered Harmful Keywords: Common Lisp, Package System, Information Hiding, Object-Oriented Programming Message-ID: <271BA6D1.5B83@wilbur.coyote.trw.com> Date: 17 Oct 90 00:33:20 GMT Sender: news@wilbur.coyote.trw.com (News Software) Organization: TRW Inc., Redondo Beach, CA Lines: 106 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.