[comp.lang.eiffel] Granularity

rick@tetrauk.UUCP (Rick Jones) (02/08/91)

In a previous article I raised the issue of mutable v. immutable objects;
I'm glad I did, it has provoked some very interesting responses:

In article <1991Feb3.225619.29491@Neon.Stanford.EDU> craig@Neon.Stanford.EDU (Craig D. Chambers) writes:

>I described this situation in a posting to comp.object a while back.

I had an inkling I'd seen or heard something on these lines before - it must
have been your article.

Are you proposing an arbitrary number of programmer-defined sub-interfaces?
Although this would be flexible, I can see that it could lead to greater
complications and ultimately make the design process even more complex.

>I'm concerned a bit about achieving the desired syntactic conciseness;
>"read-only array" is a lot longer than "array".

I agree - as above, I think there is a danger of what starts as an enhancement
eventually leading to unreadable code.

>Maybe the compiler could somehow infer the right subinterface.

If you look at it carefully, this is really what Bertrand Meyer's global
checking is intended to achieve.  The *effective* sub-interface depends on the
context in which the object reference is used.

>Of course, subinterfaces aren't strictly required, since each
>subinterface could just be a separate type/class.

See below - you're on the same track as Paul Johnson

>This sounds like you're proposing a built-in read-only subinterface
>much like const in C++.

This was my initial idea, but I'm not sure that it really addresses the problem
properly.  Continuing with the example of figures (I sometimes wonder to what
extent these things reflect reality, but at least we all understand what is
meant), then you can have an operation "scale" applicable to any figure, which
will not change its shape or properties, only its size.  You may well want a
scaleable square, but you still can't allow it to have either its number of
vertices changed, nor its aspect ratio.


In article <821@puck.mrcu> paj@uk.co.gec-mrc (Paul Johnson) writes:
>
>Yes.  I have been thinking along precisely these lines and have come
>up with some ideas which I call "Fine Grain Inheritance".

>I believe you should split your class up into lots and lots of little
>superclasses, each implementing one elementary concept (such as "first
>item" or "read/write current item") and usually having only one or two
>attributes.

I have also wondered whether this is a possible solution.  My fear is that you
might end up having to resolve awkward clashes in a complex class which
inherits from very many smaller classes.  I haven't been able to try it in
practice, so that is just a gut reaction.

>This scheme becomes completely flexible when a new type rule (which I
>call the "Sibling-Supertype" rule) is added which (put briefly) allows
>the following.

>    If the supertypes of A are a subset of the supertypes of B, then A
>    is a supertype of B.

This I like.  It appears a clean way of separating the type hierarchy from the
class hierarchy, which I believe is ultimately at the bottom of this problem.

>The only problem comes when coarse grain libraries must be reused as well.

They would still be compatible, just not as reusable as the fine grain stuff.

>Of course, tools like "flat" and "short" become absolutely vital in
>fine grain libraries, as does multiple inheritance.

Makes a convincing argument for MI :-) (but see below)

>See you all at TOOLS '91.

I look forward to meeting you - I'd like to discuss this further.


In article <1991Feb6.045542.791@visix.com> adam@visix.com (Adam Kao) writes:

>I have always felt uncomfortable with the emphasis on "Class" in
>languages like Smalltalk, C++ etc.  To me, the notion of an Object
>seems much more basic and fundamental than a Class.
> [...]
>In the real world, objects do not, strictly speaking, belong to a
>class.  A classification system is imposed on real objects by our
>world view; class membership is an answer to a question we phrase.
>Different question, different answer.

My current development project has lead to an interesting situation, which is
related both to this topic, and to the question of granularity discussed above.
Briefly, we are building OLTP server processes, and decided to use an OO
approach so we treated the servers as "classes" when defining their interfaces.
This is a clean way of giving a behavioural view of the online stored data. I
envisaged writing these servers as Eiffel classes, with the features available
as services callable by a client.  Mainly for implementation reasons this
proved impractical, and the final structure is that each "feature" of the
server is in fact written as a separate language "class".

The result is that the server "object-interface" is a composite construction,
where each "feature" is a separate component.  A new server can be assembled
using several already exisiting "features", plus new special ones where
relevant.  In this "macro-object" (it is an object - information hiding,
interface defined by behaviour, etc), reuse is by assembly, not inheritance.

Of course the smaller components are being written in an object oriented
language, but at a different level.  It seems that maybe the idea of defining
an object interface at the language level by assembling a shopping list of
features (Bertrand Meyer talks about this in OOSC), *but where each feature is
a separate module*, is a possible alternative to inheritance for reuse.  This
seems to be very close to Paul Johnson's fine grain inheritance, but is
inheritance really the correct mechanism, or does it just look right because
it's there?

I'm a bit too much up to my ears in the project at the moment to think properly
about the wider implications, but it does seem to address the issues of finer
granularity, and also the notion of type could be defined in terms of the set,
or subset, of features assembled for any particular object.

I am coming more and more to believe that finer granularity is an important
issue when it comes to effective reuse.  The larger a traditional class, the
more complex it is, and the less likely it is to be reusable.  But reuse by
inheritance by definition adds to the parent class, thus creating ever larger
classes.  It is almost self-defeating.  I'm not saying inheritance isn't
valuable, but perhaps we need something else as well.

Food for thought?

-- 
Rick Jones
Tetra Ltd.  Maidenhead, 	Your .signature has expired,
Berks, UK			please apply to your analyst for a replacement
rick@tetrauk.uucp

kers@hplb.hpl.hp.com (Chris Dollin) (02/11/91)

In all this debate about RECTANGLE, POLYGON, and add_vertex, I have wondered
one thing; what is the add_vertex feature *for*?

There are two possibilities that come to (my) mind. One is that it is required
so that a POLYGON object can be created and then its vertices added (because
they cannot be designated at the moment of creation); in this case, once the
vertex initialisation is complete, one would not expect further calls to
add_vertex. In this case it seems to me that add_vertex is really only an
encoding of create-me-this-polygon-with-these-vertices, and that a way of
saying this without lots of separate feature calls might be advisable. (In Lisp
or Pop, one might create the object passing in a list/vector/other-collection
of verticies.)

The other possibility is that during the adult life of a polygon (as opposed to
its infancy) its number of vertices changes for whatever reason (didn't
somebody suggest a graphics editor where the user can add and remove
verticies?).

I'd like to know which of these two cases is the norm in which situations, and
if there are any other reasonable patterns of add_vertex use. 

Incidentally, I'm in the camp that says "if POLYGON has add_vertex, then
RECTANGLE is not a subtype of / does not inherit from POLYGON".

--

Regards, Kers.      | "You're better off  not dreaming of  the things to come;
Caravan:            | Dreams  are always ending  far too soon."

paj@mrcu (Paul Johnson) (02/11/91)

In article <1087@tetrauk.UUCP> rick@tetrauk.UUCP (Rick Jones) writes:
>In article <821@puck.mrcu> paj@uk.co.gec-mrc (Paul Johnson) writes:

>> [...] some ideas which I call "Fine Grain Inheritance" [FGI].
>>
>>I believe you should split your class up into lots and lots of little
>>superclasses, each implementing one elementary concept (such as "first
>>item" or "read/write current item") and usually having only one or two
>>attributes.
>
>I have also wondered whether this is a possible solution.  My fear is that you
>might end up having to resolve awkward clashes in a complex class which
>inherits from very many smaller classes.  I haven't been able to try it in
>practice, so that is just a gut reaction.

This worried me when I first started thinking about the idea.  In
practice it does not seem to be much of a problem.  Name clashes are
no more common than in traditional coarse grain libraries because the
number of features is not increased, only the number of classes.

>>This scheme becomes completely flexible when a new type rule (which I
>>call the "Sibling-Supertype" rule) is added which (put briefly) allows
>>the following.
>
>>    If the supertypes of A are a subset of the supertypes of B, then A
>>    is a supertype of B.
>
>This I like.  It appears a clean way of separating the type hierarchy from the
>class hierarchy, which I believe is ultimately at the bottom of this problem.

Nice of you to say so.  Personally I would have preferred to keep the
type and class hierarchies together but I could not think of a clean way
to do it.  Separating the two makes the subtype graph implicit.  Its
no longer a heirarchy since two classes A and B can be mutual subtypes
via identical parents.  Since the subtype graph is the important one
for reuse (see below) I would have preferred to make it explicit.  On
the other hand tools to describe the subtype graph can be built fairly
easily.

On reuse: when people say "reuse" they usually mean reusing existing
code by expanding the classes.  In fact this is not particularly
powerful.  What is needed (and what FGI provides) is a way of avoiding
"case-and-paste" reuse where code is copied and then a global
search-and-replace is made to some type name.  This leads to code
bloat and some horrible problems in maintainance.

To take our existing example with rectangles and polygons, suppose we
need a centre-of-gravity (COG) function for our shapes and for some
reason it does not form part of the classes themselves.  We have one
for polygons "cog( shape: POLYGON );".  If in a coarse-grain library
we make RECTANGLE a subtype of POLYGON then we can get the cog of a
rectangle with no further effort, but we have to trust cog not to
change our shapes.  For cog that is trivial, but other functions might
not respect that and there is no way of statically type checking it.

Using FGI, we already have POLYGON_READ, so we make RECTANGLE a
subclass of that (and other things, including RECTANGLE_READ and
STRETCHABLE).  Our cog function we define as "cog(shape:POLYGON_READ)"
which guarantees that cog will only read the vertices and not change
them.  Hence we can reuse cog in a type-safe manner.

A problem arises when we have POLYGON but do not have POLYGON_READ.
We can construct it out of existing base classes but then POLYGON_READ
will not be an ancestor of POLYGON (unless we change POLYGON).  The
sibling-supertype rule gets around this because the ancestors of
POLYGON are a superset of the ancestors of POLYGON_READ.  Hence
POLYGON is a subtype of POLYGON_READ.

Paul.


-- 
Paul Johnson                               UUCP: <world>!mcvax!ukc!gec-mrc!paj
--------------------------------!-------------------------|-------------------
GEC-Marconi Research is not 	| Telex: 995016 GECRES G  | Tel: +44 245 73331
responsible for my opinions.	| Inet: paj@uk.co.gec-mrc | Fax: +44 245 75244