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.3 required=5.0 tests=BAYES_00, REPLYTO_WITHOUT_TO_CC autolearn=no autolearn_force=no version=3.4.4 X-Google-Language: ENGLISH,ASCII-7-bit X-Google-Thread: fac41,c52c30d32b866eae X-Google-Attributes: gidfac41,public X-Google-Thread: 103376,2ea02452876a15e1 X-Google-Attributes: gid103376,public X-Google-Thread: 1108a1,c52c30d32b866eae X-Google-Attributes: gid1108a1,public From: ncohen@watson.ibm.com (Norman H. Cohen) Subject: Re: Real OO Date: 1996/03/26 Message-ID: <4j9p3a$uo5@watnews1.watson.ibm.com> X-Deja-AN: 144389262 distribution: world references: <4id031$cf9@dayuc.dayton.saic.com> <4iuvmi$113p@watnews1.watson.ibm.com> <65O34ib-3RB@herold.franken.de> organization: IBM T.J. Watson Research Center reply-to: ncohen@watson.ibm.com newsgroups: comp.lang.eiffel,comp.lang.ada,comp.object Date: 1996-03-26T00:00:00+00:00 List-Id: In article <65O34ib-3RB@herold.franken.de>, jhd@herold.franken.de (Joachim Durchholz) writes: |> ncohen@watson.ibm.com wrote 22.03.96 on Re: Real OO: |> |> > ...a specific type as an Eiffel reference whose dynamic type is known to be |> > identical to its static type. A method call can be far more efficient if |> > this knowledge is conveyed to the compiler, and it is common in practice |> > for this knowledge to be available. |> |> The Eiffel paradigma is that it should *not* be possible to fix a formal |> parameter's dynamic type to its static types. Such a thing is considered |> an unnecessary limitation introduced on the routine, for efficiency |> reasons that aren't even valid because a compiler can determine wether |> such an optimization is possible. The Ada rule under discussion--that a subprogram call is not dispatching unless the relevant ACTUAL parameters belong to a classwide type--does not limit the called routine at all. It provides the opportunity for the CALLER, calling from a context in which the dynamic type is known, to declare this knowledge. The Eiffel optimization you describe is a difficult one because in practice it is difficult for an optimizing compiler to resolve pointer-induced aliasing and determine the dynamic type at compile time. The analysis required is expensive, requiring interprocedural analysis to be effective, and the payoff is rare, so it is not clear that it is worthwhile for an optimizing compiler to perform this analysis routinely. (See, for example, P. R. Carini, H. Srinivasan, and M. Hind, "Flow-Sensitive Type Analysis for C++", IBM Research Report RC 20267, http://www.watson.ibm.com:8080/PS/7899.ps.gz.) In Ada, the programmer conveys, not through any special hint-giving pragma, but just through the natural course of declaring an object to belong to a specific type, that a call should not be dispatching. |> > For a one-parameter subprogram, the effect of a classwide subprogram |> > could be achieved by a dispatching subprogram that is never overridden. |> |> Agreed. |> |> > Making it classwide simply documents the fact that the algorithm does not |> > depend on the tag of the parameter, and causes the compiler to catch any |> > attempt to override. |> |> This assumes that the writer of a module knows that his routines never |> need to be overridden. No, it assumes that the writer of a module knows that a particular routine in the module accomplishes its purpose without directly exploiting knowledge of a given object's dynamic type, or tag. (The routine may invoke other routines that dispatch based on the dynamic type, but it does not itself differentiate among different dyanamic types.) |> While this may be true when writing a program with |> a fixed and well-understood task, this isn't true when writing libraries, |> or when programs are modified in the future and in ways that weren't |> anticipated. No routine, even in an object-oriented program, is immune from future modification resulting from new requirements that change the routine's preconditions and postconditions. However, a classwide routine will generally not have to be modified just because a new type was added to the class hierarchy. A classwide routine is, by definition, one whose correctness does not depend on the dynamic types of the objects it is dealing with. |> Considering the fact that fixed and well-understood tasks don't need OO |> (structured programming and functional decomposition should suffice for |> this type of tasks), this argument gains some weight. That's a rather limited view of the utility of OO techniques! Even fixed and well understood tasks benefit from data abstraction and encapsulation, from the use of inheritance to avoid redundant effort, and from the use of polymorphism to facilitate generalized algorithms. Like any other program, an OO program reflects a model of the problem domain and the ways in which it is likely to evolve. If the problem domain evolves in the ways that the original designers anticipated, the program can be updated to reflect this evolution simply by the addition of new subclasses. But if the problem domain evolves in ways inconsistent with the original designer's model, no OO technique is going to save your code from major surgery. Concerning dispatching based on multiple parameters: |> The type of dispatching noted here can easily be achieved with anchored |> types in Eiffel. In an Ada-style notation, this would look as follows: |> |> function Corresponding_Parts_Equal |> (Self: Shape_Type; Other: like Self) |> return Boolean |> |> The "like Self" part requires Other to have the same dynamic type as Self, |> or a type that is a descendant of Self's type. (Are you sure it's the DYNAMIC type? Section 11.4.3 of Meyer's _Object_Oriented_Software_Construction_ seems to suggest that it's the declared type.) It's the "or a type that is a descendant of" that differentiates the Eiffel approach from the Ada approach. |> I'm not sure wether this is enough to handle all cases that arise in |> practice, but it goes a long way. Taking your word that the behavior is based on the dynamic type, this approach introduces a disturbing assymmetry. Peeking ahead to the example you introduce below, if the dynamic types of C, FC, and HC are Circle_Type, Filled_Circle_Type, and Hatched_Circle_Type, respectively, then the following calls (reverting to Eiffel notation) give a sensible answer: C.Corresponding_Parts_Equal(C) C.Corresponding_Parts_Equal(FC) C.Corresponding_Parts_Equal(HC) FC.Corresponding_Parts_Equal(FC) HC.Corresponding_Parts_Equal(HC) The following calls violate the requirement that the dynamic type of Other be descended (in zero or more steps) from the dynamic type of Self, and presumably result in a run-time error: FC.Corresponding_Parts_Equal(C) HC.Corresponding_Parts_Equal(C) FC.Corresponding_Parts_Equal(HC) HC.Corresponding_Parts_Equal(FC) The problem with this assymetry is not merely aesthetic: It greatly complicates the programming of a routine designed to exploit the "or a type that is a descendant of" property of Corresponding_Parts_Equal, because that routine must determine which object's type, if any, is at least as general as the other object's type. |> BTW I see serious problems with the Congruent function given. |> |> Consider descandant types "Filled_Circle", "Hatched_Circle" etc. - with |> the understanding that these don't have any new geometric properties that |> should influence Corresponding_Parts_Equal or Congruent. I was using the term "shape" to mean just that, an abstract entity in plane geometry, not the representation of a shape as a graphic. I would think of Filled_Circle and Hatched_Circle as instances of a class Graphic_Type, COMPOSED FROM a Shape_Type component and a Fill_Type component: type Graphic_Type is tagged record Shape : Shape_Type; Fill : Fill_Type; end record; If one is interested in congruence of Graphic_Type instances, that's a very different notion from congruence of abstract geometric figures, but it's easily implemented as a classwide operation: function Congruent (Graphic_1, Graphic_2: Graphic_Type'Class) return Boolean is begin return Congruent (Graphic_1.Shape, Graphic_2.Shape); end Congruent; If Graphic_Type is later extended, the function above can sensibly be applied to any object in any type in the hierarchy, because all such objects have Shape components. Nonetheless, let's persue the consequences of trying to extend Circle_Type (a descendant of Shape_Type) to include such subclasses as Filled_Circle_Type and Hatched_Circle_Type. |> First of all, it would be necessary to define a Corresponding_Parts_Equal |> function for each pair of Circle_Type descendants - this can become quite |> a formidable task if the number of direct and indirect descandants grows. No, this would not be necessary, as explained below. |> In addition, the Congruent code will break - Shape1'Tag and Shape2'Tag |> will not be equal, making Congruent return false even if a Filled_Circle |> and a Hatched_Circle actually have equal corresponding parts. True. If I had intended to allow shapes to be specialized to include nongeometric features, I would add a new abstract method to Shape_Type: function Geometry (Special_Shape: Shape_Type) return Shape_Type'Class is abstract; I would override it for Circle_Type as follows: function Geometry (Special_Circle: Circle_Type) return Shape_Type'Class is Result: Circle_Type := Special_Circle; begin return Result; end Geometry; This function always returns an object whose dynamic type, or tag, is that of Circle_Type. Types extending Circle_Type would inherit this function without overriding it. Thus if FC is a Shape_Type'Class object with the dynamic type (tag) of Filled_Circle_Type, Geometry(FC) would dispatch to the function body above and would return the corresponding (truncated) Circle_Type object. Similar definitions of Geometry would be given for all types in the hierarchy containing only geometric information (e.g., Rectangle_Type, but not Filled_Rectangle_Type). The Congruent function would be rewritten as follows: function Congruent (Shape_1, Shape_2: Shape_Type'Class) return Boolean is Shape_1_Geometry : Shape_Type'Class := Geometry(Shape_1); Shape_2_Geometry : Shape_Type'Class := Geometry(Shape_2); begin return Shape_1_Geometry'Tag = Shape_2_Geometry'Tag and then Corresponding_Parts_Equal(Shape_1_Geometry, Shape_2_Geometry); end Congruent; This function will return True when invoked with a filled circle and a hatched circle having the same radius. -- Norman H. Cohen ncohen@watson.ibm.com