* Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) @ 1994-09-27 16:52 John Volan 1994-09-27 18:48 ` Mark A Biggar 1994-09-28 14:01 ` Norman H. Cohen 0 siblings, 2 replies; 43+ messages in thread From: John Volan @ 1994-09-27 16:52 UTC (permalink / raw) 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."); -------------------------------------------------------------------------------- ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-09-27 16:52 Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) John Volan @ 1994-09-27 18:48 ` Mark A Biggar 1994-09-29 1:46 ` John Volan 1994-09-28 14:01 ` Norman H. Cohen 1 sibling, 1 reply; 43+ messages in thread From: Mark A Biggar @ 1994-09-27 18:48 UTC (permalink / raw) [Discussion of how to set up mutualy recursive tagged types] First of all you really only need to have a forward definition of one of the two types, lets choose office. So first define a abstract office type that doesn't knoe about employees: package Abs_Office is type Abstract_Office is abstract tagged null record; type Access_Office is access all Abstract_Office'class; end Abs_Office; Now we can define the employee type in terms of the above abstract type: with Abs_Office; use Abs_Office; package Emp is type Employee is tagged record Office: Access_Office; end record; type Employee_Access is access all Employee'class; end Emp; Now we can define the real office type: with Abs_Office; use Abs_Office; with Emp; use Emp; package Off is type Office is new Abstract_Office with record Employee: Employee_Access; end record; end Off; This of course ignores the private aspects of the C++ example given in the original article, but these are easly added using private types and/or private extensions in the above packages. Moral: if you need mutualy recursive tagged types with out breaking encapsulation, then use an abstract type for you forward reference. -- Mark Biggar mab@wdl.loral.com ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-09-27 18:48 ` Mark A Biggar @ 1994-09-29 1:46 ` John Volan 1994-09-29 13:57 ` Tucker Taft ` (2 more replies) 0 siblings, 3 replies; 43+ messages in thread From: John Volan @ 1994-09-29 1:46 UTC (permalink / raw) Mark, Thanks for your suggestion, but I think there are some problems with it: MAB = mab@dst17.wdl.loral.com (Mark A Biggar) writes: MAB>[Discussion of how to set up mutualy recursive tagged types] MAB> MAB>First of all you really only need to have a forward definition of one MAB>of the two types, lets choose office. Okay, I can buy that. I'd like to keep things symmetrical if possible, but I can live without it if I have to ... MAB>So first define a abstract office type that doesn't knoe about employees: MAB> MAB> package Abs_Office is MAB> MAB> type Abstract_Office is abstract tagged null record; MAB> type Access_Office is access all Abstract_Office'class; MAB> MAB> end Abs_Office; Hmmm... So, from this "abstract" point of view, an Office has no features whatsoever. No components or subprograms (whether publicly or privately visible) that deal with the fact that an Office will point to an Employee, nor even any of the "other stuff" that makes up an Office. (Perhaps you're leaving it open where that "other stuff" could go, but for now I'm going to take what you've written above at face value). Actually, I wouldn't call this an "abstract" view of an Office. Rather, let me define this as an abstract *class* that provides an "opaque" *view* of an Office. MAB>Now we can define the employee type in terms of the above abstract type: MAB> MAB> with Abs_Office; use Abs_Office; MAB> package Emp is MAB> MAB> type Employee is tagged record MAB> Office: Access_Office; MAB> end record; MAB> type Employee_Access is access all Employee'class; MAB> MAB> end Emp; ... I'm going to assume that this Employee type would really have a lot of "other stuff" in it along with this pointer, and that this package spec would also include all the primitive subprograms that define the Employee class, including subprograms that deal with the "other stuff" as well as the association with Office. The trouble here is that even though an Employee now has a pointer to an Office, this pointer only gives the Employee an "opaque" view of an Office. The "public" view of an Office is eventually going to be supplied via a *subclass* of this "opaque" view -- but the Employee class doesn't have access to that "public" view. Although I didn't explicitly say so in my original post, I'm assuming that the Employee class might very well be a full client of the Office class (and vice versa). This means that the bodies of all Employee subprograms ought to have full visibility to the public subprograms of the Office class. But even if Employee isn't *itself* a client of Office, we certainly can assume that the *clients* of Employee *will* be clients of Office as well! Let's say some client of Employee asks an Employee object for its Office. The Employee object will give back one of these "opaque" pointers in response. What can the client do with that pointer? As far as it can see, the Office object being pointed to is totally featureless. Oh, sure, it's possible that this Office object is really some subclass of Abstract_Office that does have useful features, but this information isn't available to the client. The only way the client could get to that information would be to use an unchecked conversion to change this "opaque" pointer into a pointer to the "non-opaque" Office subclass below. (I think folks in the other languages would call this "downcasting".) But this practice breaks type-safety! (Okay, maybe we could endow the abstract class with all the "other stuff", rather than wait until the subclass below. That way, a client would at least have visibility to that "other stuff". But my feeling is that the Office's association with the Employee is just as important a feature as the "other stuff". Maybe the client would want to ask the Office for its associated Employee again later, for some reason. Or maybe a client might want to tell the Office to *change* its Employee--i.e., tell the old one to vamoose and let some new guy occupy the Office! :-) MAB>Now we can define the real office type: MAB> MAB> with Abs_Office; use Abs_Office; MAB> with Emp; use Emp; MAB> package Off is MAB> MAB> type Office is new Abstract_Office with record MAB> Employee: Employee_Access; MAB> end record; MAB> MAB> end Off; At least the full Office class gets visibility to the public interface of the Employee class. But not vice versa. MAB>This of course ignores the private aspects of the C++ example given in the MAB>original article, but these are easly added using private types and/or MAB>private extensions in the above packages. MAB> MAB>Moral: if you need mutualy recursive tagged types with out breaking MAB>encapsulation, then use an abstract type for you forward reference. Moral: An "opaque" abstract class might preserve encapsulation, but it doesn't provide true mutual recursion. MAB> MAB>-- MAB>Mark Biggar MAB>mab@wdl.loral.com -- John Volan -------------------------------------------------------------------------------- -- 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."); -------------------------------------------------------------------------------- ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-09-29 1:46 ` John Volan @ 1994-09-29 13:57 ` Tucker Taft 1994-09-29 17:20 ` Bjarne Stroustrup <9758-26353> 0112760 1994-09-29 18:37 ` John Volan 1994-09-29 18:10 ` R. William Beckwith 1994-10-03 0:33 ` Cyrille Comar 2 siblings, 2 replies; 43+ messages in thread From: Tucker Taft @ 1994-09-29 13:57 UTC (permalink / raw) In article <1994Sep29.014611.20263@swlvx2.msd.ray.com>, John Volan <jgv@swl.msd.ray.com> wrote: > ... >Let's say some client of Employee asks an Employee object for its Office. The >Employee object will give back one of these "opaque" pointers in response. What >can the client do with that pointer? As far as it can see, the Office object >being pointed to is totally featureless. Oh, sure, it's possible that this >Office object is really some subclass of Abstract_Office that does have useful >features, but this information isn't available to the client. The only way the >client could get to that information would be to use an unchecked conversion to >change this "opaque" pointer into a pointer to the "non-opaque" Office subclass >below. (I think folks in the other languages would call this "downcasting".) >But this practice breaks type-safety! Actually, you can "down cast" (aka "narrow") safely in Ada 9X. There is no need to revert to unchecked conversion. If you have (a pointer to) a class-wide type, you can explicitly convert it to (a pointer to) any type "covered" by the class-wide type. A run-time check is performed as appropriate to make sure what you are doing is copacetic. See RM9X-4.6(15, 23, 42, 50);5.0. For example: type T is abstract tagged private; type PT is access all T'Class; ... type T1 is new T with private; type PT1 is access all T1'Class; ... Y : PT; X : PT1 := PT1(Y); -- Run-time check performed, as appropriate ... function To_T1(A : T'Class) return T1'Class is begin return T1'Class(A); -- Run-time check performed, as appropriate end To_T1; This capability of Ada 9X is vaguely related to the "assignment attempt" of Eiffel, and the dynamic_cast of ANSI/ISO C++-to-be, but manages to fit quite nicely into the existing Ada 83 concept of (safe) explicit conversion. Note that Ada 9X also has a way to check whether a given object is in a give class before attempting a conversion, as a generalization of the Ada 83 concept of membership test: if A in T1'Class then ... -- Checks whether "tag" of A indicates -- that it is in class of types rooted at T1. if Y = null or else Y.all in T1'Class then ... -- Checks that PT1(Y) will succeed, before -- attempting it. So using access-to-root-abstract-class-wide-type is a viable and safe approach in Ada 9X, particularly when you "know" there is only one direct derivative of the root abstract type, and you are converting to that. However, putting two mutually recursive types in the same package is the traditional Ada way of solving this problem, and seems preferable to me. You can preserve modularity by making these types abstract, while eliminating the need for explicit type conversions by declaring all of the interesting mutually-recursive primitives in this one package. S. Tucker Taft stt@inmet.com Ada 9X Mapping/Revision Team Intermetrics, Inc. Cambridge, MA 02138 ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-09-29 13:57 ` Tucker Taft @ 1994-09-29 17:20 ` Bjarne Stroustrup <9758-26353> 0112760 1994-09-30 1:38 ` Tucker Taft 1994-09-29 18:37 ` John Volan 1 sibling, 1 reply; 43+ messages in thread From: Bjarne Stroustrup <9758-26353> 0112760 @ 1994-09-29 17:20 UTC (permalink / raw) stt@dsd.camb.inmet.com (Tucker Taft) writes > This capability of Ada 9X is vaguely related to the "assignment attempt" > of Eiffel, and the dynamic_cast of ANSI/ISO C++-to-be, Ah. Hmm. I have noted a tendency to treat newer C++ features as if they were some sort of science fiction. Features such as dynamic_cast are currently available in widely-used commercial implementations of C++ as well as being part of the emerging standard. I think it would be best to leave out phrases such as ``ANSI/ISO C++-to-be'' as possibly prejudicial. Alternatively, one could start a flamewar by trying to be fair by applying matching adjectives to Ada9X and Eiffel. - Bjarne ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-09-29 17:20 ` Bjarne Stroustrup <9758-26353> 0112760 @ 1994-09-30 1:38 ` Tucker Taft 1994-09-30 12:33 ` Bjarne Stroustrup <9758-26353> 0112760 0 siblings, 1 reply; 43+ messages in thread From: Tucker Taft @ 1994-09-30 1:38 UTC (permalink / raw) In article <CwwIuJ.1ny@alice.att.com>, Bjarne Stroustrup <9758-26353> 0112760 <9758-26353> wrote: > >stt@dsd.camb.inmet.com (Tucker Taft) writes > > > This capability of Ada 9X is vaguely related to the "assignment attempt" > > of Eiffel, and the dynamic_cast of ANSI/ISO C++-to-be, > >Ah. Hmm. I have noted a tendency to treat newer C++ features as if they were >some sort of science fiction. Features such as dynamic_cast are currently >available in widely-used commercial implementations of C++ as well as being >part of the emerging standard. > >I think it would be best to leave out phrases such as ``ANSI/ISO C++-to-be'' >as possibly prejudicial. Alternatively, one could start a flamewar by trying >to be fair by applying matching adjectives to Ada9X and Eiffel. Sorry about that. It seemed wrong to write ANSI/ISO C++; is there a version number like 3.0 or 4.0 that identifies the set of features expected for ISO C++ (and is already in some current C++ compilers)? I just wanted those non-C++ folks out there to know that dynamic_cast is a feature that is anticipated in the ANSI/ISO C++, and might not be familiar to those of us who have only read older C++ reference manuals. > - Bjarne -Tuck ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-09-30 1:38 ` Tucker Taft @ 1994-09-30 12:33 ` Bjarne Stroustrup <9758-26353> 0112760 0 siblings, 0 replies; 43+ messages in thread From: Bjarne Stroustrup <9758-26353> 0112760 @ 1994-09-30 12:33 UTC (permalink / raw) stt@dsd.camb.inmet.com (Tucker Taft) writes > Bjarne Stroustrup <9758-26353> 0112760 <9758-26353> wrote: > > > >stt@dsd.camb.inmet.com (Tucker Taft) writes > > > > > This capability of Ada 9X is vaguely related to the "assignment attempt" > > > of Eiffel, and the dynamic_cast of ANSI/ISO C++-to-be, > > > >Ah. Hmm. I have noted a tendency to treat newer C++ features as if they were > >some sort of science fiction. Features such as dynamic_cast are currently > >available in widely-used commercial implementations of C++ as well as being > >part of the emerging standard. > > > >I think it would be best to leave out phrases such as ``ANSI/ISO C++-to-be'' > >as possibly prejudicial. Alternatively, one could start a flamewar by trying > >to be fair by applying matching adjectives to Ada9X and Eiffel. > > Sorry about that. It seemed wrong to write ANSI/ISO C++; is there a version > number like 3.0 or 4.0 that identifies the set of features expected for > ISO C++ (and is already in some current C++ compilers)? I just wanted those > non-C++ folks out there to know that dynamic_cast is a feature that > is anticipated in the ANSI/ISO C++, and might not be familiar to > those of us who have only read older C++ reference manuals. Yes. It is hard to know exactly how to refer to these things. I generally use a phrase like ``in the upcoming ANSI/ISO standard'' when I want specifically to refer to standards stuff. I tend to refer to features introduced over the last 3 or so years as ``new features'' and for each try to point out where it has been implemented and whether it has been made available commercially. I guess that is too elaborate, and I shouldn't have been grumpy about your posting. Sorry. It is indeed a pity that there is no generally accepted numbering, but with a dozen or so implementors out there it just doesn't seem feasible to refer to C++ with all the ANSI/ISO features as, say, C++ v5. The most current generally accessible source is my ``The Design and Evolution of C++'' book. - Bjarne ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-09-29 13:57 ` Tucker Taft 1994-09-29 17:20 ` Bjarne Stroustrup <9758-26353> 0112760 @ 1994-09-29 18:37 ` John Volan 1994-09-29 19:34 ` David Weller 1994-09-30 1:47 ` Tucker Taft 1 sibling, 2 replies; 43+ messages in thread From: John Volan @ 1994-09-29 18:37 UTC (permalink / raw) STT = stt@dsd.camb.inmet.com (Tucker Taft) writes: STT>In article <1994Sep29.014611.20263@swlvx2.msd.ray.com>, STT>John Volan <jgv@swl.msd.ray.com> wrote: [snip my own drivel claiming you need unsafe Unchecked_Conversion to downcast] STT>Actually, you can "down cast" (aka "narrow") safely in Ada 9X. STT>There is no need to revert to unchecked conversion. STT>If you have (a pointer to) a class-wide type, you can explicitly convert STT>it to (a pointer to) any type "covered" by the class-wide type. STT>A run-time check is performed as appropriate to make sure what you are STT>doing is copacetic. See RM9X-4.6(15, 23, 42, 50);5.0. [snip good example showing safe downcasting] STT>This capability of Ada 9X is vaguely related to the "assignment attempt" STT>of Eiffel, and the dynamic_cast of ANSI/ISO C++-to-be, but manages STT>to fit quite nicely into the existing Ada 83 concept of (safe) explicit STT>conversion. STT> STT>Note that Ada 9X also has a way to check whether a given object STT>is in a give class before attempting a conversion, as a generalization STT>of the Ada 83 concept of membership test: STT> STT> if A in T1'Class then ... -- Checks whether "tag" of A indicates STT> -- that it is in class of types rooted at T1. STT> STT> if Y = null or else Y.all in T1'Class then ... STT> -- Checks that PT1(Y) will succeed, before STT> -- attempting it. STT> Er, I think you meant: if Y /= null and then Y.all in T1'Class then ... Or maybe, if you wanted to cover the contrary case first: if Y = null or else Y.all not in T1'Class then ... STT>So using access-to-root-abstract-class-wide-type is a viable and safe STT>approach in Ada 9X, particularly when you "know" there is only STT>one direct derivative of the root abstract type, and you are STT>converting to that. Ahh, the light begins to dawn. I could kick myself -- saw this once in the RM9X and totally forgot about it. Should have guessed it was there anyway, on first principles: "If there's something reasonable you want to do, more than likely there's a SAFE way to do it in Ada 9X." (An old saying I just made up.) So -- this now gives me a safe, systematic technique for coding up object classes that are each fully encapsulated in their own packages, yet allowing any degree of mutual dependency among them. I'll outline this technique at the end of this article, but first ... STT>However, putting two mutually recursive types in the same STT>package is the traditional Ada way of solving this problem, and STT>seems preferable to me. You can preserve modularity by STT>making these types abstract, while eliminating the need for STT>explicit type conversions by declaring all of the interesting STT>mutually-recursive primitives in this one package. I won't argue with your personal preference, but let me point out that this "traditional" technique will have a difficult time scaling up. For the sake of discussion, I deliberately chose a very simple situation involving only two classes, mutually related in a simple one-to-one association. But, without loss of generality, I was looking for a solution that could be systematically applied to systems comprising many, many object classes, each one participating in mutually recursive associations with possibly many, many other classes, with any degree of cardinality (one-to-one, one-to-many, many-to-many, etc). Unfortunately, the "traditional" technique you suggest (and which I outlined in my original post) requires breaking encapsulation between any pair of classes that happen to be mutually recursive -- and this effect is transitive! The end result would be one huge, monolithic package containing *all* (or perhaps most) of the classes in a system. I don't think I need to elaborate on the detrimental effect that would have on a large, long-term project involving continously-changing requirements. The whole point to packaging was to avoid monolithic coding techniques. (There you go again, John, preaching to the choir -- nay, preaching to the *minister*! :-) I am an unabashed fanatic of the 1 Package = 1 Type = 1 Class approach, and making that approach workable was the whole point of this thread. In part, I'm influenced by Karl Lieberherr's work on the Demeter method, and his so-called "Law of Demeter". This law more or less asserts that each class in an object-oriented system should be completely encapsulated and self-administering. A class should only "know about" the classes it is immediately associated with, and only "know about" them in terms of their own encapsulated interfaces. Moreover, this encapsulation should never be broken down, even if some functionality of the system requires propagating an algorithm across many classes of object. Instead, the algorithm should be realized by a collaborative interaction among the public subprograms of the various classes. I think this is the essence of the whole object-oriented paradigm. STT>S. Tucker Taft stt@inmet.com STT>Ada 9X Mapping/Revision Team STT>Intermetrics, Inc. STT>Cambridge, MA 02138 Well, thanks to you and to everyone else who contributed to this thread! -- John Volan -------------------------------------------------------------------------------- -- 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."); -------------------------------------------------------------------------------- As promised, here's an outline of my coding strategy: 1. "Forward declare" the classes by writing a "root" package for each, containing a featureless abstract tagged type and an accompanying access-to-classwide-type. This establishes a way of manipulating the *identities* of objects without having to know their *structure* in advance: package Employee is type Object is abstract tagged limited null record; type Pointer is access all Object'Class; None : constant Pointer := null; end Employee; package Office is type Object is abstract tagged limited null record; type Pointer is access all Object'Class; None : constant Pointer := null; end Office; ... root packages for other object classes in the problem domain 2. Flesh out the "actual" abstraction for each class, in a package spec that is a child of its corresponding "root" package. Derive an "actual" concrete type from the root abstract tagged type. Wherever the class in question visibly supports an association with another class, import the other class's root package via a "with" clause, and use the corresponding pointer-to-abstract type: (NOTE: Where two classes are mutually recursive, *both* can provide primitive subprograms to support the association between them. Where necessary, these subprograms can be ... mutually recursive!) ---------------------------------------------------------------------- with Office; with ... ; -- other stuff, possibly including other class root packages package Employee.Actual is type Object is new Employee.Object with private; type Pointer is access all Object'Class; None : constant Pointer := null; ... -- subprograms involving other stuff -- Public support for association with Office: function Office_Occupied_By ( The_Employee : in Employee.Actual.Object'Class ) return Office.Pointer; procedure Occupy_Office ( The_Employee : in out Employee.Actual.Object'Class; New_Office : in Office.Pointer ); -- Mutually recursive with Office.Actual.Accomodate_Employee procedure Vacate_Office ( The_Employee : in out Employee.Actual.Object'Class ); -- Mutually recursive with Office.Actual.Evict_Employee ... private type Object is new Employee.Object with record ... -- components involving other stuff Its_Occupied_Office : Office.Pointer; end record; end Employee.Actual; ---------------------------------------------------------------------- with Employee; with ... ; -- other stuff, possibly including other class root packages package Office.Actual is type Object is new Office.Object with private; type Pointer is access all Object'Class; None : constant Pointer := null; ... -- subprograms involving other stuff -- Public support for association with Employee: function Employee_Occupying ( The_Office : in Office.Actual.Object'Class ) return Employee.Pointer; procedure Accomodate_Employee ( The_Office : in out Office.Actual.Object'Class; New_Employee : in Employee.Pointer ); -- Mutually recursive with Employee.Actual.Occupy_Office procedure Evict_Employee ( The_Office : in out Office.Actual.Object'Class ); -- Mutually recursive with Employee.Actual.Vacate_Office ... private type Object is new Office.Object with record ... -- components involving other stuff Its_Occupying_Employee : Employee.Pointer; end record; end Office.Actual; ---------------------------------------------------------------------- ... "actual" packages for other classes in the problem domain 3. Implement the "actual" abstraction for each class, within the corresponding child package body. Wherever the class in question needs access to the full abstraction of some other class in order to support an association with that class, import the other class's "actual" package via a "with" clause in the package body. Then, wherever the subprograms must interact with a specific other-class object, downcast the access-to-abstract-root-type into an access-to-actual-type. (No explicit validity check is needed -- we'll take it as an assertion of our design that all designated objects will be of the correct actual types, and rely on Ada 9X's implicit checks to detect any inadvertent coding errors. The run-time cost of these implicit checks can, if necessary, be eliminated from the final fielded system by using an appropriate suppressing pragma, once full testing is complete): ---------------------------------------------------------------------- with Office.Actual; with ... ; -- actual packages for other classes package body Employee.Actual is ... -- subprogram bodies involving other stuff -- Public support for association with Office: function Office_Occupied_By ( The_Employee : in Employee.Actual.Object'Class ) return Office.Pointer is begin return The_Employee.Its_Occupied_Office; end Office_Occupied_By; procedure Occupy_Office ( The_Employee : in out Employee.Actual.Object'Class; New_Office : in Office.Pointer ) is use type Office.Pointer; begin if New_Office /= Office.None and then New_Office /= The_Employee.Its_Occupied_Office then Employee.Actual.Vacate_Office (The_Employee); The_Employee.Its_Occupied_Office := New_Office; Office.Actual.Accomodate_Employee ( The_Office => Office.Actual.Pointer(New_Office).all, New_Employee => The_Employee'Access ); end if; end Occupy_Office; procedure Vacate_Office ( The_Employee : in out Employee.Actual.Object'Class ) is Old_Office : constant Office.Pointer := The_Employee.Its_Occupied_Office; use type Office.Pointer; begin if Old_Office /= Office.None then The_Employee.Its_Occupied_Office := Office.None; Office.Actual.Evict_Employee ( The_Office => Office.Actual.Pointer(Old_Office).all ); end if; end Vacate_Office; ... end Employee.Actual; ---------------------------------------------------------------------- with Employee.Actual; with ... ; -- actual packages for other classes package body Office.Actual is ... -- subprogram bodies involving other stuff -- Public support for association with Employee: function Employee_Occupying ( The_Office : in Office.Actual.Object'Class ) return Employee.Pointer is begin return The_Office.Its_Occupying_Employee; end Employee_Occupying; procedure Accomodate_Employee ( The_Office : in out Office.Actual.Object'Class; New_Employee : in Employee.Pointer ) is use type Employee.Pointer; begin if New_Employee /= Employee.None and then New_Employee /= The_Office.Its_Occupying_Employee then Office.Actual.Evict_Employee (The_Office); The_Office.Its_Occupying_Employee := New_Employee; Employee.Actual.Occupy_Office ( The_Employee => Employee.Actual.Pointer(New_Employee).all, New_Office => The_Office'Access ); end if; end Accomodate_Employee; procedure Vacate_Employee ( The_Office : in out Office.Actual.Object'Class ) is Old_Employee : constant Employee.Pointer := The_Office.Its_Occupying_Employee; use type Employee.Pointer; begin if Old_Employee /= Employee.None then The_Office.Its_Occupying_Employee := Employee.None; Employee.Actual.Vacate_Office ( The_Employee => Employee.Actual.Pointer(Old_Employee).all ); end if; end Vacate_Employee; ... end Office.Actual; ---------------------------------------------------------------------- ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-09-29 18:37 ` John Volan @ 1994-09-29 19:34 ` David Weller 1994-09-30 22:13 ` John Volan 1994-09-30 1:47 ` Tucker Taft 1 sibling, 1 reply; 43+ messages in thread From: David Weller @ 1994-09-29 19:34 UTC (permalink / raw) In article <1994Sep29.183749.7489@swlvx2.msd.ray.com>, John Volan <jgv@swl.msd.ray.com> wrote: >I am an unabashed fanatic of the 1 Package = 1 Type = 1 Class approach, and >making that approach workable was the whole point of this thread. Well, to each their own. Let's just say I'm on the other end of that spectrum (no, more to the point, I simply don't advocate class=package, although there's many times that works). In my years of developing software, I have yet to be swayed by arguments of "dual associations" needing to be represented in classes. I can see such a need in databases, but I've just not encountered it in programming. That's not saying they don't come up during analysis, but that their final representation generally requires less gymnastics than some of the interesting examples that have been discussed. I guess I can just be counted out of the class=module camp :-/ > In part, I'm >influenced by Karl Lieberherr's work on the Demeter method, and his so-called >"Law of Demeter". This law more or less asserts that each class in an >object-oriented system should be completely encapsulated and self-administering. >A class should only "know about" the classes it is immediately associated with, >and only "know about" them in terms of their own encapsulated interfaces. >Moreover, this encapsulation should never be broken down, even if some >functionality of the system requires propagating an algorithm across many >classes of object. Instead, the algorithm should be realized by a collaborative >interaction among the public subprograms of the various classes. I think this >is the essence of the whole object-oriented paradigm. > Is it appropriate at this point to shudder uncontrollably? :-) >Well, thanks to you and to everyone else who contributed to this thread! > In spite of my disagreement, I think you brought up an interesting topic. -- Proud (and vocal) member of Team Ada! (and Team OS/2) ||This is not your Ada -- Very Cool. Doesn't Suck. || father's Ada For all sorts of interesting Ada tidbits, run the command: ||________________ "finger dweller@starbase.neosoft.com | more" (or e-mail with "finger" as subj.) ObNitPick: Spelling Ada as ADA is like spelling C++ as CPLUSPLUS. :-) ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-09-29 19:34 ` David Weller @ 1994-09-30 22:13 ` John Volan 1994-10-02 3:31 ` Andrew Lees 0 siblings, 1 reply; 43+ messages in thread From: John Volan @ 1994-09-30 22:13 UTC (permalink / raw) Hmm. I guess when I started this thread, I didn't really make it clear exactly what I was asking for. Let me see if I can explain it better, taking the expression "a picture is worth a thousand words" to heart (but keeping the thousand words, too :-) (And there's a side-issue or two in this...) THE IDENTITY/INTERFACE/IMPLEMENTATION TRICHOTOMY ------------------------------------------------ If I were to draw a Rumbaugh-oid Object Model depicting a requirements analysis of the original problem I posed, it might look something like this (highly, highly, schematized): +----+ A1,2 +----+ | C1 |--------| C2 | +----+ +----+ Two classes, related to each other by a single one-to-one association. Additionally, I stated that the functional requirements of the problem demanded that this association be traversable in both directions. So, as one possible design to satisfy these requirements, I proposed making both classes "responsible" for the association, by having them point to each other: +----+ +----+ | C1 | | C2 | | o2--------->| | | |<---------o1 | +----+ +----+ Not the only possible solution, but a reasonable one. This solution entails a cyclic dependency between the two classes. I very much wanted to preserve the notion of 1 Package = 1 Class, but cyclic dependency presents a dilemma in Ada. You cannot, of course, have two packages whose interfaces (specifications) both import ("with") each other. You can have their *implementations* (package bodies) import each other's interfaces, no problem, but the interfaces themselves can't be mutually dependent. But I realized that, in terms of object-oriented classes, it wasn't really a matter of having *interfaces* import each other. To define the interface of one class you don't *really* need to know the whole interface of the other class. All you really need to establish is that the other class *exists*, and that you can express the *identities* (pointers, let's say) of objects of that other class. In other words, you only need to "forward declare" the other class. Once that's done, you can get on with declaring the type and the subprogram specs for your class. It's only when you get around to the *implementation* (package body) of your class that you really need visibility to the interface of the other class. I really see this as a three-tier system of dependencies comprising class identity, class interface, and class implementation. I call this the "Identity/Interface/Implementation Trichotomy" (is there already another term for this out there...?) +-------------------+ +-------------------+ | C1 Class Identity |<--_ _-->| C2 Class Identity | +-------------------+ \ / +-------------------+ X +-------------------+ / \ +-------------------+ | C1 Class |___/ \___| C2 Class | | Interface |<--_ _-->| Interface | +-------------------+ \ / +-------------------+ X +-------------------+ / \ +-------------------+ | C1 |___/ \___| C2 | | Class | | Class | | Implementation | | Implementation | +-------------------+ +-------------------+ (Where -----> indicates dependency) So my original question was: How can you do this with Ada 9X packages and tagged types? Of course, Ada packaging directly supports only two tiers of this trichotomy: interface and implementation. However, using abstract tagged types and safe downcasting, I was able to come up with a scheme to simulate this three-tier idea, which I outlined it a couple posts ago. (It's not without its costs, mind you. Maybe there's a better way to do this. If anyone can come up with one, I'd really appreciate it!) (Interestingly enough, I think Eiffel implicitly supports this trichotomy. At least, as far as I can tell (not actually having programmed in Eiffel) it looks like you can have two class texts that mention each other in a way that generates a cyclic dependency. Anyone out there in Eiffel-land care to comment?) Now, I grant you, if it were only a matter of a couple of classes and a single association, it would be perfectly reasonable to argue that these classes are so intimately related that they really form a single irreducible abstraction. If this were all there were too it, then, yes, you might as well couple both of these classes within a single package, and forget about decoupling them from each other. Other folks on this thread have proposed various schemes, all very elegant and workable, that seem to me to share this common assumption. Even where great pains are taken to reduce the coupling to an abstraction, and hide it away in private parts, the coupling is still there. Well, maybe that's okay. But what if two classes and one association wasn't all there was to it? What if, instead of this: +----+ A1,2 +----+ | C1 |--------| C2 | +----+ +----+ the requirements picture was really more like *this*: ` ' ` ' \ / +----+ +----+ - - --| C7 |-----| C8 | . ' +----+ +----+ +----+ ' ` . ' ' - - ---| C6 |__ | / \ ' ` . ' ' +----+ \ | ____/ | / \ | / / \ | / | / \ | / / +----+ A5,1 +----+ A1,2 +----+ A2,3 +----+ A3,4 +----+ - - --| C5 |--------| C1 |--------| C2 |--------| C3 |--------| C4 |-- - - +----+ +----+\ +----+ +----+ +----+ \ / \ \_____/__ | | \ / \ / \ . . \+----+ \+----+ +----+ . . - - --| C9 |-------| C10|----| C11|--- - - - +----+ +----+ +----+ . | / \ | . ' ` . . ' ` . (Imagine this semantic network spreading out beyond the edges of your screen :-) I gave up trying to label every association, but I think you get the picture.) Let's assume that the nature of the application requires being able to traverse *all* these binary associations in either direction. What do we do now? What if there are dozens of classes in the model? Hundreds? Do we say: "Well, then, if these classes are *all* so tightly related, then we must declare *all* of them in a single package." And, based on the common software-management practice of 1 Package = 1 Engineer, do we also say, "Our requirements model is bound to change continuously over the course of this project. Therefore, we shall have the maintainer of this very important package (upon which all else depends) work very hard to keep it up-to-date. Very, _very_ hard. Moreover, let's also keep him busy making sure all the other packages in the system recompile successfully every time he changes his package." :-) (Note to Dave Weller: Is it appropriate at this point for me to shudder uncontrollably? ;-) Okay, you might argue John's just being paranoid, real-world applications are never all *that* complex. But perhaps someday they will be. Perhaps some already are, but they're just not being done in Ada. You might argue that even if the semantic network for a problem domain were really that complex, you shouldn't have to worry that much about mutual dependency because real-world applications never demand all *that* much bi-directional traversal. But perhaps someday they will. Perhaps some already do, but they're just not being done in Ada. Well, I for one want to be able to tackle them, without a lot of pain, in _Ada_! (Gosh, I began to sound like GA there for them moment ... no offense, GA! :-) -------------------------------------------------------------------------------- IN THE "ON THE OTHER HAND" DEPARTMENT ------------------------------------- Alright, as I already said, the design I proposed is not the only possible one. Instead of making either or both of the two classes responsible for the association: +----+ +----+ | C1 | | C2 | | o2--------->| | | |<---------o1 | +----+ +----+ we could place the responsibility for the association entirely within a third component: +--------+ | A(1,2) | +----+ | | +----+ | C1 |<-----o1 o2----->| C2 | +----+ | | +----+ +--------+ This "association" package would probably contain some kind of dual hash table, mapping C1-pointers to C2-pointers, and vice versa. With good modern hashing techniques, lookups can be quite efficient. Not as fast as a single pointer dereference, but "good enough" (O(1) time complexity, amortized). The nice thing about this solution is that it totally decouples the classes from having to know anything about the association. Adding new associations to a model has very little ripple effect, if any. Moreover, this kind of thing is a prime candidate for writing generically. Given the nature of my premise (lots of classes, lots of associations) such a generic would get a *lot* of reuse. (Thanks to Matt Kennel for reminding me of this scheme.) Ultimately, this seems to be an excellent way to go -- but does anybody see any problems with this? How about if an object instance (of any class) needs to be deleted? If the object itself has no knowledge of the associations it participates in, who is going to make sure that all of these "association tables" will be notified of that particular object's disappearance? More generally, consider any event, affecting an object's state, that also requires traversing its associations, to propagate that event to other objects. If the object itself is not going to be responsible for doing that ... then who, precisely, will? Hmm ... maybe an object class *can* know about its associations after all: What if the body of the class package imported the association package ...? Hmmm ... got to give this more thought ... -- John Volan -------------------------------------------------------------------------------- -- 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."); -------------------------------------------------------------------------------- ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-09-30 22:13 ` John Volan @ 1994-10-02 3:31 ` Andrew Lees 0 siblings, 0 replies; 43+ messages in thread From: Andrew Lees @ 1994-10-02 3:31 UTC (permalink / raw) This discussion has led me to wonder if the source of the problem is a clash between the desire to have "one package = one type" and the essential structure of the problem being discussed. This may appear a little meta-physical, but I wonder if the relationship between employee and office is not _core_ to the definition of employee, and similarly for office (at least in the context of this problem ). If this is the case, then having the definitions of office and employee separate is itself part of the problem - at an essential level they are not separate. A possible solution recognising the intertwined nature of the two types may be to have the office and employee identifiers (= access types) defined in a single package as private types with their structure left to the body. This common package has nothing else, or may have common employee-office operations. The separate employee and office operations are then defined in child packages, which can re-export the associated indentifier type for those clients who are interested solely in "employee" or "office" things, and do not wish to concern themslves about the combined aspects. I'm not sure if this is directly related to what was intended, but there are certainly cases where this type of solution is required. Andy Lees. ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-09-29 18:37 ` John Volan 1994-09-29 19:34 ` David Weller @ 1994-09-30 1:47 ` Tucker Taft 1994-09-30 13:30 ` John Volan 1 sibling, 1 reply; 43+ messages in thread From: Tucker Taft @ 1994-09-30 1:47 UTC (permalink / raw) In article <1994Sep29.183749.7489@swlvx2.msd.ray.com>, John Volan <jgv@swl.msd.ray.com> wrote: >STT> if Y = null or else Y.all in T1'Class then ... >STT> -- Checks that PT1(Y) will succeed, before >STT> -- attempting it. >STT> > >Er, I think you meant: > > if Y /= null and then Y.all in T1'Class then ... > >Or maybe, if you wanted to cover the contrary case first: > > if Y = null or else Y.all not in T1'Class then ... No, actally I meant what I wrote ;-). If Y = null, then the conversion always succeeds, since all (named) access (sub)types include null as a legal value of the (sub)type. >I am an unabashed fanatic of the 1 Package = 1 Type = 1 Class approach, and >making that approach workable was the whole point of this thread. I'm not. In my view packaging is an additional dimension of flexibility that should be used (but not abused ;-). Packages are for grouping logically related entities, and it is fine if a single package includes two closely linked types. But I understand some of the advantages of the other approach as well... Vive la difference (but not on *my* project ;-). >-- John Volan >-- Company => "Raytheon Missile Systems Division", >-- E_Mail_Address => "jgv@swl.msd.ray.com", -Tucker Taft stt@inmet.com Intermetrics, Inc. ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-09-30 1:47 ` Tucker Taft @ 1994-09-30 13:30 ` John Volan 0 siblings, 0 replies; 43+ messages in thread From: John Volan @ 1994-09-30 13:30 UTC (permalink / raw) stt@dsd.camb.inmet.com (Tucker Taft) writes: >No, actally I meant what I wrote ;-). If Y = null, then the conversion >always succeeds, since all (named) access (sub)types include null >as a legal value of the (sub)type. I stand corrected. (Wow, showing your ignorance again, John, huh? :-) >-Tucker Taft stt@inmet.com >Intermetrics, Inc. -- John Volan -------------------------------------------------------------------------------- -- 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."); -------------------------------------------------------------------------------- ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-09-29 1:46 ` John Volan 1994-09-29 13:57 ` Tucker Taft @ 1994-09-29 18:10 ` R. William Beckwith 1994-10-03 0:33 ` Cyrille Comar 2 siblings, 0 replies; 43+ messages in thread From: R. William Beckwith @ 1994-09-29 18:10 UTC (permalink / raw) John Volan (jgv@swl.msd.ray.com) wrote: : MAB = mab@dst17.wdl.loral.com (Mark A Biggar) writes: snip : ... The only way the : client could get to that information would be to use an unchecked conversion to : change this "opaque" pointer into a pointer to the "non-opaque" Office subclass : below. (I think folks in the other languages would call this "downcasting".) : But this practice breaks type-safety! Actually the cool term is now `narrowing'. Narrowing the access type from access all Abstract_Office'class to access all Office'class does not break type saftey. Since Abstract_Office is abstract, there can be no objects of type Abstract_Office. Thus, Abstract_Office'class is always safely convertable to Office'class. This issue (we call it the "withing problem") is addressed in the CORBA IDL to Ada 9X mapping document. This mapping provides a nice solution to this issue that is very similiar to Mark's. The CORBA IDL to C++ mapping document defines C++ classes that are much more complex and cumbersome (IMHAARO) than non-CORBA C++ classes. I think the IDL to Ada 9X mapping provides a nice way to code Ada 9X even if you're not using CORBA. ... Bill -- e-mail: Bill.Beckwith@ois.com | Team Ada Objective Interface Systems, Inc. | dist, full O-O 1895 Preston White Drive, Suite 250 | multithreading Reston, VA 22091-5448 U.S.A. | built in ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-09-29 1:46 ` John Volan 1994-09-29 13:57 ` Tucker Taft 1994-09-29 18:10 ` R. William Beckwith @ 1994-10-03 0:33 ` Cyrille Comar 2 siblings, 0 replies; 43+ messages in thread From: Cyrille Comar @ 1994-10-03 0:33 UTC (permalink / raw) jgv@swl.msd.ray.com (John Volan) writes: : can the client do with that pointer? As far as it can see, the Office object : being pointed to is totally featureless. Oh, sure, it's possible that this :Office object is really some subclass of Abstract_Office that does have useful : features, but this information isn't available to the client.The only way the : client could get to that information would be to use an unchecked conversion : to change this "opaque" pointer into a pointer to the "non-opaque" Office : subclass below. (I think folks in the other languages would call this : "downcasting".) But this practice breaks type-safety! As far as I know, downcasting is allowed in Ada9x and is type-safe (a check is performed at run-time) by using a regular conversion not an unchecked-conversion, so Mark's solution seems very close to what you need. -- ------------------------------------------------------------------------ Cyrille Comar, E-mail: comar@cs.nyu.edu Gnat Project US phone: (212) 998-3489 ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-09-27 16:52 Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) John Volan 1994-09-27 18:48 ` Mark A Biggar @ 1994-09-28 14:01 ` Norman H. Cohen 1994-09-29 2:12 ` John Volan 1994-09-29 9:48 ` Magnus Kempe 1 sibling, 2 replies; 43+ messages in thread From: Norman H. Cohen @ 1994-09-28 14:01 UTC (permalink / raw) In article <1994Sep27.165203.9192@swlvx2.msd.ray.com>, jgv@swl.msd.ray.com (John Volan) writes: |> 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? (Actually, it's not a dispatching operation for either, since you didn't use access parameters.) |> |> 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; One variation on this is to declare recursive types meant to serve as the parents of types Employee and Office, but to provide no operations for these recursive types. Then, in child packages, declare Employee and Office themselves as derived types and declare primitive operations in those child packages: with ... ; -- other stuff needed by Employee with ... ; -- other stuff needed by Office package Employees_And_Offices is type Employee_Parent is tagged limited private; type Employee_Pointer is access all Employee'Class; type Office_Parent is tagged limited private; type Office_Pointer is access all Office'Class; private type Employee_Parent is tagged record ... -- various components involving that "other stuff" Its_Occupied_Office : Office_Pointer; ... end record; type Office_Parent is tagged record ... -- various components involving that "other stuff" Its_Occupying_Employee : Employee_Pointer; ... end record; end Employees_And_Offices; package Employees_And_Offices.Employees is type Employee is tagged private; No_Employee : constant Employee_Pointer := null; ... -- Employee subprograms involving that "other stuff" private type Employee is new Employee_Parent with null record; end Employees_And_Offices.Employees; package Employees_And_Offices.Offices is type Office is tagged private; No_Office : constant Office_Pointer := null; ... -- Office subprograms involving that "other stuff" private type Office is new Office_Parent with null record; end Employees_And_Offices.Offices; with Employees_And_Offices.Employees, Employees_And_Offices.Offices; package Employees_And_Offices.Common is 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); end Employees_And_Offices.Common; The operations in the Common child are not primitive operations of either type. (Two types cannot possibly both have primitive operations with parameters of the other type, because the declarations of the packages declaring those types would have to name each other in with clauses. If you don't mind breaking the symmetry, you can make the Offices child depend on the Employees child and put ALL the common operations in the Offices child as primitive operations of type Office, or vice versa.) The bodies of all three child packages have complete visibility into the mutually recursive data structure declared in Employees_And_Offices. Since Office_Pointer is declared to designate Office_Parent'Class rather than Office_Parent, there is no problem with Office_Pointer values designating Office objects, and similarly for the Employee-related types. -- Norman H. Cohen ncohen@watson.ibm.com ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-09-28 14:01 ` Norman H. Cohen @ 1994-09-29 2:12 ` John Volan 1994-09-29 14:01 ` Tucker Taft 1994-09-29 18:37 ` Norman H. Cohen 1994-09-29 9:48 ` Magnus Kempe 1 sibling, 2 replies; 43+ messages in thread From: John Volan @ 1994-09-29 2:12 UTC (permalink / raw) NHC = ncohen@watson.ibm.com (Norman H. Cohen) writes: NHC>One variation on this is to declare recursive types meant to serve as the NHC>parents of types Employee and Office, but to provide no operations for NHC>these recursive types. Then, in child packages, declare Employee and NHC>Office themselves as derived types and declare primitive operations in NHC>those child packages: NHC> NHC> with ... ; -- other stuff needed by Employee NHC> with ... ; -- other stuff needed by Office NHC> NHC> package Employees_And_Offices is NHC> NHC> type Employee_Parent is tagged limited private; NHC> type Employee_Pointer is access all Employee'Class; NHC> NHC> type Office_Parent is tagged limited private; NHC> type Office_Pointer is access all Office'Class; This solution suffers from the same problem as Mark Biggar's suggestion: These access types only give clients an "opaque" view of the designated objects. The useful primitive operations for these objects won't be declared until we get to the concrete *subclasses* declared later. So a client would have to resort to using a non-typesafe Unchecked_Conversion to "downcast" one of these pointers into designating the corresponding concrete subclass. Another issue is whether the concrete subclasses declared below are going to be the *only* subclasses of these abstract classes. Certainly that's the original intent -- but will a maintainer pay any attention? ;-) NHC> private NHC> NHC> type Employee_Parent is tagged NHC> record NHC> ... -- various components involving that "other stuff" NHC> Its_Occupied_Office : Office_Pointer; NHC> ... NHC> end record; NHC> NHC> type Office_Parent is tagged NHC> record NHC> ... -- various components involving that "other stuff" NHC> Its_Occupying_Employee : Employee_Pointer; NHC> ... NHC> end record; NHC> NHC> end Employees_And_Offices; Another problem here is that this doesn't really solve the original puzzle I posed: How do you avoid breaking encapsulation *between* these two classes? These type declarations are private, but since the packages below are all children of this parent package, they have complete visibility to the private part of the parent. So an Employee subprogram still has license to ignore the public primitives of the Office class and "diddle" with the Office's private components directly. (And vice versa.) NHC> package Employees_And_Offices.Employees is NHC> type Employee is tagged private; NHC> No_Employee : constant Employee_Pointer := null; NHC> ... -- Employee subprograms involving that "other stuff" NHC> private NHC> type Employee is new Employee_Parent with null record; NHC> end Employees_And_Offices.Employees; NHC> NHC> package Employees_And_Offices.Offices is NHC> type Office is tagged private; NHC> No_Office : constant Office_Pointer := null; NHC> ... -- Office subprograms involving that "other stuff" NHC> private NHC> type Office is new Office_Parent with null record; NHC> end Employees_And_Offices.Offices; NHC> NHC> NHC> with Employees_And_Offices.Employees, Employees_And_Offices.Offices; NHC> NHC> package Employees_And_Offices.Common is NHC> NHC> function Office_Occupied_By (The_Employee : in Employee) NHC> return Office_Pointer; NHC> NHC> function Employee_Occupying (The_Office : in Office) NHC> return Employee_Pointer; NHC> NHC> procedure Occupy (The_Office : in Office_Pointer; NHC> The_Employee : in Employee_Pointer); NHC> NHC> end Employees_And_Offices.Common; NHC> NHC>The operations in the Common child are not primitive operations of either NHC>type. (Two types cannot possibly both have primitive operations with NHC>parameters of the other type, because the declarations of the packages NHC>declaring those types would have to name each other in with clauses. If NHC>you don't mind breaking the symmetry, you can make the Offices child NHC>depend on the Employees child and put ALL the common operations in the NHC>Offices child as primitive operations of type Office, or vice versa.) NHC>The bodies of all three child packages have complete visibility into the NHC>mutually recursive data structure declared in Employees_And_Offices. Since NHC>Office_Pointer is declared to designate Office_Parent'Class rather than NHC>Office_Parent, there is no problem with Office_Pointer values designating NHC>Office objects, and similarly for the Employee-related types. Ah, but there *is* a problem: An Office_Pointer does't manifestly designate an *Office* object. Rather, it designates an *Office_Parent view* of what *might* be an Office object. This view, being "opaque", is not useful to a client. NHC> NHC>-- NHC>Norman H. Cohen ncohen@watson.ibm.com Well, this doesn't really work, but I think we're starting to get closer ... Thanks anyway! -- John Volan -------------------------------------------------------------------------------- -- 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."); -------------------------------------------------------------------------------- ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-09-29 2:12 ` John Volan @ 1994-09-29 14:01 ` Tucker Taft 1994-09-29 18:37 ` Norman H. Cohen 1 sibling, 0 replies; 43+ messages in thread From: Tucker Taft @ 1994-09-29 14:01 UTC (permalink / raw) In article <1994Sep29.021209.20769@swlvx2.msd.ray.com>, John Volan <jgv@swl.msd.ray.com> wrote: >NHC = ncohen@watson.ibm.com (Norman H. Cohen) writes: > >NHC>One variation on this is to declare recursive types meant to serve as the >NHC>parents of types Employee and Office, but to provide no operations for >NHC>these recursive types. Then, in child packages, declare Employee and >NHC>Office themselves as derived types and declare primitive operations in >NHC>those child packages: >NHC> >NHC> with ... ; -- other stuff needed by Employee >NHC> with ... ; -- other stuff needed by Office >NHC> >NHC> package Employees_And_Offices is >NHC> >NHC> type Employee_Parent is tagged limited private; >NHC> type Employee_Pointer is access all Employee'Class; >NHC> >NHC> type Office_Parent is tagged limited private; >NHC> type Office_Pointer is access all Office'Class; > >This solution suffers from the same problem as Mark Biggar's suggestion: >These access types only give clients an "opaque" view of the designated objects. >The useful primitive operations for these objects won't be declared until we >get to the concrete *subclasses* declared later. So a client would have to >resort to using a non-typesafe Unchecked_Conversion to "downcast" one of these >pointers into designating the corresponding concrete subclass. As mentioned in a previous post, there is no need to use non-typesafe Unchecked_Conversion. A safe, checked, explicit "downcast" (or "narrowing") conversion is permitted in Ada 9X when the operand is of a class-wide type, or of an access-to-class-wide type. See RM9X-4.6(15, 23, 42, 50);5.0. >-- John Volan >-- Company => "Raytheon Missile Systems Division", >-- E_Mail_Address => "jgv@swl.msd.ray.com", S. Tucker Taft stt@inmet.com Intermetrics, Inc. Cambridge, MA 02138 ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-09-29 2:12 ` John Volan 1994-09-29 14:01 ` Tucker Taft @ 1994-09-29 18:37 ` Norman H. Cohen 1 sibling, 0 replies; 43+ messages in thread From: Norman H. Cohen @ 1994-09-29 18:37 UTC (permalink / raw) In article <1994Sep29.021209.20769@swlvx2.msd.ray.com>, jgv@swl.msd.ray.com (John Volan) writes: |> This solution suffers from the same problem as Mark Biggar's suggestion: Mark's solution and mine have a lot in common. (I should have declared Employee_Parent and Office_Parent to be abstract, as Mark did with the corresponding types in his solution, to indicate my intent that there be no objects of this type.) |> These access types only give clients an "opaque" view of the designated objects. |> The useful primitive operations for these objects won't be declared until we |> get to the concrete *subclasses* declared later. So a client would have to |> resort to using a non-typesafe Unchecked_Conversion to "downcast" one of these |> pointers into designating the corresponding concrete subclass. You are right. This problem can be solved by declaring all the operations of Employee and Office as abstract operations of Employee_Parent and Office_Parent. (So making the parent types abstract isn't just a good idea, it's the law.) The common operations can be handled by adding function Office_Occupied_By (The_Employee : in Employee_Parent'Class) return Office_Pointer; function Employee_Occupying (The_Office : in Office_Parent'Class) return Employee_Pointer; procedure Occupy (The_Office : in access Office_Parent'Class; The_Employee : in access Employee_Parent'Class); to Employees_And_Offices as concrete classwide operations. |> Another issue is whether the concrete subclasses declared below are going to |> be the *only* subclasses of these abstract classes. Certainly that's the |> original intent -- but will a maintainer pay any attention? ;-) I don't see any problem here. If the maintainer can come up with another useful extension to Office_Parent or Employee_Parent, more power to him. Indeed, type inheritance and child packages are useful in part as a "structured" way for a maintenance programmer to tailor the facilities in the original program in a way not anticipated by the original author, while leaving the original author's source intact. The correctness of an OO program should not depend on the absence of subclasses not mentioned in the original program! |> Another problem here is that this doesn't really solve the original puzzle I |> posed: How do you avoid breaking encapsulation *between* these two classes? |> These type declarations are private, but since the packages below are all |> children of this parent package, they have complete visibility to the private |> part of the parent. So an Employee subprogram still has license to ignore |> the public primitives of the Office class and "diddle" with the Office's |> private components directly. (And vice versa.) What you are asking for is impossible. As I noted yesterday, there is no way to define, in two separate packages, two types with primitive operations that operate on the other type, because the package specs are not allowed to be mutually dependent: - If Offices depends on Employees, for example, Employees can not depend on Offices, so there is no way to declare Office_Occupied_By (which must be declared in a place where its body can see the Its_Occupied_Office record component). - If Offices does not depend on Employees, there is no way to declare Employee_Occupying (which must be declared in a place where its body can see the Its_Occupying_Employee record component). But, as the song says, that's what friends are for. The implementations of mutually recursive types are often so tightly intertwined that it is not possible to say where one ends and the other begins; in the case of tightly coupled mutually recursive types there is no point in hiding the implementation of one from the implementation of the other. Tightly coupled mutually recursive types constitute a single abstraction consisting of two or more types, typically designed by one person and implemented by one person even if the abstraction is distributed among several child packages. When the mutually recursive types are tightly coupled, the ability of the subprogram bodies for each type single to reference the full recursive definition is a feature, not a bug. However, types Office and Employee are not so intimately intertwined. Each contains one pointer to the other and much more that has nothing to do with the other type. Here is an approach that exploits the loose coupling between the recursive types to hide only the "other stuff": package Employees_And_Offices is type Employee_Parent is abstract tagged private; type Employee_Pointer is access all Employee_Parent'Class; -- Abstract Employee_Parent subprograms involving that -- "other stuff" type Office_Parent is abstract tagged private; type Office_Pointer is access all Office_Parent'Class; -- Abstract Office_Parent subprograms involving that -- "other stuff" -- Concrete nondispatching subprograms involving only the pointers -- between the classes: function Office_Occupied_By (The_Employee: in Employee_Parent'Class) return Office_Pointer; function Employee_Occupying (The_Office: in Office_Parent'Class) return Employee_Pointer; procedure Occupy (The_Office: in Office_Pointer; The_Employee: in Employee_Pointer); private -- Only those components of Office and Employee that are of mutual -- interest: type Employee_Parent is record Its_Occupied_Office : Office_Pointer; end record; type Office_Parent is record Its_Occupying_Employee : Employee_Pointer; end record; end Employees_And_Offices; package body Employees_And_Offices is function Office_Occupied_By (The_Employee: in Employee_Parent'Class) return Office_Pointer is begin return The_Employee.Its_Occupied_Office; end Office_Occupied_By; function Employee_Occupying (The_Office: in Office_Parent'Class) return Employee_Pointer is begin return The_Office.Its_Occupying_Employee; end Employee_Occupying; procedure Occupy (The_Office: in Office_Pointer; The_Employee: in Employee_Pointer) is begin The_Employee.Its_Occupied_Office := The_Office; The_Office.Its_Occupying_Employee := The_Employee; end Occupy; end Employees_And_Offices; package Employees_And_Offices.Employees is type Employee is new Employee_Parent with private; -- Concrete Employee subprograms involving that -- "other stuff" private type Employee is new Employee_Parent with record -- various components involving that "other stuff" end record; end Employees_And_Offices.Employees; package Employees_And_Offices.Offices is type Office is new Office_Parent with private; -- Concrete Office subprograms involving that -- "other stuff" private type Office is new Office_Parent with record -- various components involving that "other stuff" end record; end Employees_And_Offices.Offices; -- Norman H. Cohen ncohen@watson.ibm.com ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-09-28 14:01 ` Norman H. Cohen 1994-09-29 2:12 ` John Volan @ 1994-09-29 9:48 ` Magnus Kempe 1994-09-29 13:10 ` Magnus Kempe 1994-09-29 13:35 ` John Volan 1 sibling, 2 replies; 43+ messages in thread From: Magnus Kempe @ 1994-09-29 9:48 UTC (permalink / raw) Norm's solution could be further improved as follows: : One variation on this is to declare recursive types meant to serve as the : parents of types Employee and Office, but to provide no operations for : these recursive types. Then, in child packages, declare Employee and : Office themselves as derived types and declare primitive operations in : those child packages: Since this couple of recursive types is strictly internal to the abstraction "employee and his office", I recommend that they be declared as abstract and hidden in the private part of the parent package (Yes, this compiles with GNAT): package Company is private type Employee_Parent; type Employee_Pointer is access all Employee_Parent'Class; type Office_Parent; type Office_Pointer is access all Office_Parent'Class; type Employee_Parent is abstract tagged record Its_Occupied_Office : Office_Pointer; end record; type Office_Parent is abstract tagged record Its_Occupying_Employee : Employee_Pointer; end record; end Company; The rest of Norm's solution doesn't change; the couple of abstractions Employee and Office is declared in two child units of Company. To the client programmer, the encapsulation is strong but the relationship between the two abstractions is quite apparent because they are exported by the Company.* subsystem (a hierarchy of packages). -- Magnus Kempe "I know not what course others may take, but as for me, Magnus.Kempe@di.epfl.ch Give me Liberty... or give me Death!" -- Patrick Henry ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-09-29 9:48 ` Magnus Kempe @ 1994-09-29 13:10 ` Magnus Kempe 1994-09-29 18:05 ` Tucker Taft 1994-10-01 1:24 ` Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) Adam Beneschan 1994-09-29 13:35 ` John Volan 1 sibling, 2 replies; 43+ messages in thread From: Magnus Kempe @ 1994-09-29 13:10 UTC (permalink / raw) John is still not satisfied that Ada 9X has a clean, elegant solution to his problem (two abstractions, Employee and Office, have a recursive dependency--how to model/implement that?). The example below illustrates that Ada 9X allows you to easily model/ implement abstractions and their relationships--while enforcing and preserving the concomitant encapsulations. That's what I call progress! (Yes, this all compiles with GNAT, a.k.a. "GNU Ada".) As I wrote in an other message: -- Since this couple of recursive types is strictly internal to the -- abstraction "employee and his office", I recommend that they be -- declared as abstract and hidden in the private part of the parent -- package. -------------------------------------------------------------------- package Company is -- This subsystem exports two abstractions, Employee and Office, which -- are mutually dependent (in a symmetric, one-to-one relationship). private -- the following private declarations set up the 1-1 relationship type Employee_Parent; type Employee_Reference is access all Employee_Parent'Class; type Office_Parent; type Office_Reference is access all Office_Parent'Class; type Employee_Parent is abstract tagged record Its_Occupied_Office : Office_Reference; end record; type Office_Parent is abstract tagged record Its_Occupying_Employee : Employee_Reference; end record; end Company; -- We now declare both abstractions in child units (based on Norm's solution) ----------------------------------------------------------------------------- with ... ; -- other stuff needed by Employee package Company.Employees is type Employee is private; type Employee_Pointer is access all Employee; No_Employee : constant Employee_Pointer := null; ... -- Employee subprograms involving that "other stuff" private type Employee is new Employee_Parent with record ... -- various components involving that "other stuff" end record; end Company.Employees; with ... ; -- other stuff needed by Office package Company.Offices is type Office is private; type Office_Pointer is access all Office; No_Office : constant Office_Pointer := null; ... -- Office subprograms involving that "other stuff" private type Office is new Office_Parent with record ... -- various components involving that "other stuff" end record; end Company.Offices; -- we can now declare operations working with both abstractions --------------------------------------------------------------- with Company.Employees, Company.Offices; package Company.Common is function Office_Occupied_By (The_Employee : in Employees.Employee) return Offices.Office_Pointer; function Employee_Occupying (The_Office : in Offices.Office) return Employees.Employee_Pointer; procedure Occupy (The_Office : in Offices.Office_Pointer; The_Employee : in Employees.Employee_Pointer); ... -- Employee and Office subprograms working with the relationship -- and/or the operations exported by each abstraction. end Company.Common; -- Note that the exported pointers are "specific". The client does not -- see anything class-wide. John Volan is afraid that we will have -- recourse to "Unchecked_Conversion" and/or break Ada's type safety. -- Not at all. We need a private child, but that's part of building -- clean subsystems. ----------------------------------------------------------------------- private package Company.Employees.Ptrs is -- helper package which "knows" that Employee is derived from Employee_Parent function Office_of (The_Employee : in Employee) return Office_Reference; end Company.Employees.Ptrs; package body Company.Employees.Ptrs is function Office_of (The_Employee : in Employee) return Office_Reference is begin return The_Employee.Its_Occupied_Office; end Office_of; end Company.Employees.Ptrs; with Company.Employees.Ptrs; package body Company.Common is function Office_Occupied_By (The_Employee : in Employees.Employee) return Offices.Office_Pointer is begin return Offices.Office_Pointer (Employees.Ptrs.Office_of (The_Employee)); -- the type conversion (from one access type to another) involves -- a type check: is the designated object of type Offices.Office? -- If not, exception Constraint_Error will be raised. end Office_Occupied_By; -- similar approach for Employee_Occupying and Occupy ... end Company.Common; -- John Volan expects that the body of each abstraction will be a client -- of the other abstraction. No problem. ------------------------------------------------------------------------ with Company.Offices; package body Company.Employees is ... -- bodies of Employee subprograms involving that "other stuff" -- and using the operations exported by Office end Company.Employees; with Company.Employees; package body Company.Offices is ... -- bodies of Office subprograms involving that "other stuff" -- and using the operations exported by Employee end Company.Offices; So... The structure of the system is clearly made for one abstraction Employee and one abstraction Office. A future maintainer cannot accidentally add subclasses of the two abstractions and silently break the common operations. The encapsulation of each abstraction is not broken anywhere; rather, the one-one relationship is abstracted into the parent Company package and operated upon in the child package Company.Common. -- Magnus Kempe "I know not what course others may take, but as for me, Magnus.Kempe@di.epfl.ch Give me Liberty... or give me Death!" -- Patrick Henry PS: the above code needs a work around for a GNAT bug (lack of visibility from a grand-child to his grand-parent). In company-employees.ads amd company-offices.ads, add the following two lines in each private part: subtype Employee_Reference is Company.Employee_Reference; subtype Office_Reference is Company.Office_Reference; ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-09-29 13:10 ` Magnus Kempe @ 1994-09-29 18:05 ` Tucker Taft 1994-09-30 10:20 ` Mut. Recurs. in Ada9X w/o Breaking Encaps.? Magnus Kempe 1994-10-01 1:24 ` Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) Adam Beneschan 1 sibling, 1 reply; 43+ messages in thread From: Tucker Taft @ 1994-09-29 18:05 UTC (permalink / raw) In article <36eebb$jn5@disunms.epfl.ch>, Magnus Kempe <Magnus.Kempe@di.epfl.ch> wrote: > ... >The example below illustrates that Ada 9X allows you to easily model/ >implement abstractions and their relationships--while enforcing and >preserving the concomitant encapsulations. That's what I call progress! >(Yes, this all compiles with GNAT, a.k.a. "GNU Ada".) The type conversion toward the end of your example is illegal (at least according to *our* Ada 9X front end ;-). Remember GNAT is focusing on compiling correct programs first, and will eventually enforce all of the nit-picking RM rules, but for now, just because GNAT compiles something doesn't make it legal ... In any case, to me it is much simpler to declare abstract versions of Employee and Office in the same package, with appropriately mutually recursive (abstract) operations declared there as well. E.g.: package Company is type Office is abstract tagged null record; type Office_Pointer is access all Office'Class; type Employee is abstract tagged null record; type Employee_Pointer is access all Employee'Class; function Office_Occupied_By(The_Employee : Employee) return Office_Pointer is abstract; function Employee_Occupying (The_Office : Office) return Employee_Pointer is abstract; procedure Occupy (The_Office : in Office_Pointer; The_Employee : in Employee_Pointer); -- This one we presumably have to implement (though -- I'm not sure what the original author had in mind), -- perhaps by calling additional abstract primitive operations -- declared below.... ... end Company; You can then create concrete derivatives of these in separate packages, preserving complete encapsulation relative to one another (presuming that is a big deal). Few if any explicit conversions will be required with this approach. >Magnus Kempe "I know not what course others may take, but as for me, >Magnus.Kempe@di.epfl.ch Give me Liberty... or give me Death!" -- Patrick Henry -Tucker Taft stt@inmet.com Intermetrics, Inc. Cambridge, MA 02138 ------------------- P.S. FYI Here is the source code you suggested, with an indication of where the error occurs: > ... >package Company is >-- This subsystem exports two abstractions, Employee and Office, which >-- are mutually dependent (in a symmetric, one-to-one relationship). > >private >-- the following private declarations set up the 1-1 relationship > > ... [Eliding employee stuff] > > type Office_Parent; > type Office_Reference is access all Office_Parent'Class; > > ... [Eliding employee stuff] > > type Office_Parent is > abstract tagged > record > Its_Occupying_Employee : Employee_Reference; > end record; >end Company; > > ... [Eliding employee stuff] > >with ... ; -- other stuff needed by Office >package Company.Offices is > type Office is > private; > type Office_Pointer is > access all Office; > > No_Office : constant Office_Pointer := null; > ... -- Office subprograms involving that "other stuff" > >private > type Office is > new Office_Parent > with record > ... -- various components involving that "other stuff" > end record; >end Company.Offices; > > >-- we can now declare operations working with both abstractions >--------------------------------------------------------------- >with > Company.Employees, Company.Offices; >package Company.Common is > function Office_Occupied_By (The_Employee : in Employees.Employee) > return Offices.Office_Pointer; > > ... [Eliding employee stuff] >end Company.Common; > > ... [Eliding employee stuff] > >with Company.Employees.Ptrs; >package body Company.Common is > function Office_Occupied_By (The_Employee : in Employees.Employee) > return Offices.Office_Pointer is > begin > return Offices.Office_Pointer > (Employees.Ptrs.Office_of (The_Employee)); > -- the type conversion (from one access type to another) involves > -- a type check: is the designated object of type Offices.Office? > -- If not, exception Constraint_Error will be raised. ***** Here is the problem **** This type conversion is illegal, since at this point, it is not visible that Office is derived from Office_Parent. > end Office_Occupied_By; > ... ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? 1994-09-29 18:05 ` Tucker Taft @ 1994-09-30 10:20 ` Magnus Kempe 1994-09-30 13:22 ` Tucker Taft 0 siblings, 1 reply; 43+ messages in thread From: Magnus Kempe @ 1994-09-30 10:20 UTC (permalink / raw) stt@spock.camb.inmet.com (Tucker Taft) writes: : : The type conversion toward the end of your example is : illegal (at least according to *our* Ada 9X front end ;-). Damn. Thanks. Here's a fix, which is also accepted by GNAT :-) Is your Ada 9X front end a program, or a human? private package Company.Offices.Ptrs is function Ptr (The_Office : Office_Reference) return Office_Pointer; end Company.Offices.Ptrs; package body Company.Offices.Ptrs is function Ptr (The_Office : Office_Reference) return Office_Pointer is begin -- here we "see" that Office is derived from Office_Parent return Office_Pointer (The_Office); end Ptr; end Company.Offices.Ptrs; with Company.Employees.Ptrs; with Company.Offices.Ptrs; package body Company.Common is function Office_Occupied_By (The_Employee : in Employees.Employee) return Offices.Office_Pointer is begin return Offices.Ptrs.Ptr (Employees.Ptrs.Office_of (The_Employee)); end Office_Occupied_By; ... end Company.Common; : In any case, to me it is much simpler to declare abstract : versions of Employee and Office in the same package, with appropriately : mutually recursive (abstract) operations declared there as well. I agree, but John Volan wanted something else... My solution completely avoids showing abstract and/or class-wide stuff to the client. -- Magnus Kempe "I know not what course others may take, but as for me, Magnus.Kempe@di.epfl.ch Give me Liberty... or give me Death!" -- Patrick Henry ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? 1994-09-30 10:20 ` Mut. Recurs. in Ada9X w/o Breaking Encaps.? Magnus Kempe @ 1994-09-30 13:22 ` Tucker Taft 0 siblings, 0 replies; 43+ messages in thread From: Tucker Taft @ 1994-09-30 13:22 UTC (permalink / raw) In article <1994Sep30.111050@di.epfl.ch>, Magnus Kempe <Magnus.Kempe@di.epfl.ch> wrote: >stt@spock.camb.inmet.com (Tucker Taft) writes: >: >: The type conversion toward the end of your example is >: illegal (at least according to *our* Ada 9X front end ;-). > >Damn. Thanks. Here's a fix, which is also accepted by GNAT :-) >Is your Ada 9X front end a program, or a human? Our Ada 9X front end is a program. In fact, when it complained about your source, I initially presumed our front end was wrong. But instead, it was the human who was wrong. I guess our front end is doing pretty well now that it has surpassed the MRT in being able to compile Ada 9X ;-). In any case, our front end likes your fix. > ... >-- >Magnus Kempe "I know not what course others may take, but as for me, >Magnus.Kempe@di.epfl.ch Give me Liberty... or give me Death!" -- Patrick Henry -Tucker Taft stt@inmet.com ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-09-29 13:10 ` Magnus Kempe 1994-09-29 18:05 ` Tucker Taft @ 1994-10-01 1:24 ` Adam Beneschan 1994-10-01 12:01 ` Magnus Kempe ` (2 more replies) 1 sibling, 3 replies; 43+ messages in thread From: Adam Beneschan @ 1994-10-01 1:24 UTC (permalink / raw) Magnus.Kempe@di.epfl.ch (Magnus Kempe) writes: > John is still not satisfied that Ada 9X has a clean, elegant solution > to his problem (two abstractions, Employee and Office, have a recursive > dependency--how to model/implement that?). I'd like to chime in and say I'm not satisfied either. First, the disclaimers: I'm just starting to learn about OO concepts and Ada 9X, so I may not know entirely what I'm talking about. I certainly don't yet understand the meaning of all the new 9X syntaxes presented in the examples. Certainly, some of my ideas are influenced by what little I know about Eiffel. Also, I'm by no means an expert at language design. However, the solution shown feels like a "workaround", not a clean, elegant solution. I guess it could be called a clean, elegant workaround. My reasoning is as follows: If the dependencies went only one way (an Employee contains a reference to an Office, or vice versa), the implementation would be straightforward. Because there are mutual dependencies, though, a new package Company has to be added. This means that the package Company is there "just to get the Ada language to do what you want", not to represent any concept or abstraction that has anything to do with the application. The Company package doesn't add anything to another programmer's understanding of how the program works--and it may in fact be a hindrance, since it has a name that could mislead one into thinking that it actually has some relevance. Perhaps it would be better to name it Aybnuiyuiwrfnkb or something. Similarly, the example uses inheritance (type Employee is new Employee_Parent is inheritance, right?). As I understand it, inheritance's primary purpose is to model "IS-A" or "IS-A-KIND-OF" relationships between object types (I believe it's sometimes used purely for code reuse, although that seems like a misuse of the concept to me). Here, inheritance doesn't really model anything--it's just something that got set up in order to get things to work. Like the Company package, the use of inheritance here doesn't add anything to one's understanding of the program. I assume that the examples given work correctly. I assume also that they satisfy the really important requirement--namely, that packages other than Employee, including Office, have access to everything the Employee package wishes to put in its interface and to nothing else having to do with Employees (and similarly for Offices). Thus, this is certainly an acceptable workaround for the problem. But "clean" and "elegant"? Maybe I'm being too much of a purist, but I have trouble characterizing something that requires adding this much code that has no meaning to someone reading the program, as elegant. It seems more like a flaw in 9X that things have to be done this way. And based on my experience writing large Ada 83 programs, my gut instinct is that this is the sort of flaw that will be cursed by many 9X users down the line. I also realize that if we tried to make Ada 9X absolutely perfect, it wouldn't be Ada 9X any more but something like Ada 23. :-) What I think John wants (and I concur) is some way for package specifications to use accesses to types in other packages that haven't been compiled yet--sort of like a forward declaration that crosses package boundaries. Perhaps something like this could be added to Ada 0X or whatever the next revision of the language would be. with ...; -- other stuff without Office; -- "without" means that we're referring to a -- package that hasn't been compiled yet package Employee is ... private type Object is tagged record ... -- various components involving that "other stuff" Its_Occupied_Office : Office.Pointer; -- here we're promising that when Office is compiled, -- there will be an ACCESS type named Pointer. This -- will be verified at link time. end record; end Employee; Oh well, just my 2 cents . . . -- Adam (P.S. John, how about we get together and start our own church? :-) -- The above views are my own and do not necessarily represent those of Irvine Compiler Corp. ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-10-01 1:24 ` Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) Adam Beneschan @ 1994-10-01 12:01 ` Magnus Kempe 1994-10-01 18:43 ` Mark A Biggar 1994-10-02 16:41 ` John Volan 2 siblings, 0 replies; 43+ messages in thread From: Magnus Kempe @ 1994-10-01 12:01 UTC (permalink / raw) adam@irvine.com (Adam Beneschan) writes: : ... the package Company is there "just to get the Ada language to do : what you want", not to represent any concept or abstraction that has : anything to do with the application. This is incorrect. The requirement is for two ADTs which are mutually dependent. Ada 9X offers a hierarchical namespace which allows us to capture the fact that Employee and Office belong together (you're not going to use one without the other), and the root package Company _holds_ that mutual dependency. : As I understand it, inheritance's primary purpose is to model "IS-A" : or "IS-A-KIND-OF" relationships between object types (I believe it's : sometimes used purely for code reuse, although that seems like a : misuse of the concept to me). Here, inheritance doesn't really model : anything--it's just something that got set up in order to get things : to work. You are right that code usually doesn't "model" anything. It's just there to _implement_ a model. You seem to hold that inheritance is good only when it corresponds exactly to an "is-a" relationship of your model. But there are two kinds of inheritance: interface and implementation (or code sharing). The first corresponds to is-A relationships; the second is a means for code sharing. In the particular example you cite, the derivation was not done in the interface but in the private part, so as to _hide_ the inheritance. Compare undue interface inheritance (the client sees the ADT and implementation details, as well as some List stuff which has nothing to do with a Stack ADT) with Lists; package Stacks is type Stack_Type is new Lists.List_Type -- !!! Stack is-a List with private; ... -- Push, Pop, Size ... -- somehow try to hide the operations of List_Type which -- violate the Stack abstraction private type Stack_Type is new List_Type with null record; end Stacks; vs. implementation inheritance (the client does not see anything except the proper ADT) with Lists; package Stacks is type Stack_Type is private; ... -- Push, Pop, Size private type Stack_Type is new Lists.List_Type -- !!! Stack is-implemented-by-a List with null record; end Stacks; There is nothing revolutionary here, and nothing peculiar to Ada. The distinction between interface and implementation inheritance is quite common and useful. One may have a dislike for implementation inheritance, but that's no sufficient reason to forbid its use to those who do find it advantageous. Is it an ugly "workaround"? Maybe, to the extent that someone somewhere will castigate any high-level language mechanism for being a workaround of their favorite assembly language feature. -- Magnus Kempe "I know not what course others may take, but as for me, Magnus.Kempe@di.epfl.ch Give me Liberty... or give me Death!" -- Patrick Henry ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-10-01 1:24 ` Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) Adam Beneschan 1994-10-01 12:01 ` Magnus Kempe @ 1994-10-01 18:43 ` Mark A Biggar 1994-10-02 16:41 ` John Volan 2 siblings, 0 replies; 43+ messages in thread From: Mark A Biggar @ 1994-10-01 18:43 UTC (permalink / raw) In article <Cwyzx4.H3K@irvine.com> adam@irvine.com (Adam Beneschan) writes: >I assume that the examples given work correctly. I assume also that >they satisfy the really important requirement--namely, that packages >other than Employee, including Office, have access to everything the >Employee package wishes to put in its interface and to nothing else >having to do with Employees (and similarly for Offices). Thus, this >is certainly an acceptable workaround for the problem. But "clean" >and "elegant"? Maybe I'm being too much of a purist, but I have >trouble characterizing something that requires adding this much code >that has no meaning to someone reading the program, as elegant. It >seems more like a flaw in 9X that things have to be done this way. >And based on my experience writing large Ada 83 programs, my gut >instinct is that this is the sort of flaw that will be cursed by many >9X users down the line. I also realize that if we tried to make Ada >9X absolutely perfect, it wouldn't be Ada 9X any more but something >like Ada 23. :-) It is my understanding that a very early decision in the ada9X revision process was to only add sufficiant new features to Ada to allow development of OOP system using a building block approach. In other words there should be no "One True Way" to OOP in Ada9x. Some of the early ideas proposed for adding OOP to Ada9x would have forced a "One True Way" on the language (E.G., adding package types). Because Ada9x was designed using this building block approach, some things might not end up looking as "Elegant" of "Clean" as in other languages that do have a "One True Way", but the added flexability should make designing OO system that use only as much of the OO stuff as needed very simple compared to "One True Way" system that require you to used everything whether you want it or not. I personally thing that the Ada9x design team has done an excellent job of sticking to the building block approach and have avoided a "One True Way" while providing all the features needed to design OOP systems. -- Mark Biggar mab@wdl.loral.com ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-10-01 1:24 ` Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) Adam Beneschan 1994-10-01 12:01 ` Magnus Kempe 1994-10-01 18:43 ` Mark A Biggar @ 1994-10-02 16:41 ` John Volan 1994-10-02 23:33 ` Matt Kennel ` (3 more replies) 2 siblings, 4 replies; 43+ messages in thread From: John Volan @ 1994-10-02 16:41 UTC (permalink / raw) adam@irvine.com (Adam Beneschan) writes: >I'd like to chime in and say I'm not satisfied either.... I fear there may be many of us out there. [snip] >And based on my experience writing large Ada 83 programs, my gut >instinct is that this is the sort of flaw that will be cursed by many >9X users down the line... That's my fear as well. That's why I've been trying very hard to find an Ada9X solution. Eiffel can do decoupled mutual recursion standing on its head. Smalltalk virtually swims in a sea of decoupled mutual recursion (trivial in a typeless language). I suspect C++ can actually manage it as well. (Can any C++ folks confirm this? Realize what I mean: Can you forward-declare a class and then *not* complete its declaration in the same header file, and yet complete the declaration of a client class? Or do you *have* to declare both classes in the same header file?) We want Ada9X to be successful. We want Ada9X to attract programmers who might not have considered using Ada before. If something is trivial in other languages, it is not necessary that it be *trivial* in Ada9X. But it must at least be *feasible*. [snip] >What I think John wants (and I concur) is some way for package >specifications to use accesses to types in other packages that haven't >been compiled yet--sort of like a forward declaration that crosses >package boundaries. Perhaps something like this could be added to >Ada 0X or whatever the next revision of the language would be. I agree wholeheartedly with the *goal*, but not with the *means* you suggest. Goal: Some kind of "forward declaration" that crosses package boundaries. (Good and worthy goal.) Means: Some way for package specifications to use access types in other packages that *haven't been compiled yet*. (Horrible means, terrible implications, shudder, shudder :-) Actually, I have a better idea. Let's take a bit of inspiration from a notion that is already intrinsic to the design of Ada: Although a package is considered to be a single, self-contained, organic entity -- a *unit* of program modularity -- nevertheless it is syntactically broken up into two *parts* that present complementary *views* of that single unit -- two views which can be *separately compiled*. They are: (1) the package specification, which presents the *interface* view of the package, and (2) the package body, which presents the *implementation* view of the package. All that is really needed is to a break off a third syntactic part that can offer a view of the package where we can make "forward declarations" -- or rather, where "incomplete" declarations could be tolerated, because they will eventually be completed in the package specification. We could offer up this new part as a separately-compilable piece of code. In keeping with the long-standing Ada practice of *recycling* reserved words for new uses rather than creating new ones (bet you didn't know that Ada language designers were environmentalists! :-), let's call this new package part a "package abstract" (where "abstract" is a noun rather than an adjective, like the "abstract" of an article). Rules: (1) A package abstract would be considered a declarative region contiguous with the public part of the corresponding package specification. The spec would implicitly have visibility to any declaration that appeared in its corresponding package abstract. (2) To avoid a situation analogous to the problem of "optional" package bodies in Ada 83, we would need to syntactically distinguish ordinary package specs from those that have an abstract. The following would be illegal: (a) Compiling an ordinary spec when a package abstract with the same name is already in the library. (b) Compiling a package abstract when an ordinary package spec with the same name is already in the library. (c) Compiling a package-spec-with-abstract before its corresponding package abstract. (Analogous to compiling a package body before the corresponding package spec.) (Of course, for the GNAT compiler, you'd have to translate these into source-file dependency rules rather than order-of-compilation rules.) (3) We would also need an alternate syntax for the "with" clause, to allow a client to import only the abstract of a package. Such a client would only be able to make use of those declarations from the package abstract that were actually "complete". Later (in its body, say) a client could import the full spec via a normal "with" clause, and this would give it access to the complete view of all the public features of the package. Example: ---------------------------------------------------------------------- abstract of package Employee is -- note additional syntax type Object is tagged limited private; -- This is an example of an incomplete declaration that -- can be "tolerated" in a package abstract type Pointer is access all Object'Class; None : constant Pointer := null; -- These are examples of complete declarations (or are they?) end Employee; ---------------------------------------------------------------------- with abstract of Office; -- examples of import of package abstracts with abstract of Memo; with abstract of Meeting; with abstract of Job_Description; ... package with abstract Employee is -- note additional syntax ... function Office_Occupied_By (The_Employee : in Employee.Object'Class) return Office.Pointer; procedure Occupy_Office (The_Employee : in out Employee.Object'Class; New_Office : in Office.Pointer); -- mutually recursive with Office.Accomodate_Employee ... private type Object is tagged limited record ... Its_Occupied_Office : Office.Pointer; Its_Job_Description : Job_Description.Pointer; ... ... end record; end Employee; ---------------------------------------------------------------------- with Office; -- normal import of full package specifications with Memo; with Meeting; with Job_Description; ... package body Employee is ... function Office_Occupied_By (The_Employee : in Employee.Object'Class) return Office.Pointer is begin return The_Empoyee.Its_Occupied_Office; end Office_Occupied_By; procedure Occupy_Office (The_Employee : in out Employee.Object'Class; New_Office : in Office.Pointer) is use type Office.Pointer; begin if New_Office /= Office.None and then New_Office /= The_Employee.Its_Occupied_Office then begin Employee.Vacate_Office (The_Employee); The_Employee.Its_Occupied_Office := New_Office; Office.Accomodate_Employee (The_Office.all, The_Employee'Access); end if; end Occupy_Office; ... end Employee; ---------------------------------------------------------------------- Obvious Problems: (1) It's certainly pretty late in the game to consider this kind of thing for Ada9X. Maybe Ada0X could do something with it. Also, I haven't studied the whole MRT history, so I don't know if someone has already thought of something like this. I don't want to take credit (or blame :-) for someone else's idea. (2) In making this suggestion, I am engaging in the execrable practice of "YALFing" (where YALF = "Yet Another Language Feature" :-). This extra feature might make it trivial to write code in this particular style, but is it worth the added complexity to the language? (I'm still holding out hope that there is some elegant way of combining the *existing* features of Ada 9X to support this coding style.) (3) Is a pointer type really "complete" by the end of a package abstract? What if, instead of a private type, the Object type in the package abstract were an incomplete type declaration that got completed in the *public* part of the package spec? What if that complete type were a record type or an array type? Would a client-of-spec be able to use record-component or array-indexing syntax with the pointer type, whereas a client-of-abstract would not? (4) Could a package-with-abstract be generic? Where would the generic clause go? On the abstract? On the spec? On both? If both, would they have to be identical, or could the abstract introduce some generic formal parameters and the spec introduce more? How would instantiation work? Would you need an alternate syntax for instantiation-of-abstract vs. instantiation-of-spec? What if a client instantiated the abstract and then later instantiated the spec with the same name and actual parameters? Would they be two views of the same instantiated package, or two different instantiated packages? What if you tried this at the library level? What implications does all of this have for the generic contract model? Not being a language lawyer, I'm sure there must be other conceptual difficulties and complexities. I can't begin to assess what it would cost you compiler-writers out there to try to implement this feature, but I can well imagine it giving you the screaming willies^H^H^H^H^H hillaries! ;-) -- John Volan > -- Adam >(P.S. John, how about we get together and start our own church? :-) Dibbs! I get to be pope first! :-) -------------------------------------------------------------------------------- -- 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."); -------------------------------------------------------------------------------- ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-10-02 16:41 ` John Volan @ 1994-10-02 23:33 ` Matt Kennel 1994-10-03 8:07 ` Mut. Recurs. in Ada9X w/o Breaking Encaps.? Magnus Kempe ` (2 subsequent siblings) 3 siblings, 0 replies; 43+ messages in thread From: Matt Kennel @ 1994-10-02 23:33 UTC (permalink / raw) John Volan (jgv@swl.msd.ray.com) wrote: : That's my fear as well. That's why I've been trying very hard to find : an Ada9X solution. Eiffel can do decoupled mutual recursion standing on its : head. Smalltalk virtually swims in a sea of decoupled mutual recursion : (trivial in a typeless language). : I agree wholeheartedly with the *goal*, but not with the *means* you suggest. : Goal: Some kind of "forward declaration" that crosses package boundaries. : (Good and worthy goal.) : Means: Some way for package specifications to use access types in other : packages that *haven't been compiled yet*. (Horrible means, terrible : implications, shudder, shudder :-) : Actually, I have a better idea. Let's take a bit of inspiration from a : notion that is already intrinsic to the design of Ada: Although a package is : considered to be a single, self-contained, organic entity -- a *unit* of program : modularity -- nevertheless it is syntactically broken up into two *parts* that : present complementary *views* of that single unit -- two views which can be : *separately compiled*. They are: (1) the package specification, which presents : the *interface* view of the package, and (2) the package body, which presents : the *implementation* view of the package. : All that is really needed is to a break off a third syntactic part that can : offer a view of the package where we can make "forward declarations" -- or : rather, where "incomplete" declarations could be tolerated, because they : will eventually be completed in the package specification. Eeek. Sorry I disagree here. Remember, the road to C++ was paved with good intentions. Instead of adding complexity, subtract it. Declare _ex cathedra_ "recursive definitions are allowed". Let the compiler implement. -- -Matt Kennel mbk@inls1.ucsd.edu -Institute for Nonlinear Science, University of California, San Diego -*** AD: Archive for nonlinear dynamics papers & programs: FTP to -*** lyapunov.ucsd.edu, username "anonymous". ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? 1994-10-02 16:41 ` John Volan 1994-10-02 23:33 ` Matt Kennel @ 1994-10-03 8:07 ` Magnus Kempe 1994-10-03 12:14 ` Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) Robert I. Eachus 1994-10-03 20:29 ` Harry Koehnemann 3 siblings, 0 replies; 43+ messages in thread From: Magnus Kempe @ 1994-10-03 8:07 UTC (permalink / raw) jgv@swl.msd.ray.com (John Volan) writes: : : [summary--packages should be broken up in three, not two, parts: : specification, body, and "abstract".] John, I suggest you stop for a while, and take the time to examine the solutions which have been offered (and read the RM 9X, clause 10.1, on program structure/separate compilation). Child units are _essentially_ a means to break packages into parts. This is something _no_ other language offers, so it is quite understandable that many will not always immediately see how useful it is (and some are likely to abuse it, as I tend to :-). The final solution whch Norm presented solves _perfectly_ the problem you submitted, with abstract types, class-wide subprograms, and a hierarchy of packages. Of course, Ada is not Smalltalk. But then, Smalltalk is not Ada either. In other words: Don't try to blindly transfer programming patterns from one language to another. -- Magnus Kempe "I know not what course others may take, but as for me, Magnus.Kempe@di.epfl.ch Give me Liberty... or give me Death!" -- Patrick Henry ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-10-02 16:41 ` John Volan 1994-10-02 23:33 ` Matt Kennel 1994-10-03 8:07 ` Mut. Recurs. in Ada9X w/o Breaking Encaps.? Magnus Kempe @ 1994-10-03 12:14 ` Robert I. Eachus 1994-10-04 2:12 ` R. William Beckwith 1994-10-04 16:00 ` John Volan 1994-10-03 20:29 ` Harry Koehnemann 3 siblings, 2 replies; 43+ messages in thread From: Robert I. Eachus @ 1994-10-03 12:14 UTC (permalink / raw) In article <1994Oct2.164105.13127@swlvx2.msd.ray.com> jgv@swl.msd.ray.com (John Volan) writes: > adam@irvine.com (Adam Beneschan) writes: >> I'd like to chime in and say I'm not satisfied either.... > I fear there may be many of us out there. > That's my fear as well. That's why I've been trying very hard to > find an Ada9X solution. Eiffel can do decoupled mutual recursion > standing on its head. Smalltalk virtually swims in a sea of > decoupled mutual recursion (trivial in a typeless language). I > suspect C++ can actually manage it as well. (Can any C++ folks > confirm this? Realize what I mean: Can you forward-declare a > class and then *not* complete its declaration in the same header > file, and yet complete the declaration of a client class? Or do > you *have* to declare both classes in the same header file?) Stated this way, you are trying to solve a different problem than the original one. This one has a very simple solution: child units. This eliminates the "necessity" for mutual recursion in the visible package specifications. However, there are limitations to this approach. (Let me do a bit of a sketch here: package Employees is type Employee is tagged private; ... end Employees; package Offices is type Office is tagged private; ... end Offices; with Offices; use Offices; function Employees.Assigned_Office(E: in Employee) return Office; with Employees; use Employees; function Offices.Occupant(O: in Office) return Employee; As sketched, this provides a clean, clear interface. However there are two problems. First, there is going to have to be some abstraction breaking to implement the bodies of these units. It can be done in a type safe manner, with private child units, and there are other possibilities. Second, these functions are not inheritable, so you might want: with Offices; use Offices; function Employees.Assigned_Office(E: in Employee'CLASS) return Office'CLASS; with Employees; use Employees; function Offices.Occupant(O: in Office'CLASS) return Employee'CLASS; instead of the versions above. Since such subprograms would have to deal with all of the (possibly) many subclasses to employees or offices, this approach can result in unmaintainable code. However for small projects, or for attributes which are orthogonal to the rest of the abstraction, this approach works just fine. If any extension to the current draft standard is required in this area--and I don't think one is--it would be to change 3.2.3(6) to make child units eligible as primitive operations, probably under the influence of a pragma. I certainly don't recommend such a change to the standard--it would open up all sorts of holes--but that should prevent someone from experimenting with such a pragma. -- Robert I. Eachus with Standard_Disclaimer; use Standard_Disclaimer; function Message (Text: in Clever_Ideas) return Better_Ideas is... ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-10-03 12:14 ` Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) Robert I. Eachus @ 1994-10-04 2:12 ` R. William Beckwith 1994-10-04 16:00 ` John Volan 1 sibling, 0 replies; 43+ messages in thread From: R. William Beckwith @ 1994-10-04 2:12 UTC (permalink / raw) O.K., I guess I've been watching on the side lines too long :-). I addressed the issue of mutual recursion (I called it the `withing' problem) with Tuck early in our efforts to map CORBA IDL to Ada 9X. Since then, we have solved the problem. I have said to several people that you may want to use a CORBA IDL to Ada translator even for non-distributed systems. Now is my chance to put up or shut up. In C++ you can have mutual recursion: // file chicken.h #ifndef CHICKEN #define CHICKEN class Chicken; #include "egg.h" class Chicken { public: Egg lay(); }; #endif // file egg.h #ifndef EGG #define EGG class Egg; #include "chicken.h" class Egg { public: Chicken hatch(); }; #endif In CORBA IDL you can have mutual recursion: // file chicken.idl #ifndef CHICKEN #define CHICKEN interface Chicken; #include <egg_file.idl> interface Chicken { Egg lay(); }; #endif // file egg.idl #ifndef EGG #define EGG interface Egg; #include <chicken_file.idl> interface Egg { Chicken hatch(); }; #endif In Ada 9X you can have mutual recursion: (result of IDL to Ada 9X translator with simplifications) with Ada.Finalization; package Corba is type Object is tagged ... type Ref is tagged ... end Corba; ------------------------------------------------- with Corba; package chicken_file is type Chicken_Ref is new Corba.Ref with null record; end chicken_file; ------------------------------------------------- with Corba; with egg_file; package chicken_file.Chicken is type Ref is new Chicken_Ref with null record; function lay (Self : Ref) return egg_file.Egg_Ref'Class; -- returns a egg_file.Egg.Ref end chicken_file.Chicken; ------------------------------------------------- with Corba; with egg_file.Egg; package chicken_file.Chicken.Impl is type Object is new Corba.Object with record -- (implementation data) end record; function lay (Self : in Object) return Egg.Ref'Class; end chicken_file.Chicken.Impl; ------------------------------------------------- with Corba; package egg_file is type Egg_Ref is new Corba.Ref with null record; end egg_file; ------------------------------------------------- with Corba; with chicken_file; package egg_file.Egg is type Ref is new Egg_Ref with null record; function hatch (Self : Ref) return chicken_file.Chicken_Ref'Class; -- returns a chicken_file.Chicken.Ref end egg_file.Egg; ------------------------------------------------- with Corba; with chicken_file.Chicken; package egg_file.Egg.Impl is type Object is new Corba.Object with record -- (implementation data) end record; function hatch (Self : in Object) return Chicken.Ref'Class; end egg_file.Egg.Impl; No with'ing problems here! ... Bill -- e-mail: Bill.Beckwith@ois.com | Team Ada Objective Interface Systems, Inc. | dist, full O-O 1895 Preston White Drive, Suite 250 | multithreading Reston, VA 22091-5448 U.S.A. | built in ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-10-03 12:14 ` Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) Robert I. Eachus 1994-10-04 2:12 ` R. William Beckwith @ 1994-10-04 16:00 ` John Volan 1994-10-05 11:42 ` Robert I. Eachus 1994-10-05 21:09 ` Matt Kennel 1 sibling, 2 replies; 43+ messages in thread From: John Volan @ 1994-10-04 16:00 UTC (permalink / raw) eachus@spectre.mitre.org (Robert I. Eachus) writes: >This one has a very simple solution: child units. >This eliminates the "necessity" for mutual recursion in the visible >package specifications. > However, there are limitations to this approach. (Let me do a bit >of a sketch here: > package Employees is > type Employee is tagged private; > ... > end Employees; > package Offices is > type Office is tagged private; > ... > end Offices; > with Offices; use Offices; > function Employees.Assigned_Office(E: in Employee) return Office; > with Employees; use Employees; > function Offices.Occupant(O: in Office) return Employee; This approach is nice, as long as we are willing to give up having these operations be primitives for their respective tagged types. I guess the only way I'd expand on this would be to make these into child packages rather than just child functions. Also, it looks like this scheme gives up on the possibility of incorporating the pointers directly into the tagged record types themselves, since they would still need to be declared in the private parts of the root packages somehow. However, this scheme could be combined very nicely with the approach Matt Kennel suggested of off-loading the association itself to a third package that implemented some kind of dual hash-table: ---------------------------------------------------------------------- generic type Former (<>) is limited private; type Former_Pointer is access all Object'Class; type Latter (<>) is limited private; type Latter_Pointer is access all Object'Class; package One_To_One is function Latter_Of (The_Former : in Former_Pointer) return Latter_Pointer; function Former_Of (The_Latter : in Latter_Pointer) return Former_Pointer; procedure Associate ( The_Former : in Former_Pointer; The_Latter : in Latter_Pointer ); procedure Dissociate ( The_Former : in Former_Pointer ); procedure Dissociate ( The_Latter : in Latter_Pointer ); end One_To_One; -- Implementation of this is left as an exercise for the reader ... :-) ---------------------------------------------------------------------- with Employee; with Office; with One_To_One; package Employee_Occupies_Office is new One_To_One (Former => Employee.Object, Former_Pointer => Employee.Pointer, Latter => Office.Object, Latter_Pointer => Office.Pointer); ---------------------------------------------------------------------- with Office; with Employee_Occupies_Office; package Employee.Office_Occupied is -- Employee.Office_Occupied.That_Of function That_Of ( The_Employee : in Employee.Object'Class ) return Office.Pointer; -- Employee.Office_Occupied.Associate procedure Associate ( The_Employee : in out Employee.Object'Class; New_Office : in Office.Pointer ); -- Employee.Office_Occupied.Dissociate procedure Dissociate ( The_Employee : in out Employee.Object'Class ); end Employee.Office_Occupied; ---------------------------------------------------------------------- package body Employee.Office_Occupied is -- Employee.Office_Occupied.That_Of function That_Of ( The_Employee : in Employee.Object'Class ) return Office.Pointer is begin Employee_Occupies_Office.Latter_Of (The_Former => The_Employee'Access); end That_Of; -- Employee.Office_Occupied.Associate procedure Associate ( The_Employee : in out Employee.Object'Class; New_Office : in Office.Pointer ) is begin Employee_Occupies_Office.Associate ( The_Former => The_Employee'Access, The_Latter => New_Office ); end Associate; -- Employee.Office_Occupied.Dissociate procedure Dissociate ( The_Employee : in out Employee.Object'Class ) is begin Employee_Occupies_Office.Dissociate (The_Former => The_Employee'Access); end Dissociate; end Employee.Office_Occupied; ---------------------------------------------------------------------- with Employee; with Employee_Occupies_Office; package Office.Employee_Occupant is -- Office.Employee_Occupant.That_Of function That_Of ( The_Office : in Office.Object'Class ) return Employee.Pointer; -- Office.Employee_Occupant.Associate procedure Associate ( The_Office : in out Office.Object'Class; New_Employee : in Employee.Pointer ); -- Office.Employee_Occupant.Dissociate procedure Dissociate ( The_Employee : in out Employee.Object'Class ); end Office.Employee_Occupant; ---------------------------------------------------------------------- package body Office.Employee_Occupant is -- Office.Employee_Occupant.That_Of function That_Of ( The_Office : in Office.Object'Class ) return Employee.Pointer is begin Employee_Occupies_Office.Former_Of (The_Latter => The_Office'Access); end That_Of; -- Office.Employee_Occupant.Associate procedure Associate ( The_Office : in out Office.Object'Class; New_Employee : in Employee.Pointer ) is begin Employee_Occupies_Office.Associate ( The_Latter => The_Office'Access, The_Former => New_Employee ); end Associate; -- Office.Employee_Occupant.Dissociate procedure Dissociate ( The_Office : in out Office.Object'Class ) is begin Employee_Occupies_Office.Dissociate (The_Latter => The_Office'Access); end Dissociate; end Office.Employee_Occupant; ---------------------------------------------------------------------- I guess the real philosophical issue that everything is hinging on here is: "Is a binary association *part* of the abstraction of each of the classes, or is it a *separate* abstraction of its own?" Adam Beneschan objected to other solutions that put the association structure in a common root package, on the grounds that nothing from the "real world" was being modeled by that. I disagree with that, and agree with Magnus Kempe that in fact the association should be treated as a first-class abstraction of its own, in some fashion. However, I do not agree with Magnus Kempe that those other solutions "perfectly" implement this abstraction, because of the coupling that I see them introducing. If you want to avoid that coupling, then I think that two child packages hanging off of the associated root class packages, and/or a separate package entirely, would be better solutions. But a corollary to this issue is the question: "Do association operations *need* to be primitives of each of the associated classes?" I'm beginning to think not, but I'm not sure I have a good handle on this question yet. Any thoughts? > As sketched, this provides a clean, clear interface. However there >are two problems. First, there is going to have to be some >abstraction breaking to implement the bodies of these units. It can >be done in a type safe manner, with private child units, and there are >other possibilities. I'm not sure what you mean by "abstraction breaking". Is there some trick we could use to get those pointers into the tagged records after all? Perhaps "opaque" forms of the pointers that somehow get converted to "transparent" forms later? > Second, these functions are not inheritable, so you might want: > > with Offices; use Offices; > function Employees.Assigned_Office(E: in Employee'CLASS) > return Office'CLASS; > > with Employees; use Employees; > function Offices.Occupant(O: in Office'CLASS) return Employee'CLASS; > > instead of the versions above. Yes, I believe I drew the same conclusion early on, that these would be class-wide, non-dispatching operations, even if they could somehow be placed in the root package. I think it's reasonable to assume that a Rumbaugh-style association should apply equivalently to all instances of a class and its subclasses >Since such subprograms would have >to deal with all of the (possibly) many subclasses to employees or >offices, this approach can result in unmaintainable code. However for >small projects, or for attributes which are orthogonal to the rest of >the abstraction, this approach works just fine. I don't think there's any problem here. It seems to me the most reasonable interpretation of a Rumbaugh-style Object Model would not allow subclasses to alter the nature of an association (at least, in structural terms) which was established by a superclass. If we were tempted to do so, then I think we'd have to question the quality of our analysis model (e.g., "does this association really apply to this whole superclass?"). I'd say in general that each association, at least in terms of structure, *would* be completely orthogonal to other parts of the abstraction of a class, including the other associations and value-oriented attributes. If so, then it's perfectly natural to deal with each association one at a time, whether you do so in child units or separate association packages. > If any extension to the current draft standard is required in this >area--and I don't think one is-- Nor do I, really. (Even despite my post on "package abstracts" -- that was more of a thought experiment, not a serious recommendation or proposal.) My belief is that Ada9X is quite expressive enough, all we need to do is find creative combinations of the facilities that are already there. >it would be to change 3.2.3(6) to make >child units eligible as primitive operations, probably under the >influence of a pragma. I certainly don't recommend such a change to >the standard--it would open up all sorts of holes--but that should >prevent someone from experimenting with such a pragma. Certainly would be an interesting experiment, but dangerous. (Wear protective gear. ;-) The difficulty I see is that the primitive interface of a tagged type (embodied in its tag, perhaps implemented as a method jump table) would be a fluid thing, dependant on how many child units were actually compiled and "withed" within a program. It seems that a compiler alone would not be able to resolve this, and that it would have to be a function of a linker. >-- > Robert I. Eachus >with Standard_Disclaimer; >use Standard_Disclaimer; >function Message (Text: in Clever_Ideas) return Better_Ideas is... -- John Volan -------------------------------------------------------------------------------- -- 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."); -------------------------------------------------------------------------------- ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-10-04 16:00 ` John Volan @ 1994-10-05 11:42 ` Robert I. Eachus 1994-10-05 21:09 ` Matt Kennel 1 sibling, 0 replies; 43+ messages in thread From: Robert I. Eachus @ 1994-10-05 11:42 UTC (permalink / raw) In article <1994Oct4.160056.4243@swlvx2.msd.ray.com> jgv@swl.msd.ray.com (John Volan) writes: > This approach is nice, as long as we are willing to give up having > these operations be primitives for their respective tagged types. > I guess the only way I'd expand on this would be to make these into > child packages rather than just child functions. Yep, it may be a trade off, or it may be just what you wanted. It depends on whether the association is an integeral part of the abstraction. If it is, I'd use one of the other techniques. But if it is peripheral to the definiton of the class this is probably the way to go. > Also, it looks like this scheme gives up on the possibility of > incorporating the pointers directly into the tagged record types > themselves, since they would still need to be declared in the private > parts of the root packages somehow. I'll answer this here, instead of with the "abstraction breaking" comment. If you implement the parent type with a explicit pointer to an extension area, then the definition of that area can be deferred to the package body. Works fine, and you can even go through the gymnastics of making the extensions a linked list of arbitrary fields by having a private child export a generic which can be instantiated for each different type of extension. The "problem" is that this gives a nice clean external interface while having all sorts of complex structures "under the covers." That's why the abstraction breaking comment. > I guess the real philosophical issue that everything is hinging on > here is: "Is a binary association *part* of the abstraction of > each of the classes, or is it a *separate* abstraction of its > own?" Change one word and you've got it exactly right: "Is THIS binary association..." You will almost certainly come up with different answers in different cases, and Ada 9X supports--as we have just seen--several abstractions, each appropriate for a different reality. > But a corollary to this issue is the question: "Do association > operations *need* to be primitives of each of the associated classes?" > I'm beginning to think not, but I'm not sure I have a good handle on > this question yet. Any thoughts? Yep! Some do, some should not be. See above. > I'm not sure what you mean by "abstraction breaking". Is there > some trick we could use to get those pointers into the tagged > records after all? Perhaps "opaque" forms of the pointers that > somehow get converted to "transparent" forms later? Several solutions. You hint at one which works. You can have a abstract class, and a type which is a pointer to all objects decended from it: package Pointers is type Everything is abstract tagged null record; type Pointer is access all Everything'Class; ... end Pointers; Now you make Employees and Offices children (or grandchildren) of everything, and on as before. This looks like combining Office_Parent and Employee_Parent from earlier examples, and in a sense it is. However, replace Everything with Controlled, and it makes a lot more sense. You lose some strong type legality checking, but good compilers should be able to warn you of incorrect downcasts before run-time. (Interesting side note here. A good compiler integrated with an annotation language would allow better compile time warnings, I don't know that pragma ASSERT is sufficient.) > I don't think there's any problem here. It seems to me the most > reasonable interpretation of a Rumbaugh-style Object Model would not > allow subclasses to alter the nature of an association (at least, in > structural terms) which was established by a superclass. If we were > tempted to do so, then I think we'd have to question the quality of > our analysis model (e.g., "does this association really apply to this > whole superclass?"). Agreed, I was just trying to point out cases where this approach would not be the right choice. > >it would be to change 3.2.3(6) to make > >child units eligible as primitive operations, probably under the > >influence of a pragma. I certainly don't recommend such a change to > >the standard--it would open up all sorts of holes--but that should > >prevent someone from experimenting with such a pragma. > Certainly would be an interesting experiment, but dangerous. (Wear > protective gear. ;-) The difficulty I see is that the primitive > interface of a tagged type (embodied in its tag, perhaps implemented > as a method jump table) would be a fluid thing, dependant on how many > child units were actually compiled and "withed" within a program. It > seems that a compiler alone would not be able to resolve this, and > that it would have to be a function of a linker. In general, the linker is going to have to do part of the building of jump tables now, so that is not a drawback. The problems would occur in the implementation of compile time overload resolution, especially for non-tagged types. (Ouch!) An experimental compiler could make all such bindings non-static and resolved through jump tables, but a usable compiler that only used jump tables where necessary would be an interesting research project. (Compiling a child unit into the library might have to result in recompiling units which depend on the parent, even if they are unaware of the child. The old "legal with respect to units they do not depend on (see AI-256)" Pandorra's box.) This discussion has been very interesting. I'm beginning to think that we are starting to understand this new language. From what I have seen it is going to be a very nice place to live. -- Robert I. Eachus with Standard_Disclaimer; use Standard_Disclaimer; function Message (Text: in Clever_Ideas) return Better_Ideas is... ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-10-04 16:00 ` John Volan 1994-10-05 11:42 ` Robert I. Eachus @ 1994-10-05 21:09 ` Matt Kennel 1 sibling, 0 replies; 43+ messages in thread From: Matt Kennel @ 1994-10-05 21:09 UTC (permalink / raw) John Volan (jgv@swl.msd.ray.com) wrote: : I guess the real philosophical issue that everything is hinging on : here is: "Is a binary association *part* of the abstraction of each of : the classes, or is it a *separate* abstraction of its own?" : Adam Beneschan objected to other solutions that put the association : structure in a common root package, on the grounds that nothing from : the "real world" was being modeled by that. I disagree with that, and : agree with Magnus Kempe that in fact the association should be treated : as a first-class abstraction of its own, in some fashion. I think it's somewhat more than just a 'philosophical' issue. If you have a "thing" managing the mutual couplings, you can easily guarantee that employee(office(this_employee_object)) = this_employee_object. (and the reverse). i.e. that you always maintain an invertible one-to-one relation. I thought this was one of the original requirements. If you have a 'thing' relating the two, you can put this guarantee logic in one place, instead of having this fact be implicitly relying upon the proper programming of the classes to be related. -- -Matt Kennel mbk@inls1.ucsd.edu -Institute for Nonlinear Science, University of California, San Diego -*** AD: Archive for nonlinear dynamics papers & programs: FTP to -*** lyapunov.ucsd.edu, username "anonymous". ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-10-02 16:41 ` John Volan ` (2 preceding siblings ...) 1994-10-03 12:14 ` Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) Robert I. Eachus @ 1994-10-03 20:29 ` Harry Koehnemann 3 siblings, 0 replies; 43+ messages in thread From: Harry Koehnemann @ 1994-10-03 20:29 UTC (permalink / raw) In article <1994Oct2.164105.13127@swlvx2.msd.ray.com> jgv@swl.msd.ray.com (John Volan) writes: > >That's my fear as well. That's why I've been trying very hard to find >an Ada9X solution. Eiffel can do decoupled mutual recursion standing on its >head. Smalltalk virtually swims in a sea of decoupled mutual recursion >(trivial in a typeless language). I suspect C++ can actually manage it as well. >(Can any C++ folks confirm this? Realize what I mean: Can you forward-declare >a class and then *not* complete its declaration in the same header file, and >yet complete the declaration of a client class? Or do you *have* to declare >both classes in the same header file?) Yes. See the example. class A; Class B { A *a; // legal ... a -> print(); // illegal }; However, the body of B cannot perform any operations on 'a' since they are not visible. I think this would also work in Ada9X, except that multiple pointer declarations would be incompatible with one another due to Ada's typing mechanism. From what I remember of Eiffel and Smalltalk (it's been a while), one can invoke an operation from a class that has not yet been declared. However, both C++ and Ada9X require a function (or operation) be declared before it be used. I think this is the feature you seek, which is more than simply a forward declaration. Hope this helps. -- Harry Koehnemann Arizona State University hek@asu.edu Computer Science Department ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-09-29 9:48 ` Magnus Kempe 1994-09-29 13:10 ` Magnus Kempe @ 1994-09-29 13:35 ` John Volan 1994-09-30 20:27 ` Norman H. Cohen 1994-09-30 22:46 ` Matt Kennel 1 sibling, 2 replies; 43+ messages in thread From: John Volan @ 1994-09-29 13:35 UTC (permalink / raw) MK = Magnus.Kempe@di.epfl.ch (Magnus Kempe) writes: MK>Norm's solution could be further improved as follows: MK> MK>: One variation on this is to declare recursive types meant to serve as the MK>: parents of types Employee and Office, but to provide no operations for MK>: these recursive types. Then, in child packages, declare Employee and MK>: Office themselves as derived types and declare primitive operations in MK>: those child packages: MK> MK>Since this couple of recursive types is strictly internal to the ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ MK>abstraction "employee and his office", I recommend that they be ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Not true. The premise of my original question was that the binary association between Employee and Office is a public feature fully visible to their clients -- and they could even include each other as clients! Maybe the *implementation* of this binary association is strictly internal to the abstraction (I agree that it should be), but the *existence* of this binary association should be a visible part of this abstraction. In other words, the interface to the Employee class should include some subprogram(s) that allow(s) a client to fetch the identity of (a pointer to) the associated Office. Likewise the interface to the Office class should include some subprogram(s) that allow(s) a client to fetch the identity of (a pointer to) the associated Employee. And the interface to both classes should include some subprogram(s) that allow a client to establish an association between a given Office and a given Employee, in such a way as to always guarantee the following invariant assertion: For any given Employee "E" and Office "O", E occupies O if and only if O is occupied by E. MK>declared as abstract and hidden in the private part of the parent MK>package (Yes, this compiles with GNAT): MK> MK> package Company is MK> private MK> type Employee_Parent; MK> type Employee_Pointer is access all Employee_Parent'Class; MK> MK> type Office_Parent; MK> type Office_Pointer is access all Office_Parent'Class; MK> MK> type Employee_Parent is abstract tagged MK> record MK> Its_Occupied_Office : Office_Pointer; MK> end record; MK> MK> type Office_Parent is abstract tagged MK> record MK> Its_Occupying_Employee : Employee_Pointer; MK> end record; MK> end Company; MK> MK>The rest of Norm's solution doesn't change; the couple of abstractions MK>Employee and Office is declared in two child units of Company. To the MK>client programmer, the encapsulation is strong but the relationship MK>between the two abstractions is quite apparent because they are exported MK>by the Company.* subsystem (a hierarchy of packages). Unfortunately, hiding the pointer types within the private part of the parent prevents them from being visible to clients, even via child packages. This defeats the whole purpose of having a mutually recursive *abstraction* -- that is, something that provides an *interface* to some useful services while encapsulating the *implementation* of those services. MK>-- MK>Magnus Kempe "I know not what course others may take, but as for me, MK>Magnus.Kempe@di.epfl.ch Give me Liberty... or give me Death!" -- Patrick Henry -- John Volan -------------------------------------------------------------------------------- -- 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."); -------------------------------------------------------------------------------- ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-09-29 13:35 ` John Volan @ 1994-09-30 20:27 ` Norman H. Cohen 1994-10-01 1:47 ` John Volan 1994-09-30 22:46 ` Matt Kennel 1 sibling, 1 reply; 43+ messages in thread From: Norman H. Cohen @ 1994-09-30 20:27 UTC (permalink / raw) In article <1994Sep29.133526.2134@swlvx2.msd.ray.com>, jgv@swl.msd.ray.com (John Volan) writes: |> Not true. The premise of my original question was that the binary association |> between Employee and Office is a public feature fully visible to their |> clients -- and they could even include each other as clients! Maybe the |> *implementation* of this binary association is strictly internal to the |> abstraction (I agree that it should be), but the *existence* of this binary |> association should be a visible part of this abstraction. In other words, |> the interface to the Employee class should include some subprogram(s) that |> allow(s) a client to fetch the identity of (a pointer to) the associated Office. |> Likewise the interface to the Office class should include some subprogram(s) |> that allow(s) a client to fetch the identity of (a pointer to) the associated |> Employee. And the interface to both classes should include some subprogram(s) |> that allow a client to establish an association between a given Office and a |> given Employee, in such a way as to always guarantee the following invariant |> assertion: |> |> For any given Employee "E" and Office "O", |> E occupies O if and only if O is occupied by E. ... |> Unfortunately, hiding the pointer types within the private part of the parent |> prevents them from being visible to clients, even via child packages. This |> defeats the whole purpose of having a mutually recursive *abstraction* -- |> that is, something that provides an *interface* to some useful services while |> encapsulating the *implementation* of those services. The five lines just above seem to contradict your earlier statement that the pointer types (the imnplementation of the binary association between offices and employees) should be private, so I'll assume you really meant what you said the first time and not the second time. Pasting together bits and pieces of solutions proposed by Mark Biggar, Magnus Kempe, and me, isn't the following all that you're really looking for? package Employees_And_Offices is type Employee_Parent is abstract tagged private; type Employee_Pointer is access Employee_Parent'Class; type Office_Parent is abstract tagged private; type Office_Pointer is access Office_Parent'Class; -- Nondispatching operations concerned with the relationship -- between offices and employees: function Office_Occupied_By (The_Employee: Employee_Parent'Class) return Office_Pointer; function Employee_Occupying (The_Office: in Office_Parent'Class) return Employee_Pointer; procedure Occupy (The_Employee : in Employee_Pointer; The_Office : in Office_Pointer); private type Employee_Parent is tagged record Its_Occupied_Office: Office_Pointer; end record; type Office_Parent is tagged record Its_Occupying_Employee: Employee_Pointer; end record; end Employees_And_Offices; package Employees_And_Offices.Employees is type Employee is new Employee_Parent with private; [Operations concerned with the Employee "other stuff"] private type Employee is new Employee_Parent with record [the other stuff] end record; end Employees_And_Offices.Employees; package Employees_And_Offices.Offices is type Office is new Office_Parent with private; [Operations concerned with the Office "other stuff"] private type Office is new Office_Parent with record [the other stuff] end record; end Employees_And_Offices.Offices; Employee and Office can each see the other's links to itself, but each type's "other stuff" is hidden from the other. Outside clients can't even see the links, just the subprograms for manipulating the links. -- Norman H. Cohen ncohen@watson.ibm.com ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-09-30 20:27 ` Norman H. Cohen @ 1994-10-01 1:47 ` John Volan 1994-10-01 20:44 ` Tucker Taft 1994-10-03 11:29 ` Robert I. Eachus 0 siblings, 2 replies; 43+ messages in thread From: John Volan @ 1994-10-01 1:47 UTC (permalink / raw) ncohen@watson.ibm.com (Norman H. Cohen) writes: >The five lines just above seem to contradict your earlier statement that >the pointer types (the imnplementation of the binary association between >offices and employees) should be private, so I'll assume you really meant >what you said the first time and not the second time. Pasting together >bits and pieces of solutions proposed by Mark Biggar, Magnus Kempe, and >me, isn't the following all that you're really looking for? Well, I guess I haven't been very clear, sorry about that. Maybe the post I just sent out with all the pictures will help, but let me try again here. Your solution is perfectly fine in terms of what I think you thought I wanted :-). I described the problem in terms of a single association and two classes, and if that's all there were to it, capturing the mutual dependency as an abstraction works well. But you see, I always had in mind expanding this out to many classes with many interrelationships, and I don't think your scheme will scale up well. Suppose, instead of just writing package "Employees_And_Offices", we had to write package "Company". Suppose we tried to support not only Employees and Offices, but also other things in the problem domain of Company management. Things like Memos and Meetings and Job_Descriptions and Inventories and Stock and Benefits and so on and on and on ... (Please bear with me, I'm not even going to try to make this a realistic example, nor do I propose that you would really want to cover this particular problem domain this way. But for the sake of argument, here goes.) with Sets; package Company is type Employee_Parent is abstract tagged private; type Employee_Pointer is access Employee_Parent'Class; package Employee_Sets is new Sets (Employee_Pointer); type Office_Parent is abstract tagged private; type Office_Pointer is access Office_Parent'Class; package Office_Sets is new Sets (Office_Pointer); type Memo_Parent is abstract tagged private; type Memo_Pointer is access Memo_Parent'Class; package Memo_Sets is new Sets (Memo_Pointer): type Meeting_Parent is abstract tagged private; type Meeting_Pointer is access Meeting_Parent'Class; package Meeting_Sets is new Sets (Meeting_Pointer); type Job_Description_Parent is abstract tagged private; type Job_Description_Pointer is access Job_Description_Parent'Class; package Job_Description_Sets is new Sets (Job_Description_Pointer); type Inventory_Parent is abstract tagged private; type Inventory_Pointer is access Inventory_Parent'Class; package Inventory_Sets is new Sets (Inventory_Pointer); ... ad nauseum -- Nondispatching operations concerned with the relationships -- between offices and employees, -- between memos and employees, -- between memos and meetings, -- between meetings and employees, -- between employees and job descriptions, -- between employees and inventories, -- between inventories and stock, -- between employees and benefits, -- ... ad nauseum private type Employee_Parent is tagged record Its_Job_Description : Job_Description_Pointer; Its_Occupied_Office : Office_Pointer; Its_Sent_Memos : Memo_Sets.Set; Its_Received_Memos : Memo_Sets.Set; Its_Attended_Meetings : Meeting_Sets.Set; Its_Coordinated_Meetings : Meeting_Sets.Set; ... end record; type Office_Parent is tagged record Its_Occupying_Employee: Employee_Pointer; Its_Employee_Occupancy_Memos : Memo_Sets.Set; ... end record; type Memo_Parent is tagged record Its_Sending_Employee : Employee_Pointer; Its_Receiving_Employees : Employee_Sets.Set; ... end record; type Meeting_Parent is tagged record Its_Announcement_Memo : Memo_Pointer; Its_Followup_Memo : Memo_Pointer; Its_Atteding_Employees : Employee_Sets.Set; Its_Coordinating_Employee : Employee_Pointer; ... end record; type Job_Description is tagged record Its_Described_Employees : Employee_Sets.Set; end record; . . . ad nauseum end Company; You see, no matter how nicely you abstract the coupling between Employee and Office, no matter how well you hide the implementation of the links, no matter how well you defer the "other stuff" to the child packages, the fact remains that you are declaring the Employee and Office types in the same declarative region. If a mutually-recursive association forces you to declare two class types (however abstract) in the same declarative region, then the effect is going to be transitive, and it essentially can force you to put the entire semantic network of a large problem domain into one monolithic package. Adding just one more class or association into the network, because of a requirements change, will force you to recompile this package, all its children, all their clients ... possibly the whole application. On the other hand, if we can manage to maintain 1 Package = 1 Class even despite mutual recursion, then we at least have some hope of managing a large, complex problem -- one class at a time. Isn't that what object-orientation is all about? I think, thanks to Tucker and all the rest of you, that I managed to find an Ada9X solution to this problem, a couple of posts back. (If that scheme doesn't work, please let some kind person tell me and put me out of my misery. :-) Perhaps this mutual recursion business is a minor issue, perhaps not, but at least I'm comforted that Ada9X could deal with it if it had to. >-- >Norman H. Cohen ncohen@watson.ibm.com -- John Volan -------------------------------------------------------------------------------- -- 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."); -------------------------------------------------------------------------------- ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-10-01 1:47 ` John Volan @ 1994-10-01 20:44 ` Tucker Taft 1994-10-03 11:29 ` Robert I. Eachus 1 sibling, 0 replies; 43+ messages in thread From: Tucker Taft @ 1994-10-01 20:44 UTC (permalink / raw) I suggest that future follow-ups of this note restrict themselves to the comp.lang.ada newsgroup. This seems to be pretty much exclusively a discussion of how to do things in Ada 9X now, rather than a general discussion about OOP. -Tucker Taft stt@inmet.com Intermetrics, Inc. ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-10-01 1:47 ` John Volan 1994-10-01 20:44 ` Tucker Taft @ 1994-10-03 11:29 ` Robert I. Eachus 1 sibling, 0 replies; 43+ messages in thread From: Robert I. Eachus @ 1994-10-03 11:29 UTC (permalink / raw) In article <1994Oct1.014758.10687@swlvx2.msd.ray.com> jgv@swl.msd.ray.com (John Volan) writes: > ... Suppose, instead of just writing package > "Employees_And_Offices", we had to write package "Company". > Suppose we tried to support not only Employees and Offices, but > also other things in the problem domain of Company management. > Things like Memos and Meetings and Job_Descriptions and > Inventories and Stock and Benefits and so on and on and on ... Hmmm... I can have a model of a company MIS environment where there is one package of high-level abstractions such as people, places, and things. I can also have lower-level abstractions, which can be a hierarchy of child packages, since I want to have this as a sub system. Seems like a reasonable approach, we just have to rename some of the types in the examples: type People is abstract tagged private; type People_Pointer is access People'Class; ...etc. Now everything at the top level seems reasonable, and the lower down decisions, such as whether consultants can be assigned offices, is implementable at the appropriate level. Adding attributes to the top level abstraction of people will require modifications to the root package, for example adding a fax number field, or a teleport address. I assume that such modifications occur at a relatively slow pace, but notice that recently we have had at least TWO additions to business cards--fax number and e-mail address. The previous sucn change was the addition of telephone numbers (or perhaps area codes for telephone numbers). But that started happening about a century ago. (Area codes started appearing in the late fifties.) The next addition will probably be photographs--the MITRE on-line directory has them now. > On the other hand, if we can manage to maintain 1 Package = 1 > Class even despite mutual recursion, then we at least have some > hope of managing a large, complex problem -- one class at a time. > Isn't that what object-orientation is all about? Here I have to disagree, but it may be an issue of terminology. In Smalltalk there is ONE superclass. Do you hear complaints about Smalltalk which state that this prevents encapsulation? No. As I see it you are really arguing that superclasses shouldn't have to "know" about subclass specific interfaces and details. Stated that way, I don't have a problem with your assertion, or with Ada 9X. However, identifying the appropriate level in the tree for a specific interface is always going to be a hard problem. And we are going to spend a lot of time fixing wrong guesses. That is a part of the design (and programming) process, and probably can never be eliminated entirely. -- Robert I. Eachus with Standard_Disclaimer; use Standard_Disclaimer; function Message (Text: in Clever_Ideas) return Better_Ideas is... ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-09-29 13:35 ` John Volan 1994-09-30 20:27 ` Norman H. Cohen @ 1994-09-30 22:46 ` Matt Kennel 1994-10-01 2:11 ` John Volan 1 sibling, 1 reply; 43+ messages in thread From: Matt Kennel @ 1994-09-30 22:46 UTC (permalink / raw) John Volan (jgv@swl.msd.ray.com) wrote: : Not true. The premise of my original question was that the binary association : between Employee and Office is a public feature fully visible to their : clients -- and they could even include each other as clients! Maybe the : *implementation* of this binary association is strictly internal to the : abstraction (I agree that it should be), but the *existence* of this binary : association should be a visible part of this abstraction. In other words, : the interface to the Employee class should include some subprogram(s) that : allow(s) a client to fetch the identity of (a pointer to) the associated Office. : Likewise the interface to the Office class should include some subprogram(s) : that allow(s) a client to fetch the identity of (a pointer to) the associated : Employee. And the interface to both classes should include some subprogram(s) : that allow a client to establish an association between a given Office and a : given Employee, in such a way as to always guarantee the following invariant : assertion: : For any given Employee "E" and Office "O", : E occupies O if and only if O is occupied by E. How about this kind solution? I'm going to have to write in ersatz Eiffel/Sather: class TWO_WAY_RELATION{T1,T2} is private map_1_to_2:MAP{T1,T2}; -- MAP{T1,T2} is a hash table -- data structure that finds T2's given -- T1's as keys. private map_2_to_1:MAP{T2,T1}; create is map_1_to_2 := #MAP{T1,T2}; map_2_to_1 := #MAP{T1,T2}; end; add_pair(x:T1,y:T2) is map_1_to_2.insert(x,y); map_2_to_1.insert(y,x); end; find_one_given_two(y:T2):T1 is return map_2_to_1.find(y); end; find_one_given_two(x:T1):T2 is return map_1_to_2.find(x); end; end; -- class TWO_WAY_RELATION class EMPLOYEE is private shared mapper:TWO_WAY_RELATION{EMPLOYEE,OFFICE}; -- shared so all instances access the same object find_my_office : OFFICE is mapper.find_two_given_one(self); end; set_my_office(o:OFFICE) is mapper.add_pair(self,o); end; end; -- EMPLOYEE class OFFICE is private shared mapper:TWO_WAY_RELATION{EMPLOYEE,OFFICE} -- shared so all instances access the same object find_my_employee : OFFICE is mapper.find_one_given_two(self); end; set_my_employee(e:EMPLOYEE) is mapper.add_pair(e,self); end; end; -- OFFICE --- Now, both Office and Employee present interfaces that can find and set each one's counterpart. Neither knows how to do this except through the 'mapper' object, which presents a clean interface for managing pairs. Comments? -- -Matt Kennel mbk@inls1.ucsd.edu -Institute for Nonlinear Science, University of California, San Diego -*** AD: Archive for nonlinear dynamics papers & programs: FTP to -*** lyapunov.ucsd.edu, username "anonymous". ^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) 1994-09-30 22:46 ` Matt Kennel @ 1994-10-01 2:11 ` John Volan 0 siblings, 0 replies; 43+ messages in thread From: John Volan @ 1994-10-01 2:11 UTC (permalink / raw) mbk@inls1.ucsd.edu (Matt Kennel) writes: >How about this kind solution? I'm going to have to write in ersatz >Eiffel/Sather: Ah, you beat me to it, Matt! Or did my post get out first? Anyway... >class TWO_WAY_RELATION{T1,T2} is > private map_1_to_2:MAP{T1,T2}; -- MAP{T1,T2} is a hash table > -- data structure that finds T2's given > -- T1's as keys. > private map_2_to_1:MAP{T2,T1}; > > create is > map_1_to_2 := #MAP{T1,T2}; > map_2_to_1 := #MAP{T1,T2}; end; > > add_pair(x:T1,y:T2) is > map_1_to_2.insert(x,y); > map_2_to_1.insert(y,x); > end; > > find_one_given_two(y:T2):T1 is > return map_2_to_1.find(y); end; > > find_one_given_two(x:T1):T2 is > return map_1_to_2.find(x); end; > >end; -- class TWO_WAY_RELATION Absolutely right. Ada can certainly do that too, with generic packages. >class EMPLOYEE is > private shared mapper:TWO_WAY_RELATION{EMPLOYEE,OFFICE}; > -- shared so all instances access the same object > > find_my_office : OFFICE is ^^^^^^ Ah, but you see, this is precisely my point. You are in the midst of writing the interface (and implementation -- Eiffel does it all at once) of class EMPLOYEE. Class OFFICE has not yet been written! I'll assume that this truly works in Eiffel and that these three classes are three separate compilation units, not just one file. If so, then Eiffel is able to manage the Identity/Interface/Implementation Trichotomy I talked about a couple posts ago. > mapper.find_two_given_one(self); end; > > set_my_office(o:OFFICE) is > mapper.add_pair(self,o); end; > >end; -- EMPLOYEE > >class OFFICE is > private shared mapper:TWO_WAY_RELATION{EMPLOYEE,OFFICE} > -- shared so all instances access the same object > > find_my_employee : OFFICE is > mapper.find_one_given_two(self); end; > > set_my_employee(e:EMPLOYEE) is > mapper.add_pair(e,self); end; >end; -- OFFICE > >--- > >Now, both Office and Employee present interfaces that can find >and set each one's counterpart. Neither knows how to do this >except through the 'mapper' object, which presents a clean interface >for managing pairs. > >Comments? If this works, it's not because the mapper object did it for them. It's because Eiffel can deal with two classes that are mutually dependent even in their interfaces! (Matt, can you or some other Eiffel person confirm this?) Well, Ada folks, if this works ... need I say more? >-- >-Matt Kennel mbk@inls1.ucsd.edu >-Institute for Nonlinear Science, University of California, San Diego >-*** AD: Archive for nonlinear dynamics papers & programs: FTP to >-*** lyapunov.ucsd.edu, username "anonymous". --John Volan -------------------------------------------------------------------------------- -- 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."); -------------------------------------------------------------------------------- ^ permalink raw reply [flat|nested] 43+ messages in thread
end of thread, other threads:[~1994-10-05 21:09 UTC | newest] Thread overview: 43+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 1994-09-27 16:52 Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) John Volan 1994-09-27 18:48 ` Mark A Biggar 1994-09-29 1:46 ` John Volan 1994-09-29 13:57 ` Tucker Taft 1994-09-29 17:20 ` Bjarne Stroustrup <9758-26353> 0112760 1994-09-30 1:38 ` Tucker Taft 1994-09-30 12:33 ` Bjarne Stroustrup <9758-26353> 0112760 1994-09-29 18:37 ` John Volan 1994-09-29 19:34 ` David Weller 1994-09-30 22:13 ` John Volan 1994-10-02 3:31 ` Andrew Lees 1994-09-30 1:47 ` Tucker Taft 1994-09-30 13:30 ` John Volan 1994-09-29 18:10 ` R. William Beckwith 1994-10-03 0:33 ` Cyrille Comar 1994-09-28 14:01 ` Norman H. Cohen 1994-09-29 2:12 ` John Volan 1994-09-29 14:01 ` Tucker Taft 1994-09-29 18:37 ` Norman H. Cohen 1994-09-29 9:48 ` Magnus Kempe 1994-09-29 13:10 ` Magnus Kempe 1994-09-29 18:05 ` Tucker Taft 1994-09-30 10:20 ` Mut. Recurs. in Ada9X w/o Breaking Encaps.? Magnus Kempe 1994-09-30 13:22 ` Tucker Taft 1994-10-01 1:24 ` Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) Adam Beneschan 1994-10-01 12:01 ` Magnus Kempe 1994-10-01 18:43 ` Mark A Biggar 1994-10-02 16:41 ` John Volan 1994-10-02 23:33 ` Matt Kennel 1994-10-03 8:07 ` Mut. Recurs. in Ada9X w/o Breaking Encaps.? Magnus Kempe 1994-10-03 12:14 ` Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) Robert I. Eachus 1994-10-04 2:12 ` R. William Beckwith 1994-10-04 16:00 ` John Volan 1994-10-05 11:42 ` Robert I. Eachus 1994-10-05 21:09 ` Matt Kennel 1994-10-03 20:29 ` Harry Koehnemann 1994-09-29 13:35 ` John Volan 1994-09-30 20:27 ` Norman H. Cohen 1994-10-01 1:47 ` John Volan 1994-10-01 20:44 ` Tucker Taft 1994-10-03 11:29 ` Robert I. Eachus 1994-09-30 22:46 ` Matt Kennel 1994-10-01 2:11 ` John Volan
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox