From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on polar.synack.me X-Spam-Level: X-Spam-Status: No, score=0.7 required=5.0 tests=BAYES_00,INVALID_DATE, REPLYTO_WITHOUT_TO_CC autolearn=no autolearn_force=no version=3.4.4 X-Google-Language: ENGLISH,ASCII-7-bit X-Google-Thread: 103376,1ce9b9f0acc99abe X-Google-Attributes: gid103376,public X-Google-ArrivalTime: 1994-12-12 07:43:56 PST Path: nntp.gmd.de!newsserver.jvnc.net!howland.reston.ans.net!agate!sunsite.doc.ic.ac.uk!aixssc.uk.ibm.com!watnews.watson.ibm.com!ncohen From: ncohen@watson.ibm.com (Norman H. Cohen) Newsgroups: comp.lang.ada Subject: Re: type extension vs. inheritance Date: 12 Dec 1994 15:43:56 GMT Organization: IBM T.J. Watson Research Center Distribution: world Message-ID: <3chr3s$1abb@watnews1.watson.ibm.com> References: Reply-To: ncohen@watson.ibm.com NNTP-Posting-Host: rios8.watson.ibm.com Keywords: type extension, inheritance Date: 1994-12-12T15:43:56+00:00 List-Id: In article , 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