Behaviors vs Modules

Les Tyrrell tyrrell at canis.uiuc.edu
Sat Feb 23 05:27:46 UTC 2002


Part of what you describe is somewhat similar to some notions that I had way
back in the beginning of the Oasis project.  In my case, one thing that I had
thought worth investigating was to alter what was meant by inheritance a
little bit.  Inheritance would still be done, but not through classes-
instead, it would run through something else which we may as well call a
protocol.  Like you mention below, the idea was to better support mix-in style
composition of behavior.  Classes would contain protocols, the protocols would
maintain their own, separate inheritance, independent of any other protocols
that might happen  to be bundled into a "class".  And the protocols would, as
you also mention below, maintain their instance state independently.


----- Original Message -----
From: Anthony Hannan <ajh18 at cornell.edu>
To: <squeak-dev at lists.squeakfoundation.org>
Sent: Friday, February 22, 2002 3:38 PM
Subject: Behaviors vs Modules


> 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.

Yes- but they may turn out to be a bit smaller than you might hope.

> - Each protocol has a default behavior (a method for each selector) but
> the methods cannot contain direct instance variable references, they
> must use accessors.

Sort of- actually, a whole family of possible default, templatized
implementations.  Sort of a pattern-like macro-defiinition of behavior for a
related family of implementations.

> - 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.

hmmm.. yes and no.  Demanding that selectors live in one and only one protocol
will yield many very small protocols ( along with some reasonable ones ).
This is what I've seen in Oasis, where I've been having some success in
extracting these protocols from vanilla Smalltalk code.  However, if you don't
demand that they live in one and only one protocol, things aren't so pretty
then either.  Actually, if you allow overlap you can very rapidly extract what
I would call "shards".  Extracting something more meaningful than that ( ie,
the "shards" also happen to be broken in a variety of ways, mainly in that
they often have selectors that clearly do not belong in the group ) is quite a
bit more involved, but to an extent, possible.  I would not reccomend
introducing additional syntax to disambiguate the intent of the selectors,
however... what matters most is that they are semantically meaningful and
consistent within the context ( ie, module ) in which they are used.

> - A class can implement (override) any selectors of any protocols.

in my case, the class would have been a unit of composition, not
implementation.  Metaprotocols would have been instantiated within the context
of a given class, according to certain preferences, to yield the desired
behavior.  How often this would have been preferrable to just having
"ordinary" classes would remain to be seen.

> - A class can inherit variables and methods from a superclass the same
> way as today.

Again, in my case they would have been inherited through the protocols
instead.

> - Classes will not have to inherit from Object since all of Object's
> methods can be put in default behaviors of respective protocols.

again, a difference in inheritance schemes...

> - Classes will have fewer methods, only those that need overriding.

hmm... I'm not so sure that a given class would actually "have" fewer methods-
on the other hand, if you are thinking of all of the inherited ones then there
could be some advantage to that since you would not by default pick up all of
those that today are defined for Object.

> Only 10% of all selectors in 3.2gamma are overriden.  [snip]
> Interpretation:
> - If a message selector is not in the receiver's class or superclasses
> then use the selector's default method.

That's an interesting statistic...

> 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.

to an extent... ie, each expression contains only fragmentary evidence, which
I call "shards".  They are pieces of the puzzle... but piecing them together
in a coherent manner proved ( at least for me ) to be pretty tough.  Although,
had I just recognized one little important thing, I could have saved about 4
years of effort.

> - 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).

hmmm.... not so sure it would pan out so gracefully as this.  I have a hunch
that many of the methods that are defined for a given class are defined for a
reason...

> 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".

They would still need separate implementations, and those implementations
would still need to have a selector naming them.

> 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.

My strategies are somewhat different, but then so were my goals.  But to the
extent that they are similar, this is why I spent so much effort in Oasis on
developing strong source analysis techniques, ie, protocol extraction.

> 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.

I think the major gains are in dealing with the common things- ie, simple
accessors and various lazy instantiation patterns/macros that really only
require the name of a variable to instantiate the implementation within a
protocol. Tools like the refactoring browser do some simple things like this
in the regular Smalltalk world, but really there are still quite a few simple,
commonly used patterns that show up all over the place.  Only the names of the
parameters change- otherwise, the pattern of implementation is the same.

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

Sucks rocks, Andy.

> Cheers,
> Anthony

Er, Anthony. ( just kidding ;^)

-les




More information about the Squeak-dev mailing list