Modules instead of Behaviors (was: Generalized Object Modules Design)

Anthony Hannan ajh18 at cornell.edu
Thu Mar 7 21:48:40 UTC 2002


"Richard A. O'Keefe" <ok at cs.otago.ac.nz> wrote:
> I was at some pains to point out that code can be chopped up
> in several ways.  Because many languages (e.g., Erlang) _do_ identify
> modules and load units, I assumed that _you_ meant modules to be that.
> I don't have a precise definition of "module", except the old Parnas
> sense:  a "module" is a chunk of program that hides something.
> 
> One thing I am sure about:
>     Modules should be cohesive and should be loosely coupled.
> ("Should", not "must".)
> 	
> The traditional argument for abstract data types and OOP was that
> a "module" based on "functionality" was not cohesive.  The only example
> we've been given so far is "printing", and while my criticism talked about
> loading, the fundamental problem is cohesion ("printing" in the sense of
> #printOn: just isn't all that cohesive) and coupling (it would have to
> know about several sorts of Morphs with their fullPrintOn:, Object and
> ExternalObject with longPrintOn:, AppleScript, 3D graphics, and a lot of
> other things with printOn:, ...).  Whatever a Module is, I want it to be
> something that improves the structure of my code.  Slicing it _that_ way
> doesn't seem to do the job.
> 
> Clearly I don't understand yet whatever it is you have in mind.
> Can you provide some examples of exactly what kind of slice you are
> thinking of and how it improves structure?

Your right I was swinging the pendulum too far toward the functionality
side.  Putting all printOn: method in one functionality module would
require other functionalities to export (make public) accessor selectors
just so the printOn: methods in the 'Printing' functionality could use
them.  However, if the printOn: methods stayed with their class the
accessors could stay private.  Thanks for helping me see this, of course
I new this but I was temporarily blinded by concentrating on selector
namespaces.  Its great to have this mailing list to bounce ideas off.
	Just structuring code by classes, however, is too extreme in the other
direction, hence our need for modules and Henrik's work.  But to keep in
line with the functional notion of a module - a module exports interfaces
for other modules to use* - I still think Henrik's module design needs
improving, ie. it needs a way to specify exported selectors.
	In Erlang and many other function-oriented languages, a module is a
set of functions with only a subset of those functions (the exported ones)
callable by other modules.  For maintainability/readability the goal
when building modules is to minimize the number of exported functions
and minimize the number of imported modules*.  ModuleA is said to import
ModuleB iff ModuleA calls one or more of ModuleB's exported functions.
	So we need a way to specify which selectors in a module are exported,
in addition to which globals.  And we need a way to know which module is
imported when a message is sent or global is used.  My functionality
modules proposed in previous emails solved this but it violated the
minimal interface goal above.
	We could just keep classes as modules, but it would also violate the
minimal interface goal.  A message send would imply importing all
classes that implement that selector.  Also, many selectors would be
exported that really shouldn't be because they are specific to just one
functionality.  For example, in the 'Exceptions' functionality, the
#signal method (or one of its private methods) sends
#findNextExceptionHandlerFor:~ to thisContext, a MethodContext. 
#findNextExceptionHandlerFor: is unique to exceptions and should not be
exported to the general public.
	So we need a way to group methods by class and by functionality.

	Here is what I propose (again, but I think my proposals are getting
better each time).  Define a new type of meta-object called
Functionality.  A functionality has a name, holds public & private
globals (classes), and declares a set of public & private selectors.  A
class can implement methods for public/private functionality selectors
or its own internal selectors.  A message send is always bound to a
specific functionality selector or internal class selector.  Internal
class selectors can only be sent from methods in the defining class or
its subclasses.  A public functionality selector can be sent from any
method.  And a private functionality selector can only be sent from a
method whose selector is in the same functionality or from an internal
method that is called directly or indirectly by a method whose selector
is in the same functionality.  If a selector name is ambiguous it can be
prefixed by its functionality name.  Globals will use the same binding
rules.
	For example, the 'Exceptions' functionality would declare #signal,
#on:do:, #resume:, #return:, etc. as public selectors, and
#findNextExceptionHandlerFor: as a private selector, plus it would hold
public globals Exeception, Error, MessageNotUnderstood, Warning, etc. 
The Exception class would implement #signal which may call an internal
method called #executeNextHandler~ which may call the private
functionality selector, #findNextExceptionHandlerFor:.  MethodContext
would implement #findNextExceptionHandlerFor:.
	Now we have the concept of functionality interfaces that export its
public globals and selectors, and the concept of class modules that
implement functionality interfaces and import/use other functionality
interfaces.  Classes implement a minimal number of public exports, a
small number of private exports, and an arbitrary number of internal
selectors that are kept hidden from the rest of the system.  And we can
find precisely which classes are imported because selectors are
functionality specific.

	Henrik's module implementation can be adapted to include the features
described above.  My functionalities is equivalent to Henrik's modules. 
The main differences are:
	1. A functionality does not have sub-functionalities.  Functionalities
are only imported/used, and since functionalities are not loadable units
(see next point) we do not have to specify sub-functionalities just so
they can be loaded together.  However, functionalities are organized in
a directory structure for clarity, they are just always at the leaves.
	2. A functionality does not have delta-functionalities.  A
functionality only holds globals and exported selector names.  A
functionality is not a loadable unit.  A loadable unit is like a
changeset that can add whole classes or just add methods to existing
classes.  Loadable units are better handled by a separate mechanism
(like PIE layers) and should not be combined with
modules/functionalities/namespaces/interfaces/whateverYouWantToCallThem.
 Separating the loadable unit design from the module design - besides
making each design cleaner - allows us to reuse the loadable unit design
for arbitrary objects like Morphic projects.  Tomorrow, I will write an
email about my loadableUnits/layers design.
	3. A functionality defines a selector namespace.  Instead of selectors
being looked up in a global symbol table they are looked up in the
export lists of functionalities and internal selectors of classes. 
Ambiguities have to be explicitly prefixed.

Cheers,
Anthony

*  Klas Eriksson, M Williams, J Armstrong, "Erlang Programming Rules and
Conventions" <http://www.erlang.se/doc/programming_rules.shtml>

~  #findNextExceptionHandlerFor: and #executeNextHandler are implemented
in the block closure image



More information about the Squeak-dev mailing list