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=-1.9 required=5.0 tests=BAYES_00 autolearn=ham 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: eachus@spectre.mitre.org (Robert I. Eachus) Subject: Re: Real OO Date: 1996/04/15 Message-ID: X-Deja-AN: 147665395 references: organization: The Mitre Corp., Bedford, MA. newsgroups: comp.lang.ada,comp.lang.eiffel,comp.object Date: 1996-04-15T00:00:00+00:00 List-Id: In article donh@syd.csa.com.au (Don Harrison) writes: > Yes, it may be that an Ada programmer might choose static binding because > they know the entity happens to be of a specific type. This is fine if it > is known that the type is absolutely set in concrete. > However, we need to consider why it is known. Normally, it will be > known because the (transitive) source of the value it last > received was itself a statically bound call. That is, one designed > to handle a specific abstraction, rather than a family of > abstractions; it is not designed for reuse. If the routine > servicing the call is designed for reuse, then it returns an > indeterminate dynamic type which demands dynamic binding when we > use the returned entity as an actual parameter to the operation we > are designing. You worry about a real, but not very major problem. The Ada mechanism is simple, elegant, and results in very reusable code. But it can give you a blinding headache if you try to understand the simple rules that apply to the abstraction and the simple rules that apply to the implementation at the same time. In the abstract view, the programmer just writes Foo(Bar) not worrying most of the time whether Bar is an object marked as a member of a classwide type or a specific type. He knows if he writes: Foo(Bar_Type'CLASS(Bar)) that he will force a dispatching call, but usually he has no reason to do so. Now the compiler comes along. If the controlling operand is statically typed, a non-dispatching call is used. If it is dynamic, a dispatching call occurs. Again simple. Now for the blinding headache... type Foo is tagged ....; function Bar(F: in Foo) return Boolean; procedure Barf(F: in out Foo); procedure Barf(F: in out Foo) is begin if Bar(F) then... end Barf; ... type Foobar is new Foo with ...; function Bar(F: in Foobar) return Boolean; ... X: Foobar; Y: Foo'Class := X; ... Barf(Y); So far so good. But in the (dispatching) call to Barf of Y, the call to Bar is directed to Bar for Foo not Bar for Foobar. Is this a problem? Not really. First of all, equality is treated specially but other redispatching cases seldom occur in real Ada code. Yes, I know that they are common in other OO languages, but in Ada generics are more often used where redispatching is needed in other OO languages. This is really an efficiency issue. Generics can trade the space of mulitple copies of code for the efficiency of eliminating redispatching. Second, in Ada, from experience, the non-redispacthing operation is usually the right one (except for equality, but that is taken care of). But what if we expect to need redispatching above? The body of Barf becomes: procedure Barf(F: in out Foo) is begin if Bar(Foo'Class(F)) then... end Barf; or often better: procedure Barf(F: in out Foo) is Temp: Foo'Class := F begin if Bar(Temp) then... end Barf; More often you run into cases where the explicit redispatch is the right approach, but it usually occurs where no original dispatch is required, so there is no "extra" overhead: procedure Put(Obj: in Object_Type'Class) is begin Put(Default_File, Obj); end Put; pragma Inline(Put); procedure Put(F: in File, Obj: in Object_Type) is... This idiom often occurs, and the pleasant effect for the programmer is to minimize the number of subprograms that may need to be overridden when defining a new subclass. The first Put is classwide, and just defines the behavior of the single operand Put in terms of the two operand version. Or much more to the point: function "+" (L,R: Foo'Class) return Foo_Set is begin return Add(L,R); end "+"; function Add(L: Foo; R: Foo'Class) return Foo_Set is begin return Add(Create_Class(L),R); end Add; ... function Add(FS: Foo_Set; R: Foo) return Foo_Set is... ...and so on. This is the way to do multiple dispatching in Ada. The careful combination of classwide operations and class specific operations allows you, for example, to create a workable heterogeneous set type. And, once you have the base class declared correctly, extending it (subclassing) is fairly simple, since most of the "nuisance" operations are classwide and need no overriding. -- Robert I. Eachus with Standard_Disclaimer; use Standard_Disclaimer; function Message (Text: in Clever_Ideas) return Better_Ideas is...