Behaviors vs Modules

Anthony Hannan ajh18 at cornell.edu
Fri Feb 22 23:38:14 UTC 2002


Obviously we need modules and clear dependencies between them so we can
load only what we need.  However, I don't like having these dependencies
defined separately, rather they should be extracted from the code. 
Please consider the following proposal for restructuring behaviors in
Squeak.  It enables behaviors to be modules, ie. a behavior knows, by
looking at its methods, which other behaviors it depends on.  And it
enables multiple inheritance, or more correctly, mixins.

Structure:
- A protocol is a list of selectors commonly viewed together.
- Each protocol has a default behavior (a method for each selector) but
the methods cannot contain direct instance variable references, they
must use accessors.
- Each selector belongs to one and only one protocol.  If two selectors
have the same name, they must be in separate protocols and each must be
prefixed with their protocol name when referenced by name.  For example,
[obj morph.step] vs. [obj debugger.step].  The prefix can be left off if
it can be assumed from the context.  For example. the "morph." prefix
can be assumed when "step" is called from within other morph methods.
- A class can implement (override) any selectors of any protocols.
- A class can inherit variables and methods from a superclass the same
way as today.
- Classes will not have to inherit from Object since all of Object's
methods can be put in default behaviors of respective protocols.
- Classes will have fewer methods, only those that need overriding. 
Only 10% of all selectors in 3.2gamma are overriden.  Here is the
calculation I used:
  | overriden all |
  overriden _ IdentitySet new.
  all _ IdentitySet new.
  Smalltalk allBehaviorsDo: [:cls |
	sup _ cls superclass.
	cls methodDict keysDo: [:sel |
		all add: sel.
		(sup notNil and: [sup canUnderstand: sel])
			ifTrue: [overriden add: sel]]].
  (overriden size / all size) asFloat "=> 0.097"

Interpretation:
- If a message selector is not in the receiver's class or superclasses
then use the selector's default method.

Loading:
- A Smalltalk expression now knows, via the selectors it calls, which
protocols and corresponding default behaviors it depends on.  So
starting from a Smalltalk expression we can trace selectors and methods
recursively and load all default behaviors that will be needed.
- Classes can be loaded on demand with their instances or when
referenced directly.  We only need to load the class methods that
override protocols already loaded, we know other protocol methods will
not be used.  We then trace the loaded class methods, like above, and
load more default behaviors and class behaviors if needed (we only load
class behaviors of already loaded classes).

Conclusion:
	In this scheme, every object understands every selector because the
default method will be invoked if the object does not override it.  This
is ok since the default method will either respond with
subclassResponsibility (equivalent to doesNotUnderstand) or will call
another method that the receiver may override.  So a class can easily
add/mixin behavior just by implementing some core methods without having
to be a subclass.
	When programming a message send you inherently know which protocol you
are targeting.  This scheme makes that explicit allowing us to see
dependencies between behaviors and protocols.  The requirement to
unambiguate a selector by prefixing if necessary, I think is justified
and would be rare.  If there is confusion about which protocol is meant,
it probably means, either the selector should be in its own seperate
protocol, or the selector should be divided into two selectors with
different names.  For example, should #at: be in "array" protocol or
"dictionary" protocol?  Really it should be in a common protocol called
"collectionAccessing" or "indexing" and removed from "array" and
"dictionary".
	Initially we can start out with every selector being in its own
protocol.  Then we can combine selectors into protocols defined by
method categories, leaving out selectors that are already in other
protocols (the image converter would do all this).  For example,
selectors in Morph stepping would go into the "morphStepping" protocol
and all its methods that don't access instance vars directly would go
into the protocol's default behavior.  Methods that have inst var refs
would stay with the Morph class, while their corresponding methods in
the default behavior would be "self subclassResponsibility".  Now any
class can inherit morphStepping behavior just by implementing some core
methods that the default behavior relies on.  You will be able to query
a behavior to find out which selectors it sends to 'self' that it does
not implemented.
	Small classes that only define differences from the default behavior
decouple the main algorithms from the special cases.  Also, senders and
implementors queries will be more accurate because it will be protocol
specific.

What do you guys think?  I'm thinking of making this my next project.
Cheers,
Anthony



More information about the Squeak-dev mailing list