Behaviors vs Modules

Nathanael Schärli n.schaerli at gmx.net
Sat Feb 23 17:45:36 UTC 2002


Anthony,

What you describe sounds very interesting!! It always bothered me that ST-80
(and Squeak) only support single inheritance and don't offer a concept for
sharing behaviors (mixins, protocols, or whatever else we want to call it)
between classes. Obviously, such a concept has many benefits, such as less
code duplication, clearer structure of classes (the different aspects are
factored out in different behaviors and the class only had to specify the
code that glues those behaviors together and specializes them), etc.

A couple of weeks ago, I started a prototype implementation that adds a sort
of mixins to Squeak. The idea is similar to what you describe here. I
introduced "stateless mixins" which consist of a set of selectors with
default methods. Sometimes, such a mixin is completely independent, which
means that all the used methods are already defined in the mixin itself. In
a more general case, such a mixin refers to a set of "required selectors"
which are used by the mixin methods but not defined in the mixin itself. In
order to use such a mixin within a class (or another mixin), the programmer
has to define these required methods in an appropriate way. In other words,
the definition of these required methods is the glue code that is necessary
to use such a mixin in a particular case.

- A stateless mixin consist of a set of selectors with default methods.
- Each mixin provides meta-information such as: Provided selectors, required
selectors, etc. (These are automatically determined by analyzing the
methods).
- Every class can still use inheritance to inherit variables and methods.
- In addition, every class (and every mixin) can make use of zero or more
mixins. These mixins are not ordered, and therefore, conflicts have to be
resolved by explicitly defining (overriding) the conflicting selectors in
the class itself. (Methods defined in the class override methods defined in
the mixins).
- A mixin may contain a meta-mixin which defines class side behavior. When
such a mixin is used within a class, the methods provided by the meta-mixin
are available within the metaclass. (This guarantees inter-level
compatibility). However, if a mixin does not refer to the class side, it
does not contain a meta-mixin and can therefore be applied to both classes
and meta-classes.
- Mixins can be added to and removed from classes anytime.

This means that a class (and a mixin) can be easily composed from other
mixins that correspond to the different aspects of the class. The only
methods the programmer has to specify within the class itself is the glue
code that consists of the required and the conflicting methods of the
individual mixins. (This are typically only very few).

Note that mixins can be automatically generated from existing classes. (The
methods are automatically translated so that instance variable access,
access to super, access to class, etc. are properly replaced by other
messages, which typically correspond to "required selectors"). Since classes
should be built from mixins instead of doing it the other way round, this
feature is mainly important for being able to reuse existing code and
playing around.

One of the main problems with most of the available approachs for
mixins/multiple inheritance is the fact that it gets sometimes hard for a
programmer to understand what is actually going on. (Which methods are
available for a class? Which implementation is used if I send a certain
message? Which method do I have to override? etc.). I believe that this
problem can be tackled by having a *simple* mixin semantics (no complicated
precedence rules, explictly resolving conflicts, etc.) and, even more
important, by having a nice user interface.

In my prototype implementation, I added another pane to the Squeak browser
(between the classes and the method categories). This pane shows all the
mixins that are used by the selected class. In addition, there are the
entries '-- own --' and '-- all --'. If you select a certain mixin, the next
two panes show all the method categories and methods defined by the selected
mixin. (Similarly, if you select '-- own --' respectivey '-- all --', it
shows the categories and methods defined by the class itself respectively
all the categories and methods available in the class).
In addition to the normal method categories, there is a special category '--
required --', which shows all the selectors that are required by the
selected mixin. Similarly, there is also special category '--
conflicting --' which shows the conflicting selectors (the selectors that
are defined by multiple mixins).
Using this UI, a programmer can easily see what mixins a certain class
consists of and how these mixins are glued together.

If I understood you right, the main differences between your proposal and my
implementation is the fact that you want to make all the methods in the
system available to all the objects. Although this makes total sense from a
technical point of view, I'm afraid that this will decrease the readability
and understandability of the code because it is hard to see what aspects and
method a certain class is designed to provide. How should a programmer know
whether he is supposed to send a certain message (e.g. 'morph.step') to a
certain object? And even more important: How can a programmer avoid a
certain message to be sent? He basically has to implement the excluded
selectors as "self shouldNotImplement", right?
>From this point of view, I think that the approach with explicitely mixing
the mixins into a class leads to entities that are much easier to
understand. In fact, I believe that the classes are going to be even easier
to understand than now, because they are much better structured: Looking at
a class in the browser shows what kind of mixins (aspects) a class consist
of, what kind of methods are provided by the individual aspects and how
these methods are glued together. Nevertheless, the code of the different
mixins is only defined once (namely in the mixins) and a mixin can implement
behavior without having to be a subclass.

Another difference between the ideas you describe here and my first
implementation is the uniqueness of selectors. In my implementation,
selectors don't have to be unique. This means that there is no "nested
selector" convention (e.g. 'morph.step') that can be used in order to
resolve ambiguity in case of a selector which is defined in more than one
mixin. Instead, every conflict needs to be resolved by explicitely defining
a method for the conflicting selectors in the class that uses the mixins.
(That's why it is so important that the browser shows all the conflicts). If
we assume that a class C uses two mixins A and B that both define a selector
foo, than the programmer *has* to define a method foo in C which either uses
A>>foo, B>>foo or does anything else that is appropriate.
I have also been thinking about a "nested namespace" that makes selectors
always unique and I'm still not completely sure which is the right way to
go. Two of the main reasons why I decided against allowing the use of
"nested selectors" for now are:

(Note that these reasons do not specificly apply to your suggestion of
having every class understanding every selector. Much more, they are just
general thoughts I had when I developed the current mixin model)

- Encapsulation: In a model where an object does not understand every
selector, only the selectors defined in the class/superclass and the
selectors in the used mixins are available for a certain object. Therefore,
if a client calls 'obj morph.step', the client needs to assume that class of
obj contains a mixin that specifies a method step. But such an assumption
breaks with the concept that the concrete implementation of a certain class
(i.e. whether a method is defined in the class itself or a certain mixin)
should not matter to the clients.
- Polymorphism: Let's assume that we have a class BankAccount that uses a
mixin LockingPolicy to provide the behavior for a locking policy. Thus,
there might be a call 'obj lockingPolicy.wait', to a instance obj of
BankAccont. The problem here is that the effectively invoked method is
pretty much hardcoded in the calling statement. This is bad, because we
might want to change BankAccount so that it uses the mixin
PessimisticLockingPolicy instead of LockingPolicy. However, this does not
work because the caller explicitely specifies the target method.
- If the mixins are properly designed, I think that case where a class uses
two mixins defining the same seletor should not happen very often. If it
still happens, it is usually an indication that programmer should put some
of the class' responsibilities into other classes. And if it still happens,
the situation is so complex that the conflict should be explicitely resolved
by the programmer writing the class (and not the ones using it) anyway ;-)

The second point brings me to another problem I see with your suggestion,
but maybe I don't understand it right. Assume that there is a protocol
OptimisticLockingPolicy which provides a number of selectors and a protocol
PessimisticLockingPolicy which provides the same selectors, but implements
them differently. (This means that A and B have the same interface). If
there is now a class BankAccount that wants to make sure that it always uses
OptimisticLockingPolicy, how can you do that? The only solution is to
explicitly implement *all* the selectors x of the locking policies in the
class BankAccount as follows:

BankAccount>>x
	self OptimisticLockingPolicy.x

BankAccount>>PessimisticLockingPolicy.x
	self shouldNotImplement

Another difficulty arises when we would like to change the implementation of
BankAccount so that it uses pessimistic locking. This means that we have to
change all the selectors of the locking policies in the class bank account,
right?

I think that having explicit mixins (instead of making all the methods
globally available) and not allowing nested selectors avoids all of these
problems. It guarantees encapsulation and polymorphism, and in particular,
it allows to switch between two compatible implementations of the same
interface (e.g. optimistic and pessimistic locking policy) by simply
exchanging the mixins.
However, and that's the other side of the coin, having non-unique selectors
is a problem because it is usually not possible to know what methods are
needed in order to properly evaluate an expression. Regarding your concept
of deriving the required modules by statically looking at a certain
expression, this means that we would have to load all the methods with a
certain selector.

Well, as I said, I'm not really sure myself what is the best way to add
something like mixins to Squeak. But, and this I know for sure, I *really*
would like to have a nice mixin-like concept in Squeak, and therefore I'm
really happy about yout mail because it shows that there are other guys
thinking along the same lines and willing to invest.

As far as I see at the moment, I'm probably going to finish my first
prototype implementation by the end of next week. Then, I'd like to play
around with it and see how the system can be refactored. Like that I'll
probably learn more about the advantages/disadvantages of the different
mixin models. As I said before, the most important thing for me are not
fancy features, but simplicity, ease of use and especially undertandability
of the resulting code.

Cheers,
Nathanael

> -----Original Message-----
> From: squeak-dev-admin at lists.squeakfoundation.org
> [mailto:squeak-dev-admin at lists.squeakfoundation.org]On Behalf Of Anthony
> Hannan
> Sent: Samstag, 23. Februar 2002 00:38
> To: squeak-dev at lists.squeakfoundation.org
> 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.
> - 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