Class Dependencies

Anthony Hannan ajh18 at cornell.edu
Sat Nov 16 18:07:06 UTC 2002


Hello fellow Squeakers,

	Please consider the alternative view of packages described below.  I
think it is more object-oriented than the current approach being
pursued.  At least it may give you some new ideas.

	If we ignore conflicting versions for a minute, we could define a
package as simply a class, and prerequisite packages as all classes that
it's methods depend on.  Then SqueakMap could be a catalog of classes,
and when the user downloads one, all prerequisite classes would be
downloaded with it automatically.  The SqueakMap interface could just be
the traditional class browser with automatic download when viewing or
executing methods.
	However, the problem is finding prerequisite classes; we cannot narrow
them down enough.  Many classes are overloaded, holding many distinct
behaviors (extensions) that are used by different groups of classes.  So
a client class that only needs certain behavior of an overloaded class
is forced to bring in the whole class and all of its prerequisites that
it may not need.
	The traditional solution to this problem, which we are pursuing now, is
to cut up classes by functionality, group the pieces by functionality
into "packages", then glue the pieces back together into classes in the
user's image.  An alternative solution, which I am proposing here, is to
cut up the classes by functionality but leave them as separate
behaviors, then rely on class dependencies to automatically download
prerequisite behaviors needed.  In other words, break up classes into
distinct behaviors utilizing multiple inheritance.
	To avoid the ambiguity problem of mulitple inheritance, I propose
inheritance of behavior only, not state, and I propose every selector be
assigned a protocol (namespace) to eliminate random name clashing.  The
remaining true ambiguities would raise errors on method lookup.  More on
these later.
	With freely inherited distinct behaviors, there is no need for class
extensions and therefore no need to cut them out into packages.  If
class A needs new behavior on class B then he would create a new class C
containing the desired behavior and make it a superclass of class B. 
Class C would not be a prerequisite of class B, but it would be a
prerequisite of class A.
	If a class needs a method changed in another class then a new version
of that class is needed.  Versions are handled separately and explained
later.
	To stress the idea of distinct behaviors that may be multiply inherited
I will use the term 'behavior' instead of 'class' from now on.  Behavior
A depends on behavior B if behavior A contains a method that either
names behavior B directly or sends a selector in behavior B's protocol. 
The former is considered a concrete prerequisite, the latter is
considered an abstract prerequisite.  Notice, a behavior does not depend
on its super-behaviors because no state is inherited.  A super-behavior
will be included only if some client behavior sends a selector in its or
inherited protocol.  A behavior's protocol consists of method selectors
that are new to its behavior; all other selectors it understands are
inherited and are not part of its protocol.
	When downloading a behavior we also:
 1. Download its abstract and concrete prerequisite behaviors, then
 2. Download all behaviors in the hierarchy between all concrete and
abstract behaviors in the user's image, then
 3. Go back to step 1 for each new behavior downloaded until no more new
behavior are downloaded.
	This download algorithm (of course it needs to be optimized) guarantees
than all behaviors ever needed will be downloaded, without including
supers of abstract behaviors and subs of concrete behaviors that are
never used.  This download algorithm is able to be selective because
messages are bound to specific abstract behaviors.  This requires the
programmer to choose the right abstract behavior if a selector name is
found in disjoint behaviors.  This either means the selectors are
semantically different that happen to have the same name, in which case
it should be easy to choose which behavior is intended; or it means the
selectors are semantically the same and should belong to the same
protocol (abstract behavior), in which case the programmer should create
a new behavior that is a super of all the other behaviors (if one does
not already exist) and implement that selector in it with "self
subclassResponsibility".  I think this extra structure is actually
positive, because behaviors that are polymorphic will inherit from the
same abstract behavior, making intent more obvious, and providing a
behavior for related methods that are not sub-behavior specific.
	Multiple inheritance in general is a great thing.  I think it has just
gotten a bad reputation for its over-inflated but easily-subdued
ambiguity problem.  Mixins, Traits, etc. all try to achieve multiple
inheritance but in round about ways just to avoid this problem.  But
solving the problem directly, I think, is the best approach, because it
does not introduce new objects or concepts.  So, my solution is to
remove instance variables and replace them with primitive accessor
methods that only work on the concrete instances of the behavior they
are defined in, else an error is raised.  You add state just be adding
primitive accessor methods.  Each behavior must redefine its own
accessor methods (these can be generated from supers automatically). 
This elimates the inst var index problem in multiple inheritance and
allows behaviors to freely inherit from other behaviors without worrying
about state.  Also, since selectors are no longer just names, a selector
will only clash with another if both override the same inherited
selector, in which case a true ambiguity exists, which raises an error
on method lookup.
	
	Now how do we handle different versions of the same behavior which may
be needed by different client behaviors?  The answer is environments
(aka configurations).  Each environment represents a Squeak version that
inherits behaviors from zero or more other environments, and introduces
new behaviors and new vesions of inherited behaviors.  Like projects,
the user can enter/exit environments; when he does, its new behaviors
are installed/uninstalled in the system dictionary.  In fact, projects
would use environments instead of changesets in this new scheme.  If two
independently inherited environment override the same behavior, then the
child environment should merge the two into a version of its own.  If
not, a warning will be raised when entering the environment and an error
will be raised if the behavior is used.
	When a user enters an environment it does not mean all its behaviors
are downloaded.  As explained above, behaviors are download only when
first needed.

	The current image can be converted to the scheme I have described as
follows:  Each selector name will be considered unique and be associated
with the lowest common superclass of all classes that implement it if
that superclass implements it itself.  If not, then the selector is put
in an abstract superclass shared by the highest subclasses that do
implement it.  For example, the selector asSymbol has four implementors:
String, Symbol, Character and Vocabulary.  Since their lowest common
superclass, Object, does not implement asSymbol then a new abstract
behavior called CharacterOrStringOrVocabulary would be created (if not
already created before for a similar selector) and made superclass of
String, Character, and Vocabulary.  asSymbol is then added as a selector
with "self subclassResponsibility" as its method.  Note, that if
Vocabulary was created after the new scheme was adopted, then asSymbol
would have been defined as a separate selector in Vocabulary (even with
the same name) if it was not intended to be polymorphic with String,
which is what I suspect.  Then the new abstract superclass would reduce
to CharacterOrString, which would be logical for behavior that treats
characters as one element strings.
	This new image would be good enough to put into SqueakMap and start
having users download classes from it.  Of course most users will get
more than what they really need until further behavior factoring is
done.  But this factoring can be done incrementally without trying to
extract whole "packages".  The download "packages" will form themselves.

Cheers,
Anthony



More information about the Squeak-dev mailing list