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.8 required=5.0 tests=BAYES_00,FILL_THIS_FORM, INVALID_DATE autolearn=no autolearn_force=no version=3.4.4 X-Google-Language: ENGLISH,ASCII-7-bit X-Google-Thread: 109fba,66253344eaef63db,start X-Google-Attributes: gid109fba,public X-Google-Thread: 103376,66253344eaef63db,start X-Google-Attributes: gid103376,public X-Google-Thread: 1108a1,66253344eaef63db,start X-Google-Attributes: gid1108a1,public X-Google-ArrivalTime: 1994-09-27 10:50:10 PST Newsgroups: comp.lang.ada,comp.object,comp.lang.c++ Path: bga.com!news.sprintlink.net!howland.reston.ans.net!europa.eng.gtefsd.com!MathWorks.Com!news2.near.net!noc.near.net!ray.com!news.ray.com!swlvx2!jgv From: jgv@swl.msd.ray.com (John Volan) Subject: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) Date: Tue, 27 Sep 1994 16:52:03 GMT Summary: Can mutual recursion be established between two object classes in Ada9X without breaking encapsulation between them? Message-ID: <1994Sep27.165203.9192@swlvx2.msd.ray.com> Sender: news@swlvx2.msd.ray.com (NEWS USER) Keywords: Ada 9X, C++, object-oriented Organization: Raytheon Company, Tewksbury, MA Xref: bga.com comp.lang.ada:6237 comp.object:6836 comp.lang.c++:30652 Date: 1994-09-27T16:52:03+00:00 List-Id: As a long-time Ada evangelist and self-styled acolyte of Team-Ada (ahh gimme that ol' time language religion! :-), I'm quite pleased with the new OO features of Ada 9X. One of the things I like is that Ada 9X does *NOT* follow the crowd of other OO languages in one very important particular: *NO* attempt was made to merge the features of packages and types into some kind of new "class" construct. Issues of modularity, encapsulation, information hiding, interface control, scoping, inter-module dependency, namespace control, generic templating, etc., are still the province of packages. Issues of data abstraction, strong typing, type derivation and inheritance, etc., are still the province of types. And the place where these two areas intersect is still the same: private types. IMHAARO (In My Humble And Admittedly Religious Opinion :-) this kind of separation of concerns is extremely beneficial. I've looked at other OO languages that use the "class" as both the unit of data abstraction and the unit of modularity (mostly Smalltalk, C++, Eiffel), and my gut reaction is that this attempt to merge features into a single construct leads to a lot of unnecessary "baroqueness," in both syntax and semantics. C++'s constructors and destructors, and Eiffel's "anchor" and "like" mechanisms, come to mind as prime examples of this. (Please, no fire and brimstone from you folks in the other religions -- it's MHAARO after all, and I'd certainly welcome hearing yours... :-) However, there is one specific kind of situation that I'm having difficulty mapping into Ada 9X: How can you achieve mutual recursion between two classes of object without breaking encapsulation? In other words, you have two classes of objects that must "know" about each other's interfaces, but you don't want to have to expose their implementations to each other in order to do that. Consider a simple example where we have two classes related in a simple one-to-one association: Employee -- occupies -- Office Of course, associations are conceptually bi-directional, so this could be expressed equivalently as: Office -- is occupied by -- Employee If our application needs to traverse this association only in one direction or the other, then we are free to choose a design where only one of the classes has to "know" about the other class. It's fairly straightforward to map such a design into an Ada coding style that encapsulates each class as a private type declared in its own package. One class "knowing" about the other translates into one package importing the other via a "with" clause. (We can sort of mimic this kind of Ada modularization in C++ by putting each C++ class in a separate header file and having one header file "#include" the other.) For instance, suppose our application needs to be able to start from a given Employee object and look up its occupied Office object. We could have: ---------------------------------------------------------------------- with ...; -- other stuff, but no knowledge about the Employee package package Office is type Object is tagged limited private; -- perhaps we'll distinguish -- different subclasses of Office type Pointer is access all Object'Class; None : constant Pointer := null; ... -- various subprograms involving that "other stuff" private type Object is tagged record ... -- various components involving that "other stuff" end record; end Office; ---------------------------------------------------------------------- with ...; -- other stuff with Office; -- Employee class "knows" about Office class package Employee is type Object is tagged limited private; -- perhaps we'll distinguish -- different subclasses of Employee type Pointer is access all Object'Class; None : constant Pointer := null; ... -- various subprograms involving that "other stuff" function Office_Occupied_By (The_Employee : in Employee.Object) return Office.Pointer; procedure Occupy (The_Employee : in out Employee.Object; The_Office : in Office.Pointer); ... private type Object is tagged record ... -- various components involving that "other stuff" Its_Occupied_Office : Office.Pointer; ... end record; end Employee; ---------------------------------------------------------------------- The equivalent in C++ might be: ////////////////////////////////////////////////////////////////////// // File: Office.h #ifndef _Office_h #define _Office_h #include " ... other stuff ... " class Office { public: ... // various member functions involving that "other stuff" private: ... // various data members involving that "other stuff" }; #endif _Office_h ////////////////////////////////////////////////////////////////////// // File: Employee.h #ifndef _Employee_h #define _Employee_h #include " ... other stuff ... " #include "Office.h" class Employee { public: ... // various member functions involving that "other stuff" Office* occupiedOffice () const; void occupy (Office* theOffice); ... private: ... // various data members involving that "other stuff" Office* itsOccupiedOffice; .. }; #endif _Employee_h ////////////////////////////////////////////////////////////////////// Conversely, if our application needs to be able to start from a given Office object and look up the Employee object which occupies it, then we just need to flip the dependency around: ---------------------------------------------------------------------- with ...; -- other stuff, but no knowledge about the Office package package Employee is type Object is tagged limited private; -- perhaps we'll distinguish -- different subclasses of Employee type Pointer is access all Object'Class; None : constant Pointer := null; ... -- various subprograms involving that "other stuff" private type Object is tagged record ... -- various components involving that "other stuff" end record; end Employee; ---------------------------------------------------------------------- with ...; -- other stuff with Employee; -- Office class "knows" about Employee class package Office is type Object is tagged limited private; -- perhaps we'll distinguish -- different subclasses of Office type Object is tagged limited private; type Pointer is access all Object'Class; None : constant Pointer := null; ... -- various subprograms involving that "other stuff" function Employee_Occupying (The_Office : in Office.Object) return Employee.Pointer; procedure Occupy (The_Office : in out Office.Object; The_Employee : in Employee.Pointer); ... private type Object is tagged record ... -- various components involving that "other stuff" Its_Occupying_Employee : Employee.Pointer; ... end record; end Office; ---------------------------------------------------------------------- The equivalent in C++ might be: ////////////////////////////////////////////////////////////////////// // File: Employee.h #ifndef _Employee_h #define _Employee_h #include " ... other stuff ... " class Employee { public: ... // various member functions involving that "other stuff" private: ... // various data members involving that "other stuff" }; #endif _Employee_h ////////////////////////////////////////////////////////////////////// // File: Office.h #ifndef _Office_h #define _Office_h #include " ... other stuff ... " #include "Employee.h" class Office { public: ... // various member functions involving that "other stuff" Employee* occupyingEmployee () const; void beOccupiedBy (Employee* theEmployee); ... private: ... // various data members involving that "other stuff" Employee* itsOccupyingEmployee; .. }; #endif _Office_h ////////////////////////////////////////////////////////////////////// In either of these scenarios, we have been able to maintain complete encapsulation of both object classes. The class in the second package is always required to deal with the class from the first package strictly through the interface that appears in the public part of the first package. But what if we need to be able to traverse the association in *both* directions? The most straightforward design to meet this requirement would make the data types for Employee and Office mutually recursive: Each class of object would contain a reference [1] to an object of the other class. This is easy enough to do (in either Ada or C++) by "forward declaring" the types so that both of them can "know" about each other -- but it appears that we'd have to relinquish the idea of putting the two classes in separate packages. Here's my attempt at mutually recursive classes in Ada 9X: ---------------------------------------------------------------------- with ... ; -- other stuff needed by Employee with ... ; -- other stuff needed by Office package Employee_And_Office is type Employee is tagged limited private; -- already acts as a forward decl type Employee_Pointer is access all Employee'Class; No_Employee : constant Employee_Pointer := null; ... -- Employee subprograms involving that "other stuff" type Object is tagged limited private; -- already acts as a forward decl type Office_Pointer is access all Office'Class; No_Office : constant Office_Pointer := null; ... -- Office subprograms involving that "other stuff" function Office_Occupied_By (The_Employee : in Employee) return Office_Pointer; function Employee_Occupying (The_Office : in Office) return Employee_Pointer; procedure Occupy (The_Office : in Office_Pointer; The_Employee : in Employee_Pointer); -- Hmm ... does this count as a primitive/dispatching operation for -- *both* tagged types, and therefore a compiler error? private type Employee is tagged record ... -- various components involving that "other stuff" Its_Occupied_Office : Office_Pointer; ... end record; type Office is tagged record ... -- various components involving that "other stuff" Its_Occupying_Employee : Employee_Pointer; ... end record; end Employee_And_Office; ---------------------------------------------------------------------- Here's my equivalent C++: ////////////////////////////////////////////////////////////////////// // File: EmployeeAndOffice.h #ifndef _EmployeeAndOffice_h #define _EmployeeAndOffice_h #include " ... other stuff needed by Employee ... " #include " ... other stuff needed by Office ... " class Employee; // forward declaration class Office; // forward declaration class Employee { public: ... // various member functions involving that "other stuff" Office* occupiedOffice () const; friend void occupy (Office* theOffice, Employee* theEmployee); ... private: ... // various data members involving that "other stuff" Office* itsOccupiedOffice; .. }; class Office { public: ... // various member functions involving that "other stuff" Employee* occupyingEmployee () const; friend void occupy (Office* theOffice, Employee* theEmployee); ... private: ... // various data members involving that "other stuff" Employee* itsOccupyingEmployee; .. }; #endif _EmployeeAndOffice_h ////////////////////////////////////////////////////////////////////// In both the Ada 9X and C++ versions we do need to break encapsulation a bit: The two classes had to be stuck in the same package, and the Occupy procedure, which establishes the association between an Employee and an Office, needs to have visibility to the implementations of both classes (in the C++ version, it's a "friend" of both classes). However, in the C++ version, that's the *only* point where we need to break encapsulation. If Employee and Office objects need to interface each other in any other way, they have to go through the public member functions. In the Ada version, on the other hand, having both types in the same package broke encapsulation between them once and for all. Any Employee subprogram can exploit detailed knowledge of the implementation of the Office type, and vice versa. (Of course, we still haven't broken encapsulation with respect to any *other* classes -- clients of Employee and Office are still restricted to using their public interfaces.) So my question is: Is there some other arrangement of Ada 9X packages (perhaps involving the new hierarchical library mechanism and child units) that allows for this kind of mutual recursion, and yet preserves the same amount of encapsulation that the C++ version does? Thanks in advance for any help, -- John Volan FOOTNOTE: [1] In the above discussion, I used the term "reference to an object" as an attempt to generalize the notion of a type of data that does not represent the object itself, but which in some way "designates" or "refers to" or "expresses the identity of" the object. The most obvious implementation of this notion is pointers (Ada access values), and this implementation is assumed in my code examples above. However, this is not the only possibility. Numeric indexes to a statically allocated array of objects can act as "identification numbers" for those objects, and this leads to a possible solution of the problem I posed above: You could create packages that just introduce the identification-number types, then use them to introduce the object-type packages. Now, where those statically-allocated arrays of objects would have to go, that's another question. This solution is obviously more complicated, and less generally applicable than pointers. So I'd still like to know of any better solution that might exist. -------------------------------------------------------------------------------- -- Me : Person := (Name => "John Volan", -- Company => "Raytheon Missile Systems Division", -- E_Mail_Address => "jgv@swl.msd.ray.com", -- Affiliation => "Enthusiastic member of Team Ada!", -- Humorous_Disclaimer => "These opinions are undefined " & -- "by my employer and therefore " & -- "any use of them would be " & -- "totally erroneous."); --------------------------------------------------------------------------------