[comp.object] Inheritance vs. Composition

davidm@uunet.UU.NET (David S. Masterson) (02/11/90)

I'm curious as to the reasons that one might use inheritance (derivation of a
new object from the definition of an old object) and the reasons that one
might use composition (wrapping multiple objects in another object).  I have
been getting definitions around my work area that tend to conflict with my own
views and I'd like to build a body of evidence one way or the other in order
to help make my own views understood.  Anyone care to provide their
definitions with examples as to how to apply their definitions?

--
===================================================================
David Masterson					Consilium, Inc.
uunet!cimshop!davidm				Mt. View, CA  94043
===================================================================
"If someone thinks they know what I said, then I didn't say it!"

ark@alice.UUCP (Andrew Koenig) (02/12/90)

In article <CIMSHOP!DAVIDM.90Feb11005056@uunet.UU.NET>, cimshop!davidm@uunet.UU.NET (David S. Masterson) writes:

> I'm curious as to the reasons that one might use inheritance (derivation of a
> new object from the definition of an old object) and the reasons that one
> might use composition (wrapping multiple objects in another object).

The key difference in C++ is that if class D inherits [publicly] from
class B, then you can pass a D argument to any function that takes
a B parameter.  Another way of looking at it is that if you derive
D from B, then a D object does [at least] everything a B object does.
If you define a class D with a B member, then you must define every
operation you want a D to be able to perform, even if that is replicating
many of the B operations.
-- 
				--Andrew Koenig
				  ark@europa.att.com

drich@klaatu.lanl.gov (David Rich) (02/12/90)

In article <CIMSHOP!DAVIDM.90Feb11005056@uunet.UU.NET>
 cimshop!davidm@uunet.UU.NET (David S. Masterson) writes:

   I'm curious as to the reasons that one might use inheritance (derivation of a
   new object from the definition of an old object) and the reasons that one
   might use composition (wrapping multiple objects in another object).

   David Masterson
   uunet!cimshop!davidm

When I make use of "inheritance" as you've defined it, I'm typically
creating an "is-a" relationship between the old and new object.  In
other words, I want the new object to behave like the old object plus
some additional functionality, etc.

When I think of "composition", I think of two kinds: (1) composition
via multiple inheritance and (2) composition via encapsulation, or
ownership (for lack of a better term).  When I make use of (1), I'm
simply applying the "is-a" relationship multiple times.  When I do
this, I know that I want the composite object to be able to have
direct access to all the attributes and methods of its ancestors.
However, when the composite object shouldn't really have direct access
to the attributes and methods of its ancestors, each ancestor becomes
an attribute of the composite object (in the form of an object reference).
In other words, the composite object needs the functionality, but the
relationship is not an "is-a" type.  Here's an example from a
simulation project that I'm working on.  I've built up a map object
that represents an interface to terrain data such as relief,
vegetation, mobility, roads, rivers, etc.  The map encapsulates this
information and provides methods that will allow objects to query the
state of the terrain, generate routes to move along, etc.  The
question that came up was how to give this functionality to objects
that need to move around on terrain.  Using multiple inheritance to
give a truck, for example, the ability (among other things) to
construct movement routes over terrain doesn't seem right to me.  The
inheritance mechanism gives the truck access to information that it
really shouldn't have (e.g., the truck can change the state of terrain
directly by modifying attributes).  The alternative, it seems to me,
is to give the truck a map attribute.  That is, a reference to a map
object through which it can deal with terrain appropriately (via the
maps "public" interface and not "under the table" so to speack).

I guess the point I'm trying to make here is that composition via
multiple inheritance may give the composite object information it
doesn't need or shouldn't have access to (if there isn't a natural
"is-a" type relationship).  Composition by encapsulation (which may
not be an appropriate term since I just made it up) is a way around
this.

This "inheritance"/"composition" issue is one we've been trying to get
a grasp on too.  It seems that the line (for us anyway) is somewhat
subjective.  What's "is-a" to one person may not be to another.  In a
lot of ways, this may just be another "soap box" issue.

Dave
--




--
David Rich
Military Systems Analysis Group (A-5)
MS F602
Los Alamos National Laboratory
Los Alamos, NM 87545

Phone: (505) 665-0726
Email: dor@lanl.gov

davidm@uunet.UU.NET (David S. Masterson) (02/13/90)

In article <10465@alice.UUCP> ark@alice.UUCP (Andrew Koenig) writes:
   In article <CIMSHOP!DAVIDM.90Feb11005056@uunet.UU.NET>, [I] wrote:
   > I'm curious as to the reasons that one might use inheritance (derivation
   > of a new object from the definition of an old object) and the reasons
   > that one might use composition (wrapping multiple objects in another
   > object).
   >
   The key difference in C++ is that if class D inherits [publicly] from
   class B, then you can pass a D argument to any function that takes
   a B parameter.  Another way of looking at it is that if you derive
   D from B, then a D object does [at least] everything a B object does.
   If you define a class D with a B member, then you must define every
   operation you want a D to be able to perform, even if that is replicating
   many of the B operations.

This is interesting...

The tone here suggests that one idea is good and the other is not so good
(intentional?).  Also, except for "derive...[when] object does everything [the
base] object does", there's no real suggestion of a model to follow here.  Its
more of rules of thumb about programming style.  This is one of the points
that I seem to be finding in the use of C++ (and perhaps generally in OOP) by
programmers with a light background in the concepts (I'm certainly NOT
suggesting that Andrew Koenig has a light background in the concepts).
Instead of a design model to follow, the approach seems to be more of "do what
seems right programmatically" and the design will take care of itself.  Is
this appropriate or do people see the need for more "principles" to follow to
promote properly designed, reusable code development?

--
===================================================================
David Masterson					Consilium, Inc.
uunet!cimshop!davidm				Mt. View, CA  94043
===================================================================
"If someone thinks they know what I said, then I didn't say it!"

ark@alice.UUCP (Andrew Koenig) (02/13/90)

In article <CIMSHOP!DAVIDM.90Feb12100359@uunet.UU.NET>, cimshop!davidm@uunet.UU.NET (David S. Masterson) writes:
> In article <10465@alice.UUCP> ark@alice.UUCP (Andrew Koenig) writes:

>    The key difference in C++ is that if class D inherits [publicly] from
>    class B, then you can pass a D argument to any function that takes
>    a B parameter.  Another way of looking at it is that if you derive
>    D from B, then a D object does [at least] everything a B object does.

> This is interesting...

> The tone here suggests that one idea is good and the other is not so good
> (intentional?).  Also, except for "derive...[when] object does everything [the
> base] object does", there's no real suggestion of a model to follow here.

I do not intend to imply that one alternative is good and the
other bad -- I'm just trying to point out the differences.

Each approach has its place.  It's a little like trying to decide
whether to use screws or nails -- each can sometimes substitute for
the other, but each has advantages and disadvantages.
Which one you choose depends on the details of the problem
you're trying to solve.
-- 
				--Andrew Koenig
				  ark@europa.att.com

dag@control.lth.se (Dag Bruck) (02/13/90)

In article <CIMSHOP!DAVIDM.90Feb12100359@uunet.UU.NET> cimshop!davidm@uunet.UU.NET (David S. Masterson) writes:
>In article <10465@alice.UUCP> ark@alice.UUCP (Andrew Koenig) writes:
>   The key difference in C++ is that if class D inherits [publicly] from
>   class B, then you can pass a D argument to any function that takes
>   a B parameter.
>
>Instead of a design model to follow, the approach seems to be more of "do what
>seems right programmatically" and the design will take care of itself.  Is
>this appropriate or do people see the need for more "principles" to follow to
>promote properly designed, reusable code development?

In general, I think insight is more important than principles.

We often talk about reuse, but we must distinguish between (at least)
two different forms of reuse: reuse of code and reuse of abstraction.
The "American" argument for OOP is to reuse existing code, while the
"European" argument is to use inheritance as a structuring and
abstraction mechanism.  I find both equally important, but reuse of
code is much easier than reuse of abstraction.

I also believe reuse of code naturally lends itself to bottom-up
development, while reuse of abstraction lends itself to top-down
development. In practice we often alternate between the top-down and
the bottom-up approaches.

The C++ world is almost singularly concerned with code reuse.  This is
demonstrated whenever C++ is described, and also when Andrew Koenig
uses a function call for describing inheritance.  It is also obvious
that many Smalltalk, Objective-C and Eiffel friends believe that reuse
of abstraction is next to impossible in C++ -- a complete
misconception in my view.

Dag M. Bruck

gza@mentor.cc.purdue.edu (William R Burdick) (02/13/90)

The difference between inheritance (or generalization) and composition
(or aggregation)  is a tough point   to  figure  out without some good
examples (I hope I  can give some here).   Depending on  what language
you use and what you use your objects for, you may not be able to tell
what  the  difference is  right  off.   Mainly, inheritance  passes on
behavior to subclasses and composition passes on form to aggregations.

As  an  easy way to   tell the difference right  off   (using multiple
inheritance), consider readable  files, writable files, and read-write
files.  You  can say  that a  read-write file  inherits  behavior from
readable files  and writable files.   If you   were to   try to make a
read-write file out of a composition, you might end up  with an object
containing  two files, a readable file  and a  writable  file.  If you
make a Read-Write File (the class) a  subclass of Read File  and Write
File, you get a kind  of file which  can be read from and  written to.
classes use  inheritance   and  classes are   definitions  of objects.
Instances   use composition, and    instances  are   the  objects.   A
particular  read-write  file does   not inherit   behavior  from   its
superclass because it is not a class, it is a file, while a read-write
file could be composed of a read file and a write file.

When you want to  model objects which are  made of other  objects, use
composition, when you want  to  model objects  which behave like other
kinds of objects, use inheritance.  The key words  here are 'made of,'
'other objects,' 'behave  like,' and 'kinds of objects.'   When I  use
'kinds of objects,' I am referring to objects of other classes, when I
use 'other  objects,'  I am referring to just  any  other objects.  An
object  composed  of  other   objects shares     a   'made-of/part-of'
relationship with those objects, while  a class shares  a 'has-a/is-a'
relationship with its subclasses.

In OOPLs, you can use instance variables  for the part-of relationship
and you can use subclassing for the is-a relationship.

	
--
	-- Bill Burdick
	burdick@cello.ecn.purdue.edu

pcg@rupert.cs.aber.ac.uk (Piercarlo Grandi) (02/13/90)

In article <10465@alice.UUCP> ark@alice.UUCP (Andrew Koenig) writes:

   In article <CIMSHOP!DAVIDM.90Feb11005056@uunet.UU.NET>,
   cimshop!davidm@uunet.UU.NET (David S. Masterson) writes:

   > I'm curious as to the reasons that one might use inheritance (derivation of a
   > new object from the definition of an old object) and the reasons that one
   > might use composition (wrapping multiple objects in another object).

   The key difference in C++ is that if class D inherits [publicly] from
   class B, then you can pass a D argument to any function that takes
   a B parameter.  Another way of looking at it is that if you derive
   D from B, then a D object does [at least] everything a B object does.
   If you define a class D with a B member, then you must define every
   operation you want a D to be able to perform, even if that is replicating
   many of the B operations.

But this is just syntactic sugaring. And it leads to a lot of problems
as well, like all the funny rules about multiple inheritance, and the
ambiguity between inheritance as is-a and as part-of, and so on.

The inheritance issue is bogus and clouds the real problem, that
in sufficiently sophisticated applications you have a data
modeling and design problem of the same size and complexity as
that of a data base, and you need to apply the same conceptual
tools, and map the concepts onto ultimately contiguity or
pointers (the only ways we have in a computer to indicate a
relationship between data).

Just think that inheritance could be entirely obviated if one were
allowed to use composition with nameless members, or if a member could
be identified by any unambiguous path to it, like in PL/1.

Example:

	class vehicle { ... int weight, speed, cost; ... }	v0;
	class car : vehicle { ... int seats; ... }		c0;
	class truck : vehicle { ... int load; ... }		t0;

vs. (assuming you have unnamed members):

	class vehicle { ... int weight, speed, cost; ... }	v1;
	class car { ... vehicle; int seats; ... }		c1;
	class truck { ... vehicle; int load; ... }		t1;

or (assuming memberscan be named by any unambiguous way):

	class vehicle { ... int weight, speed, cost; ... }	v2;
	class car { ... vehicle v; int seats; ... }		c2;
	class truck { ... vehicle v; int load; ... }		t2;

You can write 'c0.speed' or 'c1.weight' or 't2.cost'
indifferently; the same goes of course for function members
(which should be abolished as well, by the way, by allowing any
function whose first argument is of a class type to be invoked in
infix position).

Getting rid of inheritance, and using only composition with some
sugaring to make members of members visible at the outer level, is
entirely possible.

	You also need a few paraphernalia, like sensible casts
	between	(pointer to a member)/(to the enclosing structure)
	and viceversa, and sensible member pointers.

It is desirable on two grounds: that inheritance is redundant, and it
clouds serious discussions about the many problems of data modeling
using composition.

	I strongly believe that inheritance was invented *only* because
	in Simula 67 class objects could only be accessed via references,
	and 'prefixing' (as it was more correctly called) was needed as
	an efficient way of concantenating object representations. The
	essentially linear nature of prefixing is what causes all the
	conceptual and implementation problems with MI.

	Notice also that in some cases MI has actually to be implemented
	using references in C++. Doesn't this point to a fundamental
	problem with the concept? :-(.
--
Piercarlo "Peter" Grandi           | ARPA: pcg%cs.aber.ac.uk@nsfnet-relay.ac.uk
Dept of CS, UCW Aberystwyth        | UUCP: ...!mcvax!ukc!aber-cs!pcg
Penglais, Aberystwyth SY23 3BZ, UK | INET: pcg@cs.aber.ac.uk

jimad@microsoft.UUCP (JAMES ADCOCK) (02/14/90)

In article <10465@alice.UUCP> ark@alice.UUCP (Andrew Koenig) writes:
>The key difference in C++ is that if class D inherits [publicly] from
>class B, then you can pass a D argument to any function that takes
>a B parameter.  Another way of looking at it is that if you derive
>D from B, then a D object does [at least] everything a B object does.
>If you define a class D with a B member, then you must define every
>operation you want a D to be able to perform, even if that is replicating
>many of the B operations.

More particularly, if you want to be able to use D as a B--and then some,
one must either/or inherit from B and make non-virtual methods of B available
unchanged from D, and/or inherit from B and possibly change some of the
virtual methods of B to reflect the added functionality of D.  In either 
case, the public methods of B can be thought of as a protocol that D
must conform to.  

The important thing to note is that in C++, unlike some other OOPLs, 
even if one delared the same-named methods as B in a class D' *not*
derived from B, those same-named methods *would-not* match B's protocol.
So D' could not be used as a B, even though D' *seemed* to have all the
correctly-named methods necessary to act like a B.  

D can be used as a B only via inheritence.

marc@dumbcat.UUCP (Marco S Hyman) (02/14/90)

In article <CIMSHOP!DAVIDM.90Feb12100359@uunet.UU.NET> cimshop!davidm@uunet.UU.NET (David S. Masterson) writes:
    Instead of a design model to follow, the approach seems to be more of
    "do what seems right programmatically" and the design will take care
    of itself.  Is this appropriate or do people see the need for more
    "principles" to follow to promote properly designed, reusable code
    development?

You start out trying to follow the right principles but end up satisfying
the customer.  That is, when the ultimate goal is to ``finish the damn thing
and make it work'' you tend to do ``what seems right programmatically.''
How good or bad this is in the long run depends upon the language being used
and the skill level of the programmer.  The more highly skilled the
programmer, the more s/he can do with the language and the more that
``seems right.''  If the language supports the principles you wish to follow
the easier it is to ``do it right.''

Or am I the only one that feels frustrated when real world constraints force
me to program in C or Assembler after I've been having so much fun with
Smalltalk or C++?

// marc				{ames,pyramid,sun}!pacbell!dumbcat!marc

djones@megatest.UUCP (Dave Jones) (02/15/90)

From article <PCG.90Feb13153607@rupert.cs.aber.ac.uk>, by pcg@rupert.cs.aber.ac.uk (Piercarlo Grandi):
...
> 
> The inheritance issue is bogus and clouds the real problem ...

Hear! Hear! I've been wanting to say this, but I've been reluctant
to get into the scrap with people who know all the fashionalble buzz-words.

> ... [ to ] map the concepts ultimately onto contiguity or
> pointers (the only ways we have in a computer to indicate a
> relationship between data).
> 

That's what this all comes down to isn't it? That and what kind of
shorthand-resolution you want the compiler to do for you. (Not easy
questions, actually.)

When I get into a discussion of this stuff, and inevitably somebody
says something like, 'But that can all be solved with post-recurrent
semideclined prevaricated polytrophism,' I say, 'Mumble, ... refresh
my memory on what that means. What do the compiler's lookup-tables do,
and when do you use pointers at runtime?'

> Just think that inheritance could be entirely obviated if one were
> allowed to use composition with nameless members, or if a member could
> be identified by any unambiguous path to it, like in PL/1.
> 

Did PL/1 do that? It's been so long since I used that language -- over
fifteen years I guess -- I've forgotten most everything about it. PL/1
has been ragged so hard over the years, it seems strange to hear one
of its features mentioned in a positive way.

For the last few years, just for the fun of it, I've been dreaming up my
own sequential imperative language. Don't know if I'll ever get around to
implementing it. I had decided that all this 'inheritance' and 'composition'
debate comes down to nothing more than when to use pointers and what kind of
shorthand you prefer.  I decided on a scheme similar to the ones you describe.
Ditto with function-members. It's good to know that at least one other person
on this planet sees things the way I do.

adiseker@potomac.ads.com (Andrew Diseker) (02/15/90)

In article <CIMSHOP!DAVIDM.90Feb11005056@uunet.UU.NET> cimshop!davidm@uunet.UU.NET (David S. Masterson) writes:
>I'm curious as to the reasons that one might use inheritance (derivation of a
>new object from the definition of an old object) and the reasons that one
>might use composition (wrapping multiple objects in another object).  I have
>been getting definitions around my work area that tend to conflict with my own
>views and I'd like to build a body of evidence one way or the other in order
>to help make my own views understood.  Anyone care to provide their
>definitions with examples as to how to apply their definitions?
>
>--
>===================================================================
>David Masterson					Consilium, Inc.
>uunet!cimshop!davidm				Mt. View, CA  94043
>===================================================================
>"If someone thinks they know what I said, then I didn't say it!"


	What follows are my opinions only, flames will be gleefully ignored
(I've been roasted by the best %^).

	From my experience working with and having discussions with some
excellent OO programmers, I think the answer to the first question about
derivation could be this:  If there is an existing definition of an object
that is close ( however you define that ) to what you want to use, then
use your language's inheritance mechanism to create a copy of the definition
and modify the parts that don't do quite what you want, and add whatever 
you need for any extra functionality.  There, I didn't use the dreaded
'c' word, didn't mention data hiding, or any other religious terms.  I tried
to keep it general enough that it could even apply to (gag, choke) ADA.

	Now, however, it's time to don the asbestos suit.  The second part
of your question, referring to composition classes, is a matter of strong
opinions on two sides.  My personal reason for using composition objects
is to circumvent the need for multiple inheritance.  If I have two or
more classes which exhibit the functionallity I need, and which contain the
data in which I'm interested, then one object which contains instances of 
those classes and which allows whatever access is needed to those instances, 
is all I need.  I just haven't seen any schemes for MH ( acronyms, anyone? )
that are general enough to be effective, and specific enough to cover all
the holes.  If you want to know what I'm talking about, just watch the next
hundred or so responses to this post! %^) %^) %^)  I really hate to have
my compiler ( environment, whatever ) bogged down trying to prevent or 
otherwise handle the abiguities that naturally arise when you try to munge
multiple, possibly disparate, possibly related classes.  I call it a "what
happens when cousins marry?" problem.  Ahem.  Now that I've offended any
number of people, I'll step aside and watch the mayhem.  Hope I've helped
in some way, though.


-- 
Andrew Diseker                            ( )-( )   Mouse-Tested!
Advanced Decision Systems                  /o o\    Hacker-Approved!
UUCP: sun!sundc!potomac!adiseker          / =v= \ ,--_
Internet:  adiseker@potomac.ads.com       ;;---;;     `--'

sjs@spectral.ctt.bellcore.com (Stan Switzer) (02/16/90)

Dave Jones (djones@megatest.UUCP) writes:
> From article <PCG.90Feb13153607@rupert.cs.aber.ac.uk>,
>    by pcg@rupert.cs.aber.ac.uk (Piercarlo Grandi):
> 
> > ... [ to ] map the concepts ultimately onto contiguity or
> > pointers (the only ways we have in a computer to indicate a
> > relationship between data).
> 
> That's what this all comes down to isn't it? That and what kind of
> shorthand-resolution you want the compiler to do for you. (Not easy
> questions, actually.)

I was meaning to comment on this earlier, but I let it pass.  There
are many ways to represent relationships in a computer.  For instance,
relational databases (conceptually) use an associative technique based
on the equality of attribute values.  Another associative technique is
pattern matching (grep, and X resource databases).  Other types of
reference relationships might include inter-addressing-environment
references, file references, and persistent-object store references.

Even granting the importance of contiguous values and pointer
references, there are all sorts of possible pointer semantics: "Atom"
semantics (eq <=> equal -- SETL, or some dialect of SETL, did this),
capability references, self-describing references, context-described
references, and simple storage-indexing memory references.

> When I get into a discussion of this stuff, and inevitably somebody
> says something like, 'But that can all be solved with post-recurrent
> semideclined prevaricated polytrophism,' I say, 'Mumble, ... refresh
> my memory on what that means. What do the compiler's lookup-tables do,
> and when do you use pointers at runtime?'

Sure, there is a lot of terminology out there, some of it needlessly
complicated, but this terminology _does_ attempt to make useful
distinctions.

> For the last few years, just for the fun of it, I've been dreaming up my
> own sequential imperative language.

What!  Another one?

Stan Switzer  sjs@bellcore.com

jss@jra.ardent.com (02/16/90)

In article <8361@potomac.ads.com> adiseker@potomac.ads.com (Andrew Diseker) writes:
>
>	From my experience working with and having discussions with some
>excellent OO programmers, I think the answer to the first question about
>derivation could be this:  If there is an existing definition of an object
>that is close ( however you define that ) to what you want to use, then
>use your language's inheritance mechanism to create a copy of the definition
>and modify the parts that don't do quite what you want, and add whatever 
>you need for any extra functionality.  

This may be good advice for "generic OO" but it is bad 
advice for C++.   

I believe that the "protected interface of a class" (i.e.  the 
relation between a class and classes derived from it) must be specified 
just as carefully as the specification of any member, and if 
the original class was not designed with such an interface then you 
shouldn't be deriving from it.  

Many C++ classes are designed with a value semantics rather than
an object semantics.  That is, they are intended to have their
values copied rather than pointers to them passed around.  The
classsical example is "complex".  Even when it isn't obvious
from the external interface, the implementation of a class
may copy values around in various ways.  It is almost always
a mistake to derive from these classes.

When I to use a class supplied by somebody else, I use it in 
the way it was designed to be used.  When I want to copy code,
I copy it.

Jerry Schwarz

sakkinen@tukki.jyu.fi (Markku Sakkinen) (02/16/90)

In article <PCG.90Feb13153607@rupert.cs.aber.ac.uk> pcg@rupert.cs.aber.ac.uk (Piercarlo Grandi) writes:
>In article <10465@alice.UUCP> ark@alice.UUCP (Andrew Koenig) writes:
>> ...
>But this is just syntactic sugaring. And it leads to a lot of problems
>as well, like all the funny rules about multiple inheritance, and the
>ambiguity between inheritance as is-a and as part-of, and so on.

There is sense behind this oversimplification. However, you could go all
the way and say that any programming language is just a universal Turing
machine with some syntactic sugaring; it tastes better to me with at least
a little sugar.

>The inheritance issue is bogus and clouds the real problem, that
>in sufficiently sophisticated applications you have a data
>modeling and design problem of the same size and complexity as
>that of a data base, and you need to apply the same conceptual
>tools, and map the concepts onto ultimately contiguity or
>pointers (the only ways we have in a computer to indicate a
>relationship between data).

That we have a limited choice in physical implementation has no
bearing on what conceptual tools and models we can or should use.
(Besides, there are other ways, e.g. "foreign keys".)
Many people in the data base community are applying
OO concepts, including inheritance. It is one of the mandatory
features suggested in "The object-oriented database system manifesto",
which was written by Atkinson, Bancilhon, DeWitt, Dittrich, Maier,
and Zdonik for the DOOD'89 conference.

>Just think that inheritance could be entirely obviated if one were
>allowed to use composition with nameless members, or if a member could
>be identified by any unambiguous path to it, like in PL/1.
> ...
>Getting rid of inheritance, and using only composition with some
>sugaring to make members of members visible at the outer level, is
>entirely possible.

This is pretty much in line with what both Raj and Levy - "A compositional
model for software reuse" and myself - "Disciplined inheritance"
presented at ECOOP'89. I think both parties had at least played with
the idea of calling the paper - you guessed it - "Inheritance
considered harmful".

> ...
>	I strongly believe that inheritance was invented *only* because
>	in Simula 67 class objects could only be accessed via references,
>	and 'prefixing' (as it was more correctly called) was needed as
>	an efficient way of concantenating object representations. The
>	essentially linear nature of prefixing is what causes all the
>	conceptual and implementation problems with MI.

I agree with much of this diagnosis, although I doubt '*only*'.
Incidentally, even two very new languages, Modula-3 and Oberon
(the latter extremely Spartan even in other ways),
seem to follow the same prefixing principle and stick
confidently to single inheritance. By the way, it still holds for almost
all common OOPL's that class objects can only be accessed via references.
- All conceptual problems of MI don't stem from the linear nature of
prefixing as far as I can see: e.g. the Flavors model looks bad to me
because it doesn't preserve the integrity of the superclass parts.

>	Notice also that in some cases MI has actually to be implemented
>	using references in C++. Doesn't this point to a fundamental
>	problem with the concept? :-(.

No, in my analysis (see the ECOOP paper) the MI _principles_ of C++ are
sounder than any others I have studied. The _implementation_ of MI has
evidently become cumbersome because C++ has offered the strong
concept of object identity (as seen in e.g. Smalltalk) on the altar
of "efficiency".

Markku Sakkinen
Department of Computer Science
University of Jyvaskyla (a's with umlauts)
Seminaarinkatu 15
SF-40100 Jyvaskyla (umlauts again)
Finland
          SAKKINEN@FINJYU.bitnet (alternative network address)

djones@megatest.UUCP (Dave Jones) (02/17/90)

From article <20020@bellcore.bellcore.com>, by sjs@spectral.ctt.bellcore.com (Stan Switzer):
...
> 
>> For the last few years, just for the fun of it, I've been dreaming up my
>> own sequential imperative language.
> 
> What!  Another one?
> 

Why not? I said, 'just for the fun of it.' Besides, I'm not satisfied with
any that I've seen so far.

djones@megatest.UUCP (Dave Jones) (02/17/90)

From article <20020@bellcore.bellcore.com>, by sjs@spectral.ctt.bellcore.com (Stan Switzer):
> Dave Jones (djones@megatest.UUCP) writes:
>> From article <PCG.90Feb13153607@rupert.cs.aber.ac.uk>,
>>    by pcg@rupert.cs.aber.ac.uk (Piercarlo Grandi):
>> 
>> > ... [ to ] map the concepts ultimately onto contiguity or
>> > pointers (the only ways we have in a computer to indicate a
>> > relationship between data).
>> 
>> That's what this all comes down to isn't it? That and what kind of
>> shorthand-resolution you want the compiler to do for you. (Not easy
>> questions, actually.)
> 
> I was meaning to comment on this earlier, but I let it pass.  There
> are many ways to represent relationships in a computer.  For instance,
> relational databases (conceptually) use an associative technique based
> on the equality of attribute values.  [...]

These other relationship techniques you named are all search-and-test
operations. You still need some way to find that something to test. Notice
that Mr. Switzer used the word 'ultimately'. These search-and-test
operations are indeed ways to indicate relationships, but they *ultimately*
depend on contiguity or pointers. Contiguity and pointers are the
atomic operations of association in digital computers.

Still the question remains as to whether you want the higher level
operations to be an integral part of a programing language, as
hash-tables are, for example, in AWK, or do you want to furnish
these capabilities as {utility/library/class} functions which extend
a basic language?  My current opinion is that if the language has good
enough extention mechanisms, it's best to start with only a small set
of primitive operations, and build. That way, you don't tend to get
'languaged into a corner' with this language having this feature and that
language having that one, and no way to mix them. (I often wish I could link
my C-procedures into an AWK program.) Problem is, I don't know of any
language whose extention mechanisms are 'good enough'. But then,
I really haven't been keeping up over the last few years. C++ is close,
though.

pcg@aber-cs.UUCP (Piercarlo Grandi) (02/19/90)

In article <20020@bellcore.bellcore.com> sjs@bellcore.com Switzer) writes:
    Dave Jones (djones@megatest.UUCP) writes:
    > From article <PCG.90Feb13153607@rupert.cs.aber.ac.uk>,
    >    by pcg@rupert.cs.aber.ac.uk (Piercarlo Grandi):
    > 
    > > ... [ to ] map the concepts ultimately onto contiguity or
    > > pointers (the only ways we have in a computer to indicate a
    > > relationship between data).
    > 
    > That's what this all comes down to isn't it? That and what kind of
    > shorthand-resolution you want the compiler to do for you. (Not easy
    > questions, actually.)
    
    I was meaning to comment on this earlier, but I let it pass.  There
    are many ways to represent relationships in a computer.

I beg to differ. In a *computer* you have memory as a block of storage
units, and composite entities can only be built out of contiguity or
pointers. C++ is a language that tries hard to let you do *implementation*
level work, that is "full unrestricted" access to the underlying hardware.

The type constructors in C++, and other machine oriented HLLs, are records
and arrays for heterogenous and homogeneous contiguity, and pointers.

    For instance, relational databases (conceptually) use an associative
    technique based on the equality of attribute values.  Another
    associative technique is pattern matching (grep, and X resource
    databases).  Other types of reference relationships might include
    inter-addressing-environment references, file references, and
    persistent-object store references.

But these are all *program* level abstractions. One of the big tasks of
programming is to map all the useful data structuring abstractions you have
(and these are usually content based) onto contiguity and pointers. An
especially hard job because contiguity is bad for dynamic resizing and
pointers by their very nature are one way only, and this has many profound
consequences (e.g. on garbage collection) in all the common cases where you
have many-to-many abstractions to represent (just think that essentially
network databases are there only to give you bidirectional pointers).

It is notable about C++ is that BS has carefully designed it to retain by
and large (the two notable exceptions being constructors/destructors and
multiple virtual inheritance) the MOHLL flavour of C. Eiffel for example,
not to speak of CLOS or Simula 67, do not have this aim.

What I find disagreeable about virtual multiple inheritance (I also don't
like much constructors destructors, incidentally) is that what you think
is an object is actually implemented as a graph (tree) of subobjects. This
may well be too high level. It is my impression that thsi is because of
prefixing.
-- 
Piercarlo "Peter" Grandi           | ARPA: pcg%cs.aber.ac.uk@nsfnet-relay.ac.uk
Dept of CS, UCW Aberystwyth        | UUCP: ...!mcvax!ukc!aber-cs!pcg
Penglais, Aberystwyth SY23 3BZ, UK | INET: pcg@cs.aber.ac.uk

pcg@aber-cs.UUCP (Piercarlo Grandi) (02/19/90)

In article <12029@goofy.megatest.UUCP> djones@megatest.UUCP (Dave Jones) writes:
    > allowed to use composition with nameless members, or if a member could
    > be identified by any unambiguous path to it, like in PL/1.
    
    Did PL/1 do that? It's been so long since I used that language -- over
    fifteen years I guess -- I've forgotten most everything about it. PL/1
    has been ragged so hard over the years, it seems strange to hear one
    of its features mentioned in a positive way.

I seem to remember that also Cobol allows you to name a member with the
shortest unique path to it. This feature may be seen as dangerous, as it
means that modifying a data structure can make a program ambiguous, but
this is really unavoidable, with inheritance as well.

	I had written in my C++ coding guidelines to prefix all identifiers
	used in a function with their scope operators. Somebody observed
	that this also should extend to members (prefixing them from the
	name of the class from which they are inherited), obviously not just
	to improve readability (where is this member from?), but also
	to guard against hazardous ambiguities caused by extending member
	lists in the future.

As to PL/1, it actually had some *good* ideas, and some ridiculous ones
(exceptions as a datatype!, 22/7 resulting in overflow,...). For example
areas, based pointers, controlled storage. Not that I think they were all
appropriate at the language level; for example, controlled storage is done
in GNU LIB C++ as the obstack class, and areas can be done in C++ 2.0 with
operator new overloading. On the other hand I would really be happy to see
based (relative) pointer support, which is vital to get position independent
data structures (and no, "smart pointers" are not a full solution).
-- 
Piercarlo "Peter" Grandi           | ARPA: pcg%cs.aber.ac.uk@nsfnet-relay.ac.uk
Dept of CS, UCW Aberystwyth        | UUCP: ...!mcvax!ukc!aber-cs!pcg
Penglais, Aberystwyth SY23 3BZ, UK | INET: pcg@cs.aber.ac.uk