Path: utzoo!utgpu!watserv1!watmath!att!rutgers!usc!trwind!gumby!wiley!scott From: scott@wiley.uucp (Scott Simpson) Newsgroups: comp.lang.lisp Subject: Re: Common Lisp Package System Considered Harmful Keywords: Object-Oriented Programming, CLOS, packages, information hiding Message-ID: <271CDC30.1E54@wilbur.coyote.trw.com> Date: 17 Oct 90 22:33:19 GMT References: <271BA6D1.5B83@wilbur.coyote.trw.com> <2298@heavens-gate.lucid.com> Sender: news@wilbur.coyote.trw.com (News Software) Reply-To: scott@wiley.UUCP (Scott Simpson) Organization: TRW Inc., Redondo Beach, CA Lines: 124 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 - 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