scott@wiley.uucp (Scott Simpson) (07/28/90)
How do people use packages with CLOS classes? The most obvious solution to me would be to make a separate package for each class. My office mate objected to this, saying that it would cause problems with inheritance. He noted that symbols are local to a package and when you inherit from another class you would need to know the superclasses name to access any inherited slot. My retort was that you should provide accessor functions for superclass slots in your class and export them if you wish to enable the user of the class to access an inherited slot. Then if you changed the superclass for any reason the code behind the package wall would change but the accessor function used by the user using the class would not change. Another issue is the mapping between file names and CLOS classes. Do you normally create a different file for each CLOS class? This seems to me to be simple method for quickly identifying where classes are stored. If all the files are stored in the same directory, it regrettably flattens the type hierarchy structure you are trying to simulate but I think it is the best you can do without some sort of object-oriented database underneath. Scott Simpson TRW scott@coyote.trw.com Scott Simpson TRW scott@coyote.trw.com
barmar@think.com (Barry Margolin) (07/30/90)
In article <26B09E74.58E0@wilbur.coyote.trw.com> scott@wiley.uucp (Scott Simpson) writes: >How do people use packages with CLOS classes? I don't think there needs to be a particular relationship between classes and packages. The purpose of packages is to prevent inadvertant name collisions between programs written by different groups of people. They can also be used to collect together all the public interfaces to a facility. > The most obvious >solution to me would be to make a separate package for each class. This is probably too extreme. A decent-sized application would probably have dozens of classes, many of which are expected to be mixed together. Related classes would be implemented by the same developer or development team, so name collisions are not likely to be a problem. They'd probably end up using package prefixes excessively. When a method is implemented by several classes, or a normal function is needed by several classes, you'll have to decide arbitrarily which class's package to use. And packages can be pretty confusing, and a large number of small packages is likely to cause more problems than it solves. Implementation of mixins can be tough with this scheme. Frequently, a mixin is designed so that it may be mixed in with any of a number of base classes. It simply requires that the instantiated subclasses implement a given set of generic functions and/or provide a specified set of slots. If each base class is in its own package this can be problematic. > My >office mate objected to this, saying that it would cause problems with >inheritance. He noted that symbols are local to a package Except, of course, for the ones that are exported and inherited or imported into another package. There's nothing special about the symbols used to name slots and methods, so you can import and export them just like any other symbols. However, care must be taken in deciding what symbols to export, since the export list defines the interface to a facility, and slot names are not usually chosen that carefully. > and when you >inherit from another class you would need to know the superclasses >name to access any inherited slot. My retort was that you should >provide accessor functions for superclass slots in your class and >export them if you wish to enable the user of the class to access an >inherited slot. Then if you changed the superclass for any reason the >code behind the package wall would change but the accessor function >used by the user using the class would not change. This is a good point. However, it's often important to distinguish between interfaces intended to be used between related classes and interfaces intended for external users of the class. This can, of course, be done by providing an additional class for external users, and importing/exporting only the public interface from this class. (Some OO languages provide more explicit support for this distinction, such as C++'s "protected" slots and methods.) However, one reason for using function-style accessors rather than SLOT-VALUE is to hide the fact that a value is implemented as a slot of a particular class. However, by using packages this way you force this association to be explicit. See my point above about choosing the package of a generic function implemented by multiple classes. > Another issue is the mapping between file names and CLOS >classes. Do you normally create a different file for each CLOS class? >This seems to me to be simple method for quickly identifying where >classes are stored. If all the files are stored in the same directory, >it regrettably flattens the type hierarchy structure you are trying to >simulate but I think it is the best you can do without some sort of >object-oriented database underneath. One of the features of CLOS is the ability to spread the implementation of a class out. In some cases it's useful to keep the methods of a class together. In other cases, though, it's useful to keep all the methods implementing a particular generic function together. For instance, if you have an optional facility that works by adding functionality to a bunch of classes. In Lisp it is often less necessary to keep source files small than it is in other languages. Most Lisp environments are integrated with an editor, and make it easy to incrementally recompile functions and methods. Therefore, the edit-compile-test cycle time is not usually a function of source file size. Therefore, if you have a number of related, small classes (mixins tend to be pretty small) it may be easier if they are in the same source file. Many good Lisp environments provide automated mechanisms for finding the source file of an identifier, so there's less need for an easy way to guess. -- Barry Margolin, Thinking Machines Corp. barmar@think.com {uunet,harvard}!think!barmar
faustus@fir.Berkeley.EDU (Wayne Christopher) (07/31/90)
The problem with using packages and CLOS is that unless you are careful you can get lots of name conflicts. Say I have two rather general classes, foo and bar, each in its own package. Each has a slot called "name", and, following the convention used in the CLOS book, I have defined and exported accessors called "name" for both. When I try to use both classes in one package, I get a symbol conflict. There are a few ways to avoid this: 1. Make foo import the symbol name from bar, or vice versa -- not too modular. 2. Use keywords for accessor functions. Pretty ugly. 3. Don't use-package either foo or bar, and use foo:name or bar:name when necessary. 4. Call the accessor functions foo-name and bar-name, as with structures. I've been using (4), but it was sort of an annoying surprise to realize that converting my program to use packages would require me to change all my accessor function names. Wayne
barmar@think.com (Barry Margolin) (08/01/90)
In article <26581@pasteur.Berkeley.EDU> faustus@fir.Berkeley.EDU (Wayne Christopher) writes: >The problem with using packages and CLOS is that unless you are careful >you can get lots of name conflicts. Say I have two rather general >classes, foo and bar, each in its own package. Each has a slot called >"name", and, following the convention used in the CLOS book, I have >defined and exported accessors called "name" for both. When I try to >use both classes in one package, I get a symbol conflict. There are a >few ways to avoid this: The problem isn't that you put each class in its own package, the problem is that you exported poorly-named symbols from the packages, and then tried to USE-PACKAGE both of them in another package. If you intend for people to USE-PACKAGE your package, then you should give the exported symbols names that make sense to use without a package prefix. Also, USE-PACKAGE must be used carefully; packages exist to *prevent* name conflicts, but when you USE-PACKAGE more than one package you are explicitly tearing down the wall that the package system puts up (but you still don't get as many conflicts as you would without packages at all, because of the internal/external distinction). Consider this: if the two classes had been in the same package wouldn't you also have gotten a conflict? The conflict wouldn't be noticed by the package system, but the conflict would have been between the two classes that define methods named NAME. In this case the conflict is benign, because they are both slot accessor methods. However, if you were to use both of them as superclasses for a new class there would only be one NAME slot, which may confuse other methods of the classes. For instance, if methods of one class expect the slot to hold a symbol, while methods of the other class expect it to hold a string, some of them won't find what they expect; if these expectations are made explicit using the :TYPE slot option, the slot in the subclass would have an effective type of (AND STRING SYMBOL), which is an empty type, so any assignment to the slot would be invalid. And what if, instead of an automatically-generated slot accessor it had been a real generic function, e.g. (defclass named-thing () (name)) (defmethod name ((object named-thing) language) (name-in-language (slot-value object 'name) language)) (defclass other-thing () ((name :accessor name))) In this case the conflict will be noticed by CLOS, because the NAME method defined automatically for OTHER-THING doesn't have a congruent lambda list to the one defined defined by the explicit DEFMETHOD. -- Barry Margolin, Thinking Machines Corp. barmar@think.com {uunet,harvard}!think!barmar