comp.lang.ada
 help / color / mirror / Atom feed
* type extension vs. inheritance
@ 1994-12-06 22:09 Ray Toal
  1994-12-09 10:42 ` Robb Nebbe
  1994-12-09 17:04 ` John Volan
  0 siblings, 2 replies; 4+ messages in thread
From: Ray Toal @ 1994-12-06 22:09 UTC (permalink / raw)


Hi,

I have always been under the impression that using C++-style
"inheritance" (derived classes) should ONLY ONLY be used for
situtations in which an IS-A relationship existed between the
derived class and the base class.

Ada 9X has "type extension" and in the Rationale I saw an example
where a 3-D box was derived from a 2-D rectangle by adding a
"depth" field to the width and the height.

1.  Is this something one would really do in practice, or was
    it just an example to illustrate type extension?  I would
    be very nervous using derivation for anything other than
    inheritance, and certainly a parallelpiped IS-NOT-A rectangle.

2.  But even if the answer to (1) is "just an example" a better
    question is, in industry, how many applications REALLY benefit
    from these IS-A hierarchies anyway??  Rosen's paper in the
    1992 CACM Ada special issue on why Ada 83 does not have C++
    style inheritance made a good case for considering classification
    secondary to "composition".

3.  And how would one, in Ada 9X, implement in a nice way the
    derivation of a square from a rectangle?  Am I on the right
    track here?

    package Shapes is

      type Figure is abstract tagged record;
      procedure Move (F: in out Figure; X, Y: Float);
      function Area (F: Figure) return Float is abstract;
      type Rectangle is new Figure with private;
      function Make_Rectange (W, H: Float) return Rectangle;
      function Area (R: Rectangle) return Float;
      type Square is new Rectangle with private;
      function Make_Square (Side_Length: Float) return Square;
      -- area for square inherited from rectangle

      ...

Thanks

Ray Toal




^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: type extension vs. inheritance
  1994-12-06 22:09 type extension vs. inheritance Ray Toal
@ 1994-12-09 10:42 ` Robb Nebbe
  1994-12-09 17:04 ` John Volan
  1 sibling, 0 replies; 4+ messages in thread
From: Robb Nebbe @ 1994-12-09 10:42 UTC (permalink / raw)


In article <RTOAL.6.00162786@lmumail.lmu.edu>,
RTOAL@lmumail.lmu.edu (Ray Toal) writes:

|> 
|> Ada 9X has "type extension" and in the Rationale I saw an example
|> where a 3-D box was derived from a 2-D rectangle by adding a
|> "depth" field to the width and the height.
|> 
|> 1.  Is this something one would really do in practice, or was
|>     it just an example to illustrate type extension?

In my opinion it is more an example of what you can do than what you
should do (but I didn't write the Rationale so I don't know what they
think).

|> 2.  But even if the answer to (1) is "just an example" a better
|>     question is, in industry, how many applications REALLY benefit
|>     from these IS-A hierarchies anyway??

If you need to implement a hierarchies of abstractions then inheritance
is the way to do it. It is cleaner, easier to modify and maintain. There
are two main uses for these hierarchies:

1. They better document the structure of the software thus facilitating
its maintenance and extension but dispatching (polymorphism) is not
used.

2. You need to refer to a class of abstractions. How necessary this is
depends a lot on the domain you are working in but in most cases I find
that composition is a more common mechanism than classification.

|> 3.  And how would one, in Ada 9X, implement in a nice way the
|>     derivation of a square from a rectangle?

There are two ways to model this, semantically what you want is something
close to (* see note):

   type Rectangle is private;
   subtype Square is Rectangle;

but classification is probably best used like:

   type Four_Sided is abstract tagged private;

   type Quadrilateral is new Four_Sided with private;
   type Parallelogram is new Four_Sided with private;
   type Rectangle is new Four_Sided with private;
   type Square is new Four_Sided with private;

and then declare conversion functions between different types to represent
overlaping domains, which is not necessarily the same as a subtype.

Robb Nebbe

*note:
The problem is that you can't associate the necessary predicate with the
subtype Square like you can with numeric types so you would probably
declare a function Is_Square and distribute the complexity of testing
a rectangle to see if it is square acrossed the clients. It might be
worth looking into extending the pragma Assert in GNAT to do this but
this is pure speculation.



^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: type extension vs. inheritance
  1994-12-06 22:09 type extension vs. inheritance Ray Toal
  1994-12-09 10:42 ` Robb Nebbe
@ 1994-12-09 17:04 ` John Volan
  1994-12-12 15:43   ` Norman H. Cohen
  1 sibling, 1 reply; 4+ messages in thread
From: John Volan @ 1994-12-09 17:04 UTC (permalink / raw)


RTOAL@lmumail.lmu.edu (Ray Toal) writes:

>Hi,
>
>I have always been under the impression that using C++-style
                                                    ^^^^^^^^^
(Ahem. Minor quibble: Inheritance was not invented with C++,
nor does C++ have a monopoly on the concept.)

>"inheritance" (derived classes) should ONLY ONLY be used for
>situtations in which an IS-A relationship existed between the
>derived class and the base class.
>
>Ada 9X has "type extension" and in the Rationale I saw an example
>where a 3-D box was derived from a 2-D rectangle by adding a
>"depth" field to the width and the height.
>
>1.  Is this something one would really do in practice, or was
>    it just an example to illustrate type extension?  I would
>    be very nervous using derivation for anything other than
>    inheritance, and certainly a parallelpiped IS-NOT-A rectangle.
>

Ray, let me just chime in here to say that I wholeheartedly agree:
Inheritance is a powerful mechanism for code reuse, but, IMHO, the
most "proper" use of it is to represent generalization/specialization
("IS-A") relationships, particularly those arising directly from the
problem domain (rather than as artefacts of the solution domain).

A true generalization/specializiation relationship satisfies the
Liskov Type-Substitutability Principle.  This principle requires that
any type that is visibly derived from some supertype must fully
support the semantics the supertype.  Wherever an object of the
supertype is expected, we should be able to substitute any object
of any type derived from that supertype, and have it behave for
all intents and purposes as if it were an object of the supertype.

However, many object-oriented practitioners often exploit inheritance
purely as a mechanism for reusing code without necessarily satisfying
Liskov substitutability.  IMHO, this is an "improper" use of
inheritance (and perhaps even an outright abuse).  Sometimes it's the
fault of the language: Programmers might be forced into exploiting
inheritance in order to simulate the effect of other constructs (such
as Ada's "with" and "use" clauses) that might be missing from the
language.  But sometimes it's just a symptom of lazy thinking on the
part of the programmer.  

At best, this kind of code reuse should be strictly reserved for
"implementation inheritance": In other words, if you're going to reuse
the structure and behavior of one type in order to *implement* another
type, but the new type does not really support the outwardly-visible
semantics of the first, then you should hide the fact that you're
using type derivation, by squirelling it away as an implementation
detail in a private part.

The example you cite of a 3-D box derived from a 2-D rectangle is a
glaring example of inheritance without any apparent Liskov
substitutability.  The fact that it appears in the Ada9X Rationale is
very regrettable.  The Rationale is a highly-visible document, and as
such it's vital (IMHO) that it contain programming examples of the
highest possible quality.  Two reasons: (1) We want to attract
programmers from other OO languages to try out our new-and-improved
Ada, so it would be ironic if they were turned off by poor examples
that gave them a wrong impression of the language (or its adherents
;-).  (2) The Rationale, being so highly visible, has a tremendous
potential to influence what (we hope :-) will be a whole new
generation of Ada programmers.  We'd like to get them off on the right
foot.


>2.  But even if the answer to (1) is "just an example" a better
>    question is, in industry, how many applications REALLY benefit
>    from these IS-A hierarchies anyway??  Rosen's paper in the
>    1992 CACM Ada special issue on why Ada 83 does not have C++
>    style inheritance made a good case for considering classification
>    secondary to "composition".
>
>3.  And how would one, in Ada 9X, implement in a nice way the
>    derivation of a square from a rectangle?  Am I on the right
>    track here?
>
>    package Shapes is
>
>      type Figure is abstract tagged record;
>      procedure Move (F: in out Figure; X, Y: Float);
>      function Area (F: Figure) return Float is abstract;
>      type Rectangle is new Figure with private;
>      function Make_Rectange (W, H: Float) return Rectangle;
>      function Area (R: Rectangle) return Float;
>      type Square is new Rectangle with private;
>      function Make_Square (Side_Length: Float) return Square;
>      -- area for square inherited from rectangle
>
>      ...

This particular example crops up every now and then.  I think the
going answer (in any language) is that you really don't want to derive
"Square" from "Rectangle", because "Rectangle" (as you've defined it)
has semantics that are inappropriate for "Square".  (For instance, you
can make a Rectangle with any arbitrary height and width, but you
shouldn't be able to make a Square this way.)  Instead, both "Square"
and "Rectangle" should be derived from some common abstract type,
called, let's say, "Any_Rectangle":

	type Any_Rectangle is abstract new Figure with private;
	-- No Make function for this; it's abstract.
	function Area (R: Any_Rectangle) return Float;
          -- Overrides abstract Area function inherited from Figure.
          -- Computes area as Width(R) * Height(R), which it will
	  -- invoke via dispatching calls.
	function Width (R: Any_Rectangle) return Float is abstract;
	function Height (R: Any_Rectangle) return Float is abstract;

	type Rectangle is new Any_Rectangle with private;
	function Make_Rectangle (Width, Height: Float) return Rectangle;
	function Width (R: Rectangle) return Float;
	function Height (R: Rectangle) return Float;
	-- Area for Rectangle inherited from Any_Rectangle

	type Square is new Any_Rectangle with private;
	function Make_Square (Side_Length: Float) return Square;
	function Side_Length (S: Square) return Float;
	function Width (S: Square) return Float;
	function Height (S: Square) return Float;
	-- Area for Square inherited from Any_Rectangle

	...

        -- In the body, you could just implement Width and Height for
        -- Square as renamings of Side_Length:
	function Width (S: Square) return Float renames Side_Length;
	function Height (S: Square) return Float renames Side_Length;

	function Area (R: Any_Rectangle) return Float is
	begin
	  return Width(Any_Rectangle'Class(R)) * Height(Any_Rectangle'Class(R));
	  -- Casting to the class-wide type causes the function calls to
	  -- dynamically dispatch on the 'Tag of R.
	end Area;

Alternatively, you could just wait until types Rectangle and Square to
provide actual Area functions:

	type Any_Rectangle is abstract new Figure with private;
	-- Inherits abstract Area function from Figure,
        -- but that's okay, Any_Rectangle is abstract too.
	function Width (R: Any_Rectangle) return Float is abstract;
	function Height (R: Any_Rectangle) return Float is abstract;

	type Rectangle is new Any_Rectangle with private;
	function Make_Rectangle (Width, Height: Float) return Rectangle;
	function Width (R: Rectangle) return Float;
	function Height (R: Rectangle) return Float;
	function Area (R: Rectangle) return Float; -- Overrides Area from Figure

	type Square is new Any_Rectangle with private;
	function Make_Square (Side_Length: Float) return Square;
	function Side_Length (S: Square) return Float;
	function Width (S: Square) return Float;
	function Height (S: Square) return Float;
	function Area (S: Square) return Float;  -- Overrides Area from Figure

	...

	function Area (R: Rectangle) return Float is
	begin
	  return Width(R) * Height(R); -- Non-dispatching calls
	end Area;

	function Area (S: Square) return Float is
	begin
	  return Side_Length(S) ** 2;
	end Area;


>Thanks

You're most welcome. :-)

>
>Ray Toal

John Volan

--------------------------------------------------------------------------------
--  Me : Person := (Name                => "John Volan",
--                  Company             => "Raytheon Missile Systems Division",
--                  E_Mail_Address      => "jgv@swl.msd.ray.com",
--                  Affiliation         => "Enthusiastic member of Team Ada!",
--                  Humorous_Disclaimer => "These opinions are undefined " &
--                                         "by my employer and therefore " &
--                                         "any use of them would be "     &
--                                         "totally erroneous.");
--------------------------------------------------------------------------------



^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: type extension vs. inheritance
  1994-12-09 17:04 ` John Volan
@ 1994-12-12 15:43   ` Norman H. Cohen
  0 siblings, 0 replies; 4+ messages in thread
From: Norman H. Cohen @ 1994-12-12 15:43 UTC (permalink / raw)


In article <D0JzFD.ABu@swlvx2.msd.ray.com>, jgv@swl.msd.ray.com (John
Volan) writes: 

|> RTOAL@lmumail.lmu.edu (Ray Toal) writes: 
|>
|> >Hi,
|> >
|> >I have always been under the impression that using C++-style
...
|> >"inheritance" (derived classes) should ONLY ONLY be used for
|> >situtations in which an IS-A relationship existed between the
|> >derived class and the base class.
|> >
|> >Ada 9X has "type extension" and in the Rationale I saw an example
|> >where a 3-D box was derived from a 2-D rectangle by adding a
|> >"depth" field to the width and the height.
...
|> A true generalization/specializiation relationship satisfies the
|> Liskov Type-Substitutability Principle.  This principle requires that
|> any type that is visibly derived from some supertype must fully
|> support the semantics the supertype.  Wherever an object of the
|> supertype is expected, we should be able to substitute any object
|> of any type derived from that supertype, and have it behave for
|> all intents and purposes as if it were an object of the supertype.
...
|> The example you cite of a 3-D box derived from a 2-D rectangle is a
|> glaring example of inheritance without any apparent Liskov
|> substitutability.  The fact that it appears in the Ada9X Rationale is
|> very regrettable.

One can make an (admittedly strained) case that the 3-D box IS a
specialization of a 2-D rectangle.  It all depends on what operations are
defined for the type.  If the only operations for the 2-D rectangle are
to inquire about width and length, these are certainly meaningful for the
3-D box as well (along with an additional operation to inquire about
height).  If the type for 2-D rectangles also has an operation to draw
the given rectangle at a given place on a 2-D screen, that's a problem,
because it is not a meaningful for a 3-D box.

(I don't know what operations are defined for these types because I
haven't been able to find the example in the Rationale.  Ray Toal, are
you sure that you saw it there?  In which section?)

|> >3.  And how would one, in Ada 9X, implement in a nice way the
|> >    derivation of a square from a rectangle?
...
|> This particular example crops up every now and then.

Indeed.  Common variants are deriving Real_Number from Complex_Number and
deriving Whole_Number from Fraction.  This is specialization by
constraint rather than by extension (constraining the Height and Width
components of a Square record to be equal, constraining the
Imaginary_Part component of a Complex_Number record to be zero, or
constraining the Denominator component of a Fraction record to be one).

The specialization exhibits Liskov substitutability:  All the operations
of the unconstrained superclass are meaningful for the constrained
subclass, and there may be other operations meaningful for the subclass
but not for superclass (such as a "<" operator for Real_Number that is
not meaningful for Complex_Number or a Successor operation for
Whole_Number that is not meaningful for Fraction).  Often we will want to
override the inherited operations with more efficient implementations
exploiting the constraint, however (for example, overriding the
calculation of (a+0i)*(b+0i) as (a*b-0*0)+(a*0+0*b)i with the calculation
(a*b)+0i).

If you're willing to make all the objects in your type immutable (an
acceptable restriction if you're programming in a functional style), you
can sometimes achieve specialization by constraint in Ada using tagged
types with corresponding discriminants.  ALL the constrainable data must
reside in the discriminants themselves, which is what makes the objects
immutable.  Here are examples: 

   type Rectangle (Length, Width: Float) is tagged null record;
   type Square (Side: Float) is
      new Rectangle (Length => Side, Width => Side) with null record;

   type Complex_Number (Real_Part, Imaginary_Part: Float) is
      tagged null record;
   type Real_Number (Value: Float) is
      new Complex_Number (Real_Part => Value, Imaginary_Part => 0.0)
         with null record;

   type Fraction (Numerator: Integer; Denominator: Positive) is
      tagged null record;
   type Whole_Number (Value: Integer) is
      new Fraction (Numerator => Value, Denominator => 1)
         with null record;

The applicability of this approach is limited by the restriction in RM9X
3.8(12) that the derived-type discriminants appearing in the constraint
on the parent type in the derived-type declaration cannot be part of a
larger expression.  Thus we cannot write: 

   Golden_Ratio: constant := (Sqrt(5.0)+1.0)/2.0;

   type Golden_Rectangle (Height: Float) is
      new Rectangle (Length => Height, Width => Golden_Ratio * Height);
                                       -- ILLEGAL USE OF DISCRIMINANT!

|>                                                       I think the
|> going answer (in any language) is that you really don't want to derive
|> "Square" from "Rectangle", because "Rectangle" (as you've defined it)
|> has semantics that are inappropriate for "Square".  (For instance, you
|> can make a Rectangle with any arbitrary height and width, but you
|> shouldn't be able to make a Square this way.)

I disagree.  While Ada does not provide full support for specialization
by constraint, it is easy to imagine an OO language that would.
Concerning your observation that the parent could have a constructor
function that is not appropriate for the derived type, this is an issue
that was discussed in c.l.a a few weeks ago.  (In this case, the
constructor function would be inappropriate because its specification
allows the construction of objects that do not obey the constraint on the
derived type.)  Among the solutions suggested then were: 

   - Give the constructor function for a value of type T a result type of
     T'Class, so that (an abstract version of) the function is not
     inherited by the derived type.  The function will always return a
     result with the tag of T, and a separate function with different
     parameters can be defined for a type derived from T, returning a
     result with the tag of the derived type.

   - Place constructor functions only at the leaves of the inheritance
     hierarchy.  Given a parent type T with its own constructor Make_T
     and a type D derived from T, this entails factoring all of the
     definition of T except for the Make_T function into a new abstract
     parent A, then deriving both T and D from A.  T inherits all its
     operations except for Make_T from A, and introduces Make_T.

|>                                                Instead, both "Square"
|> and "Rectangle" should be derived from some common abstract type,
|> called, let's say, "Any_Rectangle": 

This is the second solution, and yes, it's a perfectly good approach.

--
Norman H. Cohen    ncohen@watson.ibm.com



^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~1994-12-12 15:43 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
1994-12-06 22:09 type extension vs. inheritance Ray Toal
1994-12-09 10:42 ` Robb Nebbe
1994-12-09 17:04 ` John Volan
1994-12-12 15:43   ` Norman H. Cohen

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox