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: 1108a1,c52c30d32b866eae X-Google-Attributes: gid1108a1,public X-Google-Thread: 103376,2ea02452876a15e1 X-Google-Attributes: gid103376,public From: donh@syd.csa.com.au (Don Harrison) Subject: Re: Real OO Date: 1996/03/27 Message-ID: X-Deja-AN: 144424859 sender: news@assip.csasyd.oz references: <4iuvmi$113p@watnews1.watson.ibm.com> organization: CSC Australia reply-to: donh@syd.csa.com.au newsgroups: comp.lang.ada,comp.lang.eiffel,comp.object Date: 1996-03-27T00:00:00+00:00 List-Id: Norman H. Cohen wrote: :In article , donh@syd.csa.com.au :(Don Harrison) writes: : :|> In Eiffel, there is only one species of :|> type - the class - which may be viewed as specific or class-wide depending on :|> the context. This makes modelling considerably easier. :|> :|> Ada complicates matters by forcing an atificial distinction of specific versus :|> classwide depending on usage. Since the formal and actual parameters of an :|> operation may be either specific or classwide, the number of possible :|> combinations is 4 compared with 1 in Eiffel. To my mind, this is 4 times more :|> complicated than is really needed. : :This is not an artificial distinction. Let us consider actual parameters :and formal parameters separately. I note that you spell better than I do :-). Whether we call it 'artificial' or not doesn't matter too much, I guess. What is significant, is whether it is necessary to make such a distinction. There are a number of issues: a) Is there a significant difference in efficiency between the two approaches? Quantitatively, what is the relative difference in efficiency of Jump to Subroutine compared with an Indirect Jump to Subroutine? As Joachim pointed out, Eiffel compilers typically optimise where possible. Perhaps optimisation by the developer would be more effective but remember that efficiency is being traded off against the power and flexibility of dynamic binding. b) What is more important for the job at hand - flexibility and reusability or efficiency? As Joachim indicated, Eiffel developers would be more concerned with flexibility and reusability, prefering to maximise dynamic binding rather than squeezing every last clock cycle out of the processor. Admittedly, however, an Ada developer implementing a real-time system with stringent timing constraints may be more interested in efficiency. c) How do the mechanisms compare wrt clarity of expression? IMO, the Eiffel mechanism simplifies reasoning. :In the case of actual parameters, it is a formalization of the :distinction that Bertrand Meyer recognizes between the static type and :the dynamic type of a reference. Think of an Ada actual parameter of 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 extra efficiency arises not :only from saving a cycle or two for an indirect jump, but also because of :the possibility of inlining the method body or performing precise :interprocedural analysis.) I guess the difference in performance here would depend on how effectively Eiffel compilers optimise (by staticly binding and inlining) and how much slower an indirect jump is. :In the case of formal parameters, the use of a classwide type or specific :type reflects the distinction between a single type and the hierarchy of :types descended (in zero or more steps) from that type. (In Eiffel, the :word "class" refers to any one of these types; in Ada, the word "class" :refers to the hierarchy, so "classwide" means "hierarchy-wide".) A :classwide subprogram (i.e., one with classwide formal parameters) is one :whose algorithm is the same for all objects in the hierarchy. A :dispatching subprogram (i.e., one with specific formal parameters) is one :whose algorithm depends on the tag (in Eiffel terms, the dynamic type) of :the actual parameter. The body of a classwide subprogram relies only on :properties common to all types in the hierarchy, i.e., features :introduced at the root of the hierarchy. These may include record :components, other classwide subprograms, and dispatching operations. An Eiffel operation may effectively be made 'classwide' by declaring it 'frozen'. The real purpose of 'frozen' is to prevent the implementation being changed in descendants so that they may rely on fixed universal semantics. Freezing features for efficiency purposes is a hack as pointed out by Bob Duff. Note that you generally would not want to freeze an implementation because flexibility is reduced. (You can both freeze and provide an overridable version by using two copies but that's another matter). :For a one-parameter subprogram, the effect of a classwide subprogram :could be achieved by a dispatching subprogram that is never overridden. :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. The real power of a classwide program arises with :multiple parameters. In Eiffel, dispatching is controlled by the one :object whose method is invoked (the x in x.f()). In an Ada dispatching :subprogram, there can be multiple parameters controlling the dispatching. :Consider a hierarchy for geometric figures, with Shape_Type at the root :and types such as Circle_Type, Triangle_Type, and Rectangle_Type at the :leaves. They may all be regarded as controlling dispatching only because they happen to be of the same type. It is really the common type of the operands that is controlling dispatching rather than the operands per se. The operation being executed belongs to the class, not the instances of the class - the same as Eiffel. I prefer the dot notation of the Eiffel syntax because all the parameters in the parentheses have the same status - they are all effectively classwide - and the controlling operand is clearly identified because it the target of the call. There is no need to put up a signpost to say which is classwide and which is not as is necessary with co-encapsulation. The information is implicit. In the case of 'frozen', that information is explicit but conveys different semantics. : There may be a dispatching operation Corresponding_Parts_Equal :defined (as an abstract function) for Shape_Type and overridden for each :shape. For example: : : function Corresponding_Parts_Equal (C1, C2: Circle_Type) return Boolean is : begin : return C1.Radius = C2.Radius; : end Corresponding_Parts_Equal; : : : function Corresponding_Parts_Equal : (R1, R2: Rectangle_Type) return Boolean is : begin : return : (R1.Base = R2.Base and R1.Height = R2.Height) or : (R1.Base = R2.Height and R1.Height = R2.Base); : end Corresponding_Parts_Equal; : :Each time a new kind of shape is introduced to the hierarchy, a new :overriding version of Corresponding_Parts_Equal, concerned only with the :features of that kind of shape, is written. : :The call Corresponding_Parts_Equal(Shape1, Shape2), where Shape1 and :Shape2 are of type Shape_Type'Class (in Eiffel terms, their static types :are Shape_Type and their dynamic types are unknown), will dispatch to the :first body above if the dynamic types of Shape1 and Shape2 are both :Circle_Type, to the second body above if the dynamic types are both :Rectangle_Type, and so forth. But if Shape1 and Shape2 have different :dynamic tags, there is no sensible way to check that their corresponding :parts are equal, and a run-time error results. That leaves the problem :of how to determine whether two arbitrary Shape_Type'Class values, :possibly with different tags, are congruent, and this is where classwide :subprograms come in. There is no requirement that the parameters of a :classwide subprogram have the same tag. Therefore, we can write: : : function Congruent (Shape1, Shape2: Shape_Type'Class) return Boolean is : begin : return : Shape1'Tag = Shape2'Tag and then : Corresponding_Parts_Equal (Shape1, Shape2); : end Congruent; : :When the shapes have different tags, the function immediately returns :False without invoking Corresponding_Parts_Equal. This might be done in Eiffel by: class CONGRUENCY inherit INTERNAL feature congruent (a, b: SHAPE): BOOLEAN is do Result := (dynamic_type (a) = dynamic_type (b)) and then equal (a, b) end end Yes, short circuit booleans were stolen from Ada. Thanks, Ada :-). Note that equal may be protected with a precondition thus: equal (a, b: ANY): BOOLEAN is require same_type: dynamic_type (a) = dynamic_type (b) deferred end (Not sure of the details, but you see what I mean). :-- :Norman H. Cohen ncohen@watson.ibm.com Don.