[comp.lang.lisp] Using Packages With CLOS

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