* Ada 90 inheritance request? @ 1994-11-23 21:33 S M Ryan 1994-12-02 16:46 ` Tucker Taft 0 siblings, 1 reply; 13+ messages in thread From: S M Ryan @ 1994-11-23 21:33 UTC (permalink / raw) A few months back, there were some postings comparing Ada90 proposed single inheritance and how it compared to multiple inheritance such as C. Does any kind soul still have copies? -- _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ __________________________________ | | | | | | | | | | | | | | | | | | | | | smryan@netcom.com PO Box 1563 | | | | | | | | | | | | | | | | | | | | | Cupertino, California |_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_(xxx)xxx-xxxx_______________95015 ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Ada 90 inheritance request? 1994-11-23 21:33 Ada 90 inheritance request? S M Ryan @ 1994-12-02 16:46 ` Tucker Taft 1994-12-09 17:26 ` Cyrille Comar 0 siblings, 1 reply; 13+ messages in thread From: Tucker Taft @ 1994-12-02 16:46 UTC (permalink / raw) Here is the note on multiple inheritance in Ada 9X. -Tucker Taft P.S. This note is absolutely not intended to start Y.A.F.W. (yet another flame war ;-). -TT ------------------------- MULTIPLE INHERITANCE IN ADA 9X S. Tucker Taft stt@inmet.com Ada 9X Mapping/Revision Team Copyright (C) 1994 Intermetrics, Inc. 733 Concord Avenue Cambridge, MA 02138 May be copied if accompanied by this notice. This note discusses the creation of multiple inheritance type hierarchies using the Ada 9X object-oriented programming features. It is in part directed at programmers familiar with other object-oriented programming languages that build in syntax for a particular multiple-inheritance mechanism, rather than simply providing the building blocks needed to support it. In this discussion, we will in general use Ada 9X terminology, where every object has a single "type," and multiple similar types (typically in some kind of hierarchy or oligarchy) form a "class" of types. If we want to use the term "class" as it is used in C++ or Eiffel, we will always say "C++ class" or "Eiffel class." In some languages, such as Eiffel, multiple inheritance serves many purposes. For example, there is no equivalent in Eiffel to the "include" statement of C/C++ or the "with/use" clauses of Ada. Therefore, to gain direct visibility to the declarations of some other module, one must inherit from that module (Eiffel class). In C/C++, one can simply "include" a file containing a set of type and object definitions. In Ada, one first identifies the external modules of interest via "with" clauses, and then chooses selectively whether to make only the name of the module (package) visible, or its contents (via a "use" clause). In both Eiffel and C++, one can choose to inherit from some other type, without making that visible to clients of the new type. Effectively, the linguistic multiple inheritance mechanism is being used to express not an "is a refinement of" relationship, but rather an "is implemented using" relationship. Finally, there are the situations where a single type visibly inherits from two or more other types. In these cases, this is rarely a symmetric situation. Rather, one of the ancestor types is the "primary" ancestor, while the others are typically "mix-ins" designed to augment behavior of the primary ancestor. Ada 9X supports multiple-inheritance module inclusion (via multiple "with"/"use" clauses), multiple-inheritance "is-implemented-using" via private extensions and record composition, and multiple-inheritance mix-ins via the use of generics, formal packages, and access discriminants. The Ada 9X mechanisms are designed to eliminate "distributed" overhead, so that there is no added expense for the general user of the language because of the presence of the mechanisms supporting multiple inheritance. Furthermore, the Ada 9X building blocks that support multiple inheritance are general purpose, and support many other paradigms beyond conventional multiple inheritance. There are basically three distinct situations associated with multi- inheritance mixins: 1. The case where the mix-in provides components and operations, and any overriding of these operations needs only to look at the components of the mix-in itself. 2. The case where the mix-in provides components and operations, and some of the overriding of these operations needs access to the whole object, rather than just the components of the mix-in. 3. Like (2), and in addition, any object with the mix-in must be able to be linked onto a list (or into some similar heterogeneous data structure) of other objects with the same mix-in. Case (1) is supported directly in Ada 9X by a record or private extension, with the type being mixed in (in a possibly extended form) as a component of the record extension. Case (2) is handled with a generic, that takes any type in a given class (formal derived type), adds components (via extension) and operations, and then reexports the extended type. The new operations have access to the whole object, not just to the components being added. Case (3) is handled with an access discriminant, that provides access to the enclosing object for the operations of the mix-in, while still allowing links through the mix-in. Generics can also be used to automate the approach. Here are a few examples: - Case (1) -- One has an abstract type "Set_of_Strings" and one wants to implement it by reusing an existing (concrete) type "Hash_Table": Here is the abstract type: type Set_Of_Strings is abstract tagged limited private; type Element_Index is new Natural; -- Index within set. No_Element : constant Element_Index := 0; Invalid_Index : exception; procedure Enter( -- Enter an element into the set, return the index Set : in out Set_Of_Strings; S : String; Index : out Element_Index) is abstract; procedure Remove( -- Remove an element from the set; ignore if not there Set : in out Set_Of_Strings; S : String) is abstract; procedure Combine( -- Combine Additional_Set into Union_Set Union_Set : in out Set_Of_Strings; Additional_Set : Set_Of_Strings) is abstract; procedure Intersect( -- Remove all elements of Removal_Set from Intersection_Set Intersection_Set : in out Set_Of_Strings; Removal_Set : Set_Of_Strings) is abstract; function Size(Set : Set_Of_Strings) return Element_Index is abstract; -- Return a count of the number of elements in the set function Index( -- Return the index of a given element; -- return No_Element if not there. Set : Set_Of_Strings; S : String) return Element_Index is abstract; function Element(Index : Element_Index) return String is abstract; -- Return element at given index position -- raise Invalid_Index if no element there. private type Set_Of_Strings is abstract tagged limited ... Here is an implementation of this abstract type that inherits its interface from Set_Of_Strings, and its implementation from Hash_Table: type Hashed_Set(Table_Size : Positive) is new Set_Of_Strings with private; -- Now we give the specs of the operations being implemented procedure Enter( -- Enter an element into the set, return the index Set : in out Hashed_Set; S : String; Index : out Element_Index); procedure Remove( -- Remove an element from the set; ignore if not there Set : in out Hashed_Set; S : String); . . . etc. private type Hashed_Set(Table_Size : Positive) is new Set_Of_Strings with record Table : Hash_Table(1..Table_Size); end record; In the body of this package, we would provide the bodies for each of the operations, in terms of the operations available from Hash_Table. Chances are they don't match exactly, so a little bit of "glue" code will be necessary in any case. - Case (2) -- One has a type Basic_Window that responds to various events and calls. One wants to embellish the Basic_Window in various ways with various mix-ins. type Basic_Window is tagged limited private; procedure Display(W : Basic_Window); procedure Mouse_Click(W : in out Basic_Window; Where : Mouse_Coords); . . . Now one can define any number of mix-in generics, like the following: generic type Some_Window is new Window with private; -- take in any descendant of Window package Label_Mixin is type Window_With_Label is new Some_Window with private; -- Jazz it up somehow. -- Overridden operations: procedure Display(W : Window_With_Label); -- New operations: procedure Set_Label(W : in out Window_With_Label; S : String); -- Set the label function Label(W : Window_With_Label) return String; -- Fetch the label private type Window_With_Label is new Some_Window with record Label : String_Quark := Null_Quark; -- An XWindows-Like unique ID for a string end record; In the generic body, we implement the Overridden and New operations as appropriate, using any inherited operations, if necessary. For example, this might be an implementation of the overridden Display: procedure Display(W : Window_With_Label) is begin Display(Some_Window(W)); -- First display the window normally, -- by passing the buck to the parent type. if W.Label /= Null_Quark then -- Now display the label if it is not null Display_On_Screen(XCoord(W), YCoord(W)-5, Value(W.Label)); -- Use two inherited functions on Basic_Window -- to get the coordinates where to display the label. end if; end Display; Presuming we have several such generic packages defined, We can now create the desired window by mixing in repeatedly. First we declare the tailored window as a private extension of Basic_Window, and then we define it via a sequence of instantiations: type My_Window is Basic_Window with private; . . . private package Add_Label is new Label_Mixin(Basic_Window); package Add_Border is new Border_Mixin(Add_Label.Window_With_Label); package Add_Menu_Bar is new Menu_Bar_Mixin(Add_Border.Window_With_Border); type My_Window is new Add_Menu_Bar.Window_With_Menu_Bar with null record; -- Final window is a null extension of Window_With_Menu_Bar. -- We could instead make a record extension and -- add components for My_Window over and above those -- needed by the mix-ins. - Case(3) -- In this case, let us presume we have two independent hierarchies, one for Windows, which represent areas on the screen, and one for Monitors, which wait for an object to change, and then do something in reaction to it. An object that supports Monitors keeps a linked list of Monitors, and calls their Update operation whenever the object is changed. For example: type Monitor; type Monitor_Ptr is access Monitor'CLASS; type Monitored_Object is abstract tagged limited -- Monitored objects are derived from this root type record First : Monitor_Ptr; - List of monitors -- More components to be added by extension end record; type Monitored_Object_Ptr is access Monitored_Object'CLASS; type Monitor is abstract tagged limited record Next : Monitor_Ptr; Obj : Monitored_Object_Ptr; -- More components to be added by extension end record; procedure Update(M : in out Monitor) is abstract; -- Dispatching operation, called when monitored object -- is changed. Now suppose we want to create a Window that can act as a Monitor as well as a Window: First we define a mix-in that is a monitor, and override its Update op: type Monitor_Mixin(Win : access Basic_Window'CLASS) is new Monitor with null record; procedure Update(M : in out Monitor_Mixin); The body for this Update could be: procedure Update(M : in out Monitor_Mixin) is -- On an Update, simply re-Display the window begin Display(M.Win); -- This is a dispatching call end Update; Now we can "mix" this Monitor_Mixin into any window type, as follows: type Window_That_Monitors is new My_Window with record Mon : Monitor_Mixin(Window_That_Monitors'ACCESS); end record; We could define a tailored Monitor mix-in that did something besides just call the Display operation of the associated Window. But in many cases, this simple one will do the job. As these examples illustrate, Ada 9X provides support for the construction of essentially arbitrary multiple inheritance type hierarchies, without having to commit itself to a single linguistic mechanism for multiple inheritance, and without burdening simple single-inheritance problems with the complexity and implementation burden of linguistic multiple inheritance. Furthermore, the building blocks that support multiple inheritance are very general, and allow the programmer to easily go well beyond the limitations of conventional multiple inheritance mechanisms. ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Ada 90 inheritance request? 1994-12-02 16:46 ` Tucker Taft @ 1994-12-09 17:26 ` Cyrille Comar 1994-12-11 18:47 ` Bob Duff ` (2 more replies) 0 siblings, 3 replies; 13+ messages in thread From: Cyrille Comar @ 1994-12-09 17:26 UTC (permalink / raw) stt@spock.camb.inmet.com (Tucker Taft) writes: : : Here is the note on multiple inheritance in Ada 9X. : -Tucker Taft Thank's Tuck for your very instructive note. I love this kind of post where I fill like learning something rather than listening to someone complains. By the way, I have an intersting challenge for our Ada9x experts. Here is a problem that would have an obvious solution with Multiple Inheritance, I would like to see how it can be solved cleanly in 9x. The problem is not abstract, this is something that everybody will need at some point. PROBLEM: I have defined a hierarchy or tagged types and now I would like specialize one of them to be controlled (i.e. finalizable) with MI, I could write something like: type Ctrl_T is new T, Controlled with null record; -- overriding of Initialize/Adjust/Finalize procedure Initialize (Obj : in out Ctrl_T); procedure Adjust (Obj : in out Ctrl_T); procedure Finalize (Obj : in out Ctrl_T); and it would be the end of it... Beginning of Solution --------------------- Following your suggestions, we could define a generic package: generic type T is tagged private; package Make_it_Controlled is type Ctrl_T is new T with private; procedure Initialize (Obj : in out Ctrl_T); procedure Adjust (Obj : in out Ctrl_T); procedure Finalize (Obj : in out Ctrl_T); private type T_Controller is new Controlled with null record; procedure Initialize (Obj : in out T_Controller); procedure Adjust (Obj : in out T_Controller); procedure Finalize (Obj : in out T_Controller); type Ctrl_T is new T with record Ctrl : T_Controller; end record; end; and then I need to be able to call Initialize on Ctrl_T inside the Initialize of type T_Controller. Each time a variable V of type Ctrl_T is defined the Initialize on V.Ctrl will be called and will itself call Initialize on V. But now I am stuck because there is no way in the body of Initialize on T_Controller to refer to the englobing object.... Argggg Tuck gave another nice trick by using access discriminant that solves partially the problem. We can use it to redefine the controller: type Acc is access all Ctrl_T; type T_Controller (Englobing_Obj : Acc) is new Limited_Controlled with null record; type Ctrl_T is new T with record Ctrl : T_Controller (Ctrl_T'Access); end; and now I can write Initialize procedure Initialize (Obj : in out T_Controller) is begin Initialize (Obj.Englobing_Obj.all); end; And everything would be nice if it was legal... The problem is that the access discriminant requires a LIMITED TYPE and thus Ctrl_T must be limited. So this approach works pretty well if my formal generic parameter is type T is tagged limited private; This package allows any limited tagged type to be specialized as a limited_controlled type (sort of). MY CHALLENGE is : how to do the same thing with non-limited types ? -- ------------------------------------------------------------------------ Cyrille Comar, E-mail: comar@cs.nyu.edu Gnat Project US phone: (212) 998-3489 ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Ada 90 inheritance request? 1994-12-09 17:26 ` Cyrille Comar @ 1994-12-11 18:47 ` Bob Duff 1994-12-12 3:15 ` Tucker Taft 1994-12-13 19:02 ` John Goodsen 2 siblings, 0 replies; 13+ messages in thread From: Bob Duff @ 1994-12-11 18:47 UTC (permalink / raw) In article <3ca3vl$n14@lang8.cs.nyu.edu>, Cyrille Comar <comar@cs.nyu.edu> wrote: >PROBLEM: I have defined a hierarchy or tagged types and now I would like > specialize one of them to be controlled (i.e. finalizable) This issue came up in the review process, and I'll repeat pretty much what we said at the time. I think you have to ask what are the Initialize and Finalize *doing*? In most cases, you want to add controlledness because you are adding a component that needs it. For example, you are extending the parent type by adding a component of an access type, and you want finalization to clean up the heap object. In this (most common) case, it's no big deal -- just make the new component be of a controlled type. Whenever an object of the derived type is finalized, all of its components will be finalized. In this case, Initialize and Finalize don't need access to the containing object. It's hard to imagine a case where Initialize and Finalize for the new component(s) *do* need access to the containing object. Can anybody think of a realistic one? I would be interested to hear it. I suppose if the parent type needs finalization, but doesn't have it, then one might want to add it to avoid storage leaks or whatever. But in that case, the parent type is already broken. Type extension is mainly for extending abstractions, not really for fixing broken ones -- the latter is what Emacs is for. ;-) If the Initialize and Finalize *do* need access to the parent's fields, then the access-discriminant method can be used. But, as you point out, it only works for limited types. By the way, in your example: > type Acc is access all Ctrl_T; > type T_Controller (Englobing_Obj : Acc) is new Limited_Controlled > with null record; > > type Ctrl_T is new T with record > Ctrl : T_Controller (Ctrl_T'Access); > end; Englobing_Obj is *not* an access discriminant. It is a discriminant, and it is of a *named* access type. An "access discriminant" is a discriminant of an *anonymous* access type. (The terminology is a bit confusing, I admit.) As you wrote it, the example would be illegal, because it violates the accessibility rules (i.e. it can create dangling references). You could make it legal by changing 'Access to 'Unchecked_Access, but that leaves the possibility of dangling references (somebody might copy the value of the discriminant into a global variable of type Acc). Better to use an access discriminant: type T_Controller(Englobing_Obj: access Ctrl_T) is ... This prevents dangling references, and is legal. By the way, the "limited" rule you referred to is that only a limited type can have an access discriminant. Any type (limited or nonlimited) can have a discriminant of a named access type. (Well, it has to be composite, and not an array.) - Bob -- Bob Duff bobduff@inmet.com Oak Tree Software, Inc. Ada 9X Mapping/Revision Team (Intermetrics, Inc.) ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Ada 90 inheritance request? 1994-12-09 17:26 ` Cyrille Comar 1994-12-11 18:47 ` Bob Duff @ 1994-12-12 3:15 ` Tucker Taft 1994-12-13 19:02 ` John Goodsen 2 siblings, 0 replies; 13+ messages in thread From: Tucker Taft @ 1994-12-12 3:15 UTC (permalink / raw) In article <3ca3vl$n14@lang8.cs.nyu.edu>, Cyrille Comar <comar@cs.nyu.edu> wrote: > ... >PROBLEM: I have defined a hierarchy or tagged types and now I would like > specialize one of them to be controlled (i.e. finalizable) [Later you impose the additional requirement that the type be non-limited.] One cop-out solution is to make the root type controlled (even though OOP allows one to minimize the need to edit code when reusing it, it doesn't eliminate it altogether). Although this may seem be a bit of a cop-out, it may be that if one descendant of a type wants to "add" finalization, then other descendants would also benefit from it. Another approach is to simply make the new *components* controlled, rather than the type as a whole. In general, I would recommend that components clean up "themselves," rather than rely on a clean-up routine of an enclosing type. It seems to be simpler and more flexible. To give the best answer, it would help to have a better idea of what is the underlying problem you want to solve. In the abstract it is hard to make appropriate tradeoffs. For example, it might be a sign that you are abusing the "Is-A" relationship if a descendant needs to finalize the object as a whole (as opposed to just its new components) while its ancestor types do not. This might imply that inheritance is the wrong approach to begin with, and some other kind of type composition would fit the problem better. E.g., perhaps you should make the planned parent type into a component of your new type, rather than making the new type an extension of it. More details would help... >Cyrille Comar, E-mail: comar@cs.nyu.edu >Gnat Project US phone: (212) 998-3489 -Tucker Taft stt@inmet.com Intermetrics, Inc. ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Ada 90 inheritance request? 1994-12-09 17:26 ` Cyrille Comar 1994-12-11 18:47 ` Bob Duff 1994-12-12 3:15 ` Tucker Taft @ 1994-12-13 19:02 ` John Goodsen 1994-12-14 19:49 ` John Goodsen 1994-12-17 13:55 ` Tucker Taft 2 siblings, 2 replies; 13+ messages in thread From: John Goodsen @ 1994-12-13 19:02 UTC (permalink / raw) In article <3ca3vl$n14@lang8.cs.nyu.edu> comar@cs.nyu.edu (Cyrille Comar) writes: I am dismayed at how anyone could look at this so-called "solution" to multiple inheritance and walk away comfortable that Ada9X properly supports MI. The lack of *direct* support for MI in Ada 9X, is IMHO, a serious foobar that has to this point been explained away by the ivory tower language designers as either (a) something that I don't really need (thanks for being so luminiscent in my needs) or (b) if you really need it, it's only for abstract, mixin type class behavior which you can perform using SI and generics. Neither of which is an aesthetically or technically pleasing solution. So I'll go past the rhetoric of verbal, get-us-nowhere discussions on why *DIRECT* support for MI should or should not be in Ada 9X and propose a little excercise for one of those language designers who feel that direct MI support is not necessary: 1. Read and understand the Design Patterns book by Gamma et. al... If you haven't read this yet, then you are missing out on some great insight into designing reusable software. 2. Recreate in Ada9X some of the sample implementations of patterns that require MI as part of the solution. Post the Gamma et. al. C++ solutions and compare them to the Ada9X solution. I believe it will become quite obvious what a hack it turns out to be in Ada 9X. Tucker, sound like something you or your people could take on as a quick and dirty excercise? I think it would give you better insight into the problems. you up for it? speaking from my Wall St. cubicle... -- John Goodsen Currently on-site at: The Dalmatian Group JP Morgan User Interface Specialists 60 Wall St., New York City jgoodsen@radsoft.com jgoodsen@jpmorgan.com --- included message ---- stt@spock.camb.inmet.com (Tucker Taft) writes: : : Here is the note on multiple inheritance in Ada 9X. : -Tucker Taft Thank's Tuck for your very instructive note. I love this kind of post where I fill like learning something rather than listening to someone complains. By the way, I have an intersting challenge for our Ada9x experts. Here is a problem that would have an obvious solution with Multiple Inheritance, I would like to see how it can be solved cleanly in 9x. The problem is not abstract, this is something that everybody will need at some point. PROBLEM: I have defined a hierarchy or tagged types and now I would like specialize one of them to be controlled (i.e. finalizable) with MI, I could write something like: type Ctrl_T is new T, Controlled with null record; -- overriding of Initialize/Adjust/Finalize procedure Initialize (Obj : in out Ctrl_T); procedure Adjust (Obj : in out Ctrl_T); procedure Finalize (Obj : in out Ctrl_T); and it would be the end of it... Beginning of Solution --------------------- Following your suggestions, we could define a generic package: generic type T is tagged private; package Make_it_Controlled is type Ctrl_T is new T with private; procedure Initialize (Obj : in out Ctrl_T); procedure Adjust (Obj : in out Ctrl_T); procedure Finalize (Obj : in out Ctrl_T); private type T_Controller is new Controlled with null record; procedure Initialize (Obj : in out T_Controller); procedure Adjust (Obj : in out T_Controller); procedure Finalize (Obj : in out T_Controller); type Ctrl_T is new T with record Ctrl : T_Controller; end record; end; and then I need to be able to call Initialize on Ctrl_T inside the Initialize of type T_Controller. Each time a variable V of type Ctrl_T is defined the Initialize on V.Ctrl will be called and will itself call Initialize on V. But now I am stuck because there is no way in the body of Initialize on T_Controller to refer to the englobing object.... Argggg Tuck gave another nice trick by using access discriminant that solves partially the problem. We can use it to redefine the controller: type Acc is access all Ctrl_T; type T_Controller (Englobing_Obj : Acc) is new Limited_Controlled with null record; type Ctrl_T is new T with record Ctrl : T_Controller (Ctrl_T'Access); end; and now I can write Initialize procedure Initialize (Obj : in out T_Controller) is begin Initialize (Obj.Englobing_Obj.all); end; And everything would be nice if it was legal... The problem is that the access discriminant requires a LIMITED TYPE and thus Ctrl_T must be limited. So this approach works pretty well if my formal generic parameter is type T is tagged limited private; This package allows any limited tagged type to be specialized as a limited_controlled type (sort of). MY CHALLENGE is : how to do the same thing with non-limited types ? -- ------------------------------------------------------------------------ Cyrille Comar, E-mail: comar@cs.nyu.edu Gnat Project US phone: (212) 998-3489 -- -- John Goodsen Currently on-site at: The Dalmatian Group JP Morgan User Interface Specialists 60 Wall St., New York City jgoodsen@radsoft.com jgoodsen@jpmorgan.com ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Ada 90 inheritance request? 1994-12-13 19:02 ` John Goodsen @ 1994-12-14 19:49 ` John Goodsen 1994-12-15 18:41 ` Robert I. Eachus 1994-12-17 13:55 ` Tucker Taft 1 sibling, 1 reply; 13+ messages in thread From: John Goodsen @ 1994-12-14 19:49 UTC (permalink / raw) In article <JGOODSEN.94Dec13140247@treasure.radsoft.com> jgoodsen@treasure.radsoft.com (John Goodsen) writes: Tucker, sound like something you or your people could take on as a quick and dirty excercise? I think it would give you better insight into the problems. you up for it? Of course, I don't really expect the ivory tower league to undertake such a revealing comparison. It makes too much sense and would expose the err in the MI reasoning process. Keeping your balance on the soapbox seems to take priority over putting Ada into the mainstream communities. Ahhhh, back to the real world of C++ (unfortunately). You guys make it too damned hard to sell Ada into the mainstream markets... see ya 6 feet under in the OOPL graveyard... -- -- John Goodsen Currently on-site at: The Dalmatian Group JP Morgan User Interface Specialists 60 Wall St., New York City jgoodsen@radsoft.com jgoodsen@jpmorgan.com ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Ada 90 inheritance request? 1994-12-14 19:49 ` John Goodsen @ 1994-12-15 18:41 ` Robert I. Eachus 1994-12-19 18:58 ` John Goodsen 0 siblings, 1 reply; 13+ messages in thread From: Robert I. Eachus @ 1994-12-15 18:41 UTC (permalink / raw) In article <JGOODSEN.94Dec14144949@treasure.radsoft.com> jgoodsen@treasure.radsoft.com (John Goodsen) writes: > Of course, I don't really expect the ivory tower league to > undertake such a revealing comparison. It makes too much sense > and would expose the err in the MI reasoning process. Keeping > your balance on the soapbox seems to take priority over putting > Ada into the mainstream communities. Never being afraid to jump into the fray, I'll respond to John by taking his MI example and asking why the obvious Ada 95 version is not acceptable: type Needs_Finalization is new Controlled with...; -- overriding of Initialize/Adjust/Finalize procedure Initialize (Obj : in out Needs_Finalization); procedure Adjust (Obj : in out Needs_Finalization); procedure Finalize (Obj : in out Needs_Finalization); type Ctrl_T is new T with record NF: Needs_Finalization; end record; Now I can think of cases where this won't work, but they are precisely the cases where the "true" MI version won't work either. No, I take that back. There are cases where true MI will fail, and the Ada 95 version will sail through. For example, if someone modifies T so that it inherits directly from Controlled... In general where Ada 95 says "this is unsafe, so we will not allow it," most MI languages say "this is really neat, and if you are VERY careful, it will work, but never on large projects, because it doesn't scale well." I've been there, I've done that, I've seen the maintenance nightmares that can occur when more than one programmer depends on unsafe MI on the same project. There is a lot of reasonable, and safe, MI out there. But if you check, it is all easily (and cleanly) translatable to Ada 95. In this case, I think it was Tucker who gave exactly the right answer--if Ctrl_T needs to modify parts of T as part of assignment or finalization, you really need to think long and hard about whether or not other descendants of T will need to do the same thing. If so, the right choice is to make T a (direct or indirect) child of Controlled. If not then you are almost certainly breaking the class model for T. (Making Ctrl_T the root of it's own class, or the part of a class rooted at Controlled, and having it add the "T" abstraction as a component or mix-in is easy, if a solution without the "is a" property is correct.) -- Robert I. Eachus with Standard_Disclaimer; use Standard_Disclaimer; function Message (Text: in Clever_Ideas) return Better_Ideas is... ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Ada 90 inheritance request? 1994-12-15 18:41 ` Robert I. Eachus @ 1994-12-19 18:58 ` John Goodsen 1994-12-20 10:40 ` Robert I. Eachus ` (2 more replies) 0 siblings, 3 replies; 13+ messages in thread From: John Goodsen @ 1994-12-19 18:58 UTC (permalink / raw) In article <EACHUS.94Dec15184124@spectre.mitre.org> eachus@spectre.mitre.org (Robert I. Eachus) writes: Never being afraid to jump into the fray, I'll respond to John by taking his MI example and asking why the obvious Ada 95 version is not acceptable: type Needs_Finalization is new Controlled with...; -- overriding of Initialize/Adjust/Finalize procedure Initialize (Obj : in out Needs_Finalization); procedure Adjust (Obj : in out Needs_Finalization); procedure Finalize (Obj : in out Needs_Finalization); type Ctrl_T is new T with record NF: Needs_Finalization; end record; Maybe it's just me not seeing something here (which could be an indicator of the usability issues involved with addressing MI in Ada95), but where is the polymorphic behavior in the MI lattice? Wouldn't the above solution require me to delegate methods to the NF member of Ctrl_T (by hand)? Now I can think of cases where this won't work, but they are precisely the cases where the "true" MI version won't work either. No, I take that back. There are cases where true MI will fail, and the Ada 95 version will sail through. For example, if someone modifies T so that it inherits directly from Controlled... In general where Ada 95 says "this is unsafe, so we will not allow it," most MI languages say "this is really neat, and if you are VERY careful, it will work, but never on large projects, because it doesn't scale well." I've been there, I've done that, I've seen the maintenance nightmares that can occur when more than one programmer depends on unsafe MI on the same project. This isn't clear to me. Can you give an example (besides the overused diamond inheritance pattern) ? What do you mean by "unsafe MI" ? thanks -- -- John Goodsen Currently on-site at: The Dalmatian Group JP Morgan User Interface Specialists 60 Wall St., New York City jgoodsen@radsoft.com jgoodsen@jpmorgan.com ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Ada 90 inheritance request? 1994-12-19 18:58 ` John Goodsen @ 1994-12-20 10:40 ` Robert I. Eachus 1994-12-21 16:02 ` Norman H. Cohen 1994-12-22 1:21 ` Bob Duff 2 siblings, 0 replies; 13+ messages in thread From: Robert I. Eachus @ 1994-12-20 10:40 UTC (permalink / raw) In article <JGOODSEN.94Dec19135859@treasure.radsoft.com> jgoodsen@treasure.radsoft.com (John Goodsen) writes: > Maybe it's just me not seeing something here (which could be an indicator > of the usability issues involved with addressing MI in Ada95), but where > is the polymorphic behavior in the MI lattice? Wouldn't the above solution > require me to delegate methods to the NF member of Ctrl_T (by hand)? You lost me here. Initialize, Finalize, and Adjust have to be defined on NF, but not on Ctrl_T. All the operations on T are derived for Ctrl_T, and appropriate dispatching occurs. The ONLY issue, as has been discussed is if you need finalization, etc. for some part of the inherited T, or if you need access to the inherited parts of T to do the initialization, etc. But as I (and others) see it, the major problem in that case is the broken abstraction, not the unsafe approaches to implementing the operations. > This isn't clear to me. Can you give an example (besides the overused > diamond inheritance pattern) ? What do you mean by "unsafe MI" ? The easiest definition on a large project is any case where a new subclass of one of the parents (or a change to one of the existing subclasses) can break the MI inheriting class. The usual break is when a new subclass of class A has a method which conflicts with a method inherited from class B, and you apply a class-wide operation of the MI class to a member of that subclass (with casting as necessary). You can get a dispatching call in some other method which now calls the "wrong" method, and there is no point visible to a single programmer where all this is visible. That's the major "doesn't scale" problem. Ada 9X avoids it by insisting that both of the methods involved must have a common template inherited from a single parent. If you have two mix-ins with the "same" method, the visibility rules, not the dispatching rules determine which gets called. (Unless, as stated, they both explicitly match a template in a superclass. But in that case, we do have a warning flag.) -- Robert I. Eachus with Standard_Disclaimer; use Standard_Disclaimer; function Message (Text: in Clever_Ideas) return Better_Ideas is... ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Ada 90 inheritance request? 1994-12-19 18:58 ` John Goodsen 1994-12-20 10:40 ` Robert I. Eachus @ 1994-12-21 16:02 ` Norman H. Cohen 1994-12-22 1:21 ` Bob Duff 2 siblings, 0 replies; 13+ messages in thread From: Norman H. Cohen @ 1994-12-21 16:02 UTC (permalink / raw) In article <JGOODSEN.94Dec19135859@treasure.radsoft.com>, jgoodsen@treasure.radsoft.com (John Goodsen) writes: |> In article <EACHUS.94Dec15184124@spectre.mitre.org> eachus@spectre.mitre.org (Robert I. Eachus) writes: |> |> Never being afraid to jump into the fray, I'll respond to John by |> taking his MI example and asking why the obvious Ada 95 version is not |> acceptable: |> |> type Needs_Finalization is new Controlled with...; |> -- overriding of Initialize/Adjust/Finalize |> procedure Initialize (Obj : in out Needs_Finalization); |> procedure Adjust (Obj : in out Needs_Finalization); |> procedure Finalize (Obj : in out Needs_Finalization); |> |> type Ctrl_T is new T with record |> NF: Needs_Finalization; |> end record; |> |> |> Maybe it's just me not seeing something here (which could be an indicator |> of the usability issues involved with addressing MI in Ada95), but where |> is the polymorphic behavior in the MI lattice? Wouldn't the above solution |> require me to delegate methods to the NF member of Ctrl_T (by hand)? Here's what I think you're not seeing: EVERY Ada object, whether or not it is a controlled type, gets "finalized" when it is about to cease to exist. Finalization consists of calling that object's Finalize procedure if the type is controlled, and then finalizing each of the object's components, whether or not the type is controlled. (See RM95 section 7.6.1, paragraphs 6 through 9.) Thus when a Ctrl_T object is about to cease to exist, the Finalize procedure for type Needs_Finalization is called for the NF component of that object. (C++ works the same way. The difference comes in the treatment of any finalization code for ancestors. In C++, the base class destructor is unavoidably invoked after the destructor for the derived class; in Ada, it is up to the programmer whether a Finalize procedure for a derived controlled type should invoke its parent's Finalize procedure.) Perhaps you forgot the context of the question that was posed. No requirement was stated for Ctrl_T to publicly inherit any "normal" operations of Needs_Finalization. Rather, Needs_Finalization was presumed to be part of the IMPLEMENTATION of the extension. There was a misconceived requirement for Ctrl_T to inherit the Finalization operation of Needs_Finalization, but Robert Eachus's construction shows that this is unnecessary. |> Can you give an example (besides the overused |> diamond inheritance pattern) ? What do you mean by "unsafe MI" ? If the diamond inheritance pattern is "overused", that's because the problems it presents are compelling, all by themselves. But there is a more general safety issue. In a language with MI, given two arbitrary classes A and B, it is always possible to construct an object that can be used as both an A and a B, so one cannot recognize uses of an A as a B as illegitimate at compile time. (The legitimate uses, but they are so few and far between that it is not worth opening gaping holes in the type system.) This issue shows up in the new C++ dynamic_cast feature. In Ada, conversion from access-to-S'Class to access-to-T'Class is not allowed unless S is derived from T or T is derived from S. (If T is derived from S, a run-time check is performed to ensure that the access-to-S'Class value actually points to an object whose type is derived from T; if S is derived from T, no run-time check is necessary.) In any other case, the conversion is rejected as illegal at compile time. In C++, a dynamic cast from S* to T* is always legal at compile time. It cannot be rejected, because someone may later derive a class D from BOTH S and T, in which case an S* pointing to a D can be safely converted to a T*. (At run time, the dynamic cast returns a null pointer if the S* value does not point to an object of a class derived from T. :-( ) -- Norman H. Cohen ncohen@watson.ibm.com ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Ada 90 inheritance request? 1994-12-19 18:58 ` John Goodsen 1994-12-20 10:40 ` Robert I. Eachus 1994-12-21 16:02 ` Norman H. Cohen @ 1994-12-22 1:21 ` Bob Duff 2 siblings, 0 replies; 13+ messages in thread From: Bob Duff @ 1994-12-22 1:21 UTC (permalink / raw) In article <JGOODSEN.94Dec19135859@treasure.radsoft.com>, John Goodsen <jgoodsen@treasure.radsoft.com> wrote: >In article <EACHUS.94Dec15184124@spectre.mitre.org> eachus@spectre.mitre.org (Robert I. Eachus) writes: > type Needs_Finalization is new Controlled with...; > -- overriding of Initialize/Adjust/Finalize > procedure Initialize (Obj : in out Needs_Finalization); > procedure Adjust (Obj : in out Needs_Finalization); > procedure Finalize (Obj : in out Needs_Finalization); > > type Ctrl_T is new T with record > NF: Needs_Finalization; > end record; > >Maybe it's just me not seeing something here (which could be an indicator >of the usability issues involved with addressing MI in Ada95), but where >is the polymorphic behavior in the MI lattice? Wouldn't the above solution >require me to delegate methods to the NF member of Ctrl_T (by hand)? No. When an object is finalized, all of its components are finalized automatically. If this weren't true, then finalization would be rather badly broken -- any object that was a component of another object wouldn't work right. - Bob -- Bob Duff bobduff@inmet.com Oak Tree Software, Inc. Ada 9X Mapping/Revision Team (Intermetrics, Inc.) ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Ada 90 inheritance request? 1994-12-13 19:02 ` John Goodsen 1994-12-14 19:49 ` John Goodsen @ 1994-12-17 13:55 ` Tucker Taft 1 sibling, 0 replies; 13+ messages in thread From: Tucker Taft @ 1994-12-17 13:55 UTC (permalink / raw) In article <JGOODSEN.94Dec13140247@treasure.radsoft.com>, John Goodsen <jgoodsen@treasure.radsoft.com> wrote: >I am dismayed at how anyone could look at this so-called "solution" to >multiple inheritance and walk away comfortable that Ada9X properly >supports MI. The lack of *direct* support for MI in Ada 9X, is IMHO, >a serious foobar that has to this point been explained away by the >ivory tower language designers ... Feel free to accuse of us of all kinds of crimes against nature, but please don't put us in an ivory tower ;-). I trust that those who know me recognize that I tend to live more in the cement dungeon of language design, scrabbling around in the cement dust of human engineering, implementation concerns, efficiency, etc. The Ada 9X mapping/revision team consisted almost entirely of dedicated, very experienced, Ada and other-language programmers with a strong penchant for designing the most productive, reliable, readable, and usable language they knew how, given the constraints of the process. An easy to use, intuitive, efficient, straightforward, multiple inheritance mechanism in Ada 9X would have been great. Unfortunately, we have never seen such a beast. Perhaps you have. What we found when investigating ways to support multiple inheritance was that everybody knew what was the best way to do it -- their own way! When we have encountered such things in the past during language design, the conclusion to be drawn is that implementing multiple inheritance is still a *programming* problem, not a language design problem. If even two existing OOP languages did Multiple Inheritance the same way we would have seen that as a reasonable starting place. But the fact is that the rules for dealing with the inevitable additional complexities of multiple inheritance are different in essentially every langauge we looked at. Even Sather and Eiffel, which otherwise have a lot in common, differ in how they resolve the MI issues. C++ in its usual pragmatic way provides two different ways to do MI, using regular or virtual base classes. CLOS just merges based on names and a search order for methods. Eiffel provides renaming. Etc... By constrast, all of the languages agree on how to do single inheritance in the language, and it is essentially unchanged since the days of Simula '67. It is intuitive, efficient, straightforward, reliable, type-safe, productive, useful, ... That sounds like a feature for Ada ;-). Note that Smalltalk, Object Pascal, Modula-3, Oberon-X, and Objective-C all have chosen to stick with single inheritance. There are (ivory tower ;-) researchers studying ways to add MI in some of these languages, but the practitioners seem to be plowing ahead very productively and happily relying on the rock-solid single inheritance, building up their conceptual multiple inheritance hierarchies as they see fit, in an application-specific way. We expect that most Ada 95 programmers will find the same thing. Single inheritance is there, in its full glory and simplicity, right in the language. In addition, there are building blocks for constructing robust application-specific solutions to problems that might otherwise use a language-provided multiple inheritance mechanism. Furthermore, what happens in some cases, is that the language-provided multiple inheritance mechanism turns out to be not quite the right solution for a given problem that is "reminiscent" of multiple inheritance. Then you have the worst of both worlds -- a language made more complex by the addition of linguistic multiple inheritance, and an application made more difficult by the lack of appropriate building blocks for solving the problem the "right" way. To summarize, it is our view that it is a bit ivory-towerish to look at a language, and say that if it doesn't have feature X implemented in some specific way, then it is useless and FUBAR. I can't tell you how many times we have been threatened over the past 5 years with similar statements about all kinds of obscure features. It is interesting to follow the C++ standardization process, where the same desparate statements are made in support of one obscure feature after another. The fact is that a good language has its own self-consistent logic, and some features make a good addition to that structure, and others just don't fit. We couldn't find a language-provided multiple inheritance mechanism that enhanced Ada. We could find plenty that made it more complicated, less intuitive, less reliable, less readable, etc. We welcomed proposals and suggestions for one that would be a net addition to the language, but on deeper investigation into the nitty-gritty, we never found one where the pluses outweighed the minuses. We had an "ivory-tower" desire to try to find one, but we had a more practical concern that we didn't want to FU the language to get one in, just for the marketing potential. We wanted the features to be useful and reliable, not just there for show. Perhaps the designers of Ada 007 will find the silver bullet... Meanwhile, we will be productively building reliable systems in our cement basement. >John Goodsen Currently on-site at: >The Dalmatian Group JP Morgan >User Interface Specialists 60 Wall St., New York City >jgoodsen@radsoft.com jgoodsen@jpmorgan.com S. Tucker Taft stt@inmet.com Ada 9X Mapping/Revision Team Intermetrics, Inc. Cambridge, MA 02138 ^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~1994-12-22 1:21 UTC | newest] Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 1994-11-23 21:33 Ada 90 inheritance request? S M Ryan 1994-12-02 16:46 ` Tucker Taft 1994-12-09 17:26 ` Cyrille Comar 1994-12-11 18:47 ` Bob Duff 1994-12-12 3:15 ` Tucker Taft 1994-12-13 19:02 ` John Goodsen 1994-12-14 19:49 ` John Goodsen 1994-12-15 18:41 ` Robert I. Eachus 1994-12-19 18:58 ` John Goodsen 1994-12-20 10:40 ` Robert I. Eachus 1994-12-21 16:02 ` Norman H. Cohen 1994-12-22 1:21 ` Bob Duff 1994-12-17 13:55 ` Tucker Taft
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox