* Mutually dependent private types @ 1998-05-21 0:00 adam 1998-05-21 0:00 ` Matthew Heaney 1998-05-21 0:00 ` John Volan 0 siblings, 2 replies; 31+ messages in thread From: adam @ 1998-05-21 0:00 UTC (permalink / raw) I've run into another problem trying to get something implemented in Ada 95. This problem seems superficially similar to John Volan's with-ing problem, but I don't think it's quite the same. (1) If I want to have two private types in two separate packages whose *implementations* are mutually dependent on each other, this is easy to do, if we're willing to make the private types accesses: package P1 is type T1 is private; private type T1_Info; type T1 is access T1_Info; end P1; package P2 is type T2 is private; private type T2_Info; type T2 is access T2_Info; end P2; and this will work since it's legal for P1's package body to depend on P2 and P2's package body to depend on P1. The definitions of P1.T1_Info and P2.T2_Info would contain references to the other package's type. (2) Another mutual-dependency situation is this: Suppose I want to have two types T1 and T2 in two different packages, and I want to provide operations on T1 that take T2 as a parameter, and operations on T2 that take T1 as a parameter. You can't do this by declaring T1 and T1's operations in the same package and do the same for T2, since then the two package specs would have to be mutually dependent. However, you can solve this problem by separating the type definition from the operations in two separate packages (preferably using child units): package P1 is type T1 is ... end P1; package P2 is type T2 is ... end P2; with P2; package P1.Operations is procedure Some_Operation (X : T1; Y : P2.T2); end P1.Operations; with P1; package P2.Operations is procedure Some_Operation (X : T2; Y : P1.T1); end P2.Operations; Actually, you don't have to do this for both packages; you can solve the problem by splitting the operations in just one of the packages. (3) But what happens if you want to do both (1) and (2)? Now the whole thing seems to break down. If you try to do both of the above, something like: package P1 is type T1 is private; private type T1_Info; type T1 is access T1_Info; end P1; with P2; package P1.Operations is procedure Some_Operation (X : T1; Y : P2.T2); end P1.Operations; you can't, because the body of Some_Operation cannot do anything useful with X, since the definition of T1_Info is not visible to it. I've been trying to come up with some way around this problem; for example, I thought about putting some routines in the private part of P1 that P1.Operations could access to give it information about T1_Info, or by setting up another private child package to declare actual type info, but I couldn't quite get anything to work. Does anyone know of a good way to accomplish this, without using Unchecked_Conversion? (It's pretty easy to come up with a solution using Unchecked_Conversion.) I might be missing something obvious here due to my incomplete knowledge of Ada 95. P.S. I believe this would also be solved by Tucker Taft's "with type" proposed language extension. -- thanks, Adam -----== Posted via Deja News, The Leader in Internet Discussion ==----- http://www.dejanews.com/ Now offering spam-free web-based newsreading ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Mutually dependent private types 1998-05-21 0:00 Mutually dependent private types adam @ 1998-05-21 0:00 ` Matthew Heaney 1998-05-22 0:00 ` John Volan 1998-05-21 0:00 ` John Volan 1 sibling, 1 reply; 31+ messages in thread From: Matthew Heaney @ 1998-05-21 0:00 UTC (permalink / raw) In article <6k25ra$6j7$1@nnrp1.dejanews.com>, adam@irvine.com wrote: (start of quote) I've run into another problem trying to get something implemented in Ada 95. This problem seems superficially similar to John Volan's with-ing problem, but I don't think it's quite the same. (1) If I want to have two private types in two separate packages whose *implementations* are mutually dependent on each other, this is easy to do, if we're willing to make the private types accesses: (end of quote) Done. (start of quote) (2) Another mutual-dependency situation is this: Suppose I want to have two types T1 and T2 in two different packages, and I want to provide operations on T1 that take T2 as a parameter, and operations on T2 that take T1 as a parameter. You can't do this by declaring T1 and T1's operations in the same package and do the same for T2, since then the two package specs would have to be mutually dependent. However, you can solve this problem by separating the type definition from the operations in two separate packages (preferably using child units): (end of quote) Done. (start of quote) (3) But what happens if you want to do both (1) and (2)? Now the whole thing seems to break down. If you try to do both of the above, something like: (end of quote) Done. Here's is a solution to your problem. 1) No private child package is required. Move the representation of T1 and T2 up into the private region of a common parent package. 2) Types T1 and T2 privately derive from the respective rep types. Each type, because it's in a child package of package containing both rep types, has direct visibility to the rep of the other type. 3) The mutual dependency in operations is effected by making the other type have type Root'Class, in the spec. In the body, just downcast the object to the representation type. Of course, the compiler can't verify statically that the object is really of the required type, but you'll find out at runtime, because a tag check happens when you downcast. This is out-of-the-box Ada 95. No complex language extensions or Unchecked_Conversion or implementing types using access types is required. I read John's paper, but I don't find his argument convincing. Doesn't the following code solve the putative with'ing "problem"? Matt --STX package body Adam.P1 is procedure Op1 (O1 : in out T1; O2 : in Root'Class) is begin O1.F := Float (T2_Rep (O2).I); end Op1; end Adam.P1; package Adam.P1 is type T1 is new Root with private; procedure Op1 (O1 : in out T1; O2 : in Root'Class); -- precondition: O2 in P2.T2'Class private type T1 is new T1_Rep with null record; end Adam.P1; package body Adam.P2 is procedure Op2 (O2 : in out T2; O1 : in Root'Class) is begin O2.I := Integer (T1_Rep (O1).F); end Op2; end Adam.P2; package Adam.P2 is type T2 is new Root with private; procedure Op2 (O2 : in out T2; O1 : in Root'Class); -- precondition: O1 in P1.T1'Class private type T2 is new T2_Rep with null record; end; with Adam.P1; with Adam.P2; procedure Adam.Test is O1 : P1.T1; O2 : P2.T2; use P1, P2; begin Op1 (O1, O2); Op2 (O2, O1); end Adam.Test; package Adam is pragma Pure; -- the package, not Adam! type Root is abstract tagged null record; private type T1_Rep is new Root with record F : Float; end record; type T2_Rep is new Root with record I : Integer; end record; end Adam; ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Mutually dependent private types 1998-05-21 0:00 ` Matthew Heaney @ 1998-05-22 0:00 ` John Volan 1998-05-22 0:00 ` Matthew Heaney 0 siblings, 1 reply; 31+ messages in thread From: John Volan @ 1998-05-22 0:00 UTC (permalink / raw) Matthew Heaney wrote: > > Here's is a solution to your problem. > > 1) No private child package is required. Move the representation of T1 and > T2 up into the private region of a common parent package. Adam's two types didn't need to know about each other's implementations. Even if you had the P1.T1 type holding a reference (access) to a P2.T2 object, from the perspective of package P1, P2.T2 could still look like a private type. And vice versa in the other direction. > 2) Types T1 and T2 privately derive from the respective rep types. Each > type, because it's in a child package of package containing both rep types, > has direct visibility to the rep of the other type. > > 3) The mutual dependency in operations is effected by making the other type > have type Root'Class, in the spec. In the body, just downcast the object > to the representation type. Of course, the compiler can't verify > statically that the object is really of the required type, but you'll find > out at runtime, because a tag check happens when you downcast. See http://bluemarble.net/~jvolan/WithingProblem/FAQ.html#strong_typing, where I describe how the with-ing problem can be avoided, at the expense of Ada95's static strong typing checks, by deriving all your object classes from some universal root type (Objects.Object_Type was my suggestion). > This is out-of-the-box Ada 95. No complex language extensions or > Unchecked_Conversion or implementing types using access types is required. Yes, but unfortunately it forces Ada95 to act a lot like Smalltalk. The programmer presumably knows statically that O1 is always supposed to be a T1 and O2 is always supposed to be a T2, but this coding style prevents the compiler from knowing that. It is very possible that someone could write code that tried to pass something other than a T2 into P1.Op1. Such code would of course be a bug, but it wouldn't be caught until the call to P1.Op1 was actually executed and the tag-check was performed. Depending on the testing scheme used during development, this call may or may not get executed. So this bug could actually go undetected during testing and wind up being reported by an irate end user. > I read John's paper, but I don't find his argument convincing. Doesn't the > following code solve the putative with'ing "problem"? [snip Matt's example] The *_Rep types aren't even necessary, if you're going to go with the universal root type strategy: package Adam is type Root is abstract tagged null record; end Adam; package Adam.P1 is type T1 is new Root with private; procedure Op1 (O1 : in out T1; O2 : in Root'Class); -- precondition: O2 in P2.T2'Class function Get_F (O1 : in T1) return Float; private type T1 is new Root with record F : Float; end record; end Adam.P1; with Adam.P2; package body Adam.P1 is procedure Op1 (O1 : in out T1; O2 : in Root'Class) is begin O1.F := Float (P2.Get_I(P2.T2'Class(O2))); end Op1; function Get_F (O1 : in T1) return Float is begin return O1.F; end Get_F; end Adam.P1; package Adam.P2 is type T2 is new Root with private; procedure Op2 (O2 : in out T2; O1 : in Root'Class); -- precondition: O1 in P1.T1'Class function Get_I (O2 : in T2) return Integer; private type T2 is new Root with record I : Integer; end record; end Adam.P2; with Adam.P1; package body Adam.P2 is procedure Op2 (O2 : in out T2; O1 : in Root'Class) is begin O2.I := Integer (P1.Get_F(P1.T1'Class(O1))); end Op2; function Get_I (O2 : in T2) return Integer is begin return O2.I; end Get_I; end Adam.P2; with Adam.P1; with Adam.P2; procedure Adam.Test is O1 : P1.T1; O2 : P2.T2; begin P1.Op1 (O1, O2); P2.Op2 (O2, O1); end Adam.Test; -- Signature volanSignature = new Signature ( /*name: */ "John G. Volan", /*employer: */ "Raytheon Advanced C3I Systems, San Jose", /*workEmail: */ "johnv@ac3i.dseg.ti.com", /*homeEmail: */ "johnvolan@sprintmail.com", /*selfPlug: */ "Sun Certified Java Programmer", /*twoCents: */ "Java would be even cooler with Ada95's " + "generics, enumerated types, function types, " + "named parameter passing, etc...", /*disclaimer:*/ "These views not packaged in COM.ti.dseg.ac3i, " + "so loading them throws DontQuoteMeError. :-)" ); ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Mutually dependent private types 1998-05-22 0:00 ` John Volan @ 1998-05-22 0:00 ` Matthew Heaney 1998-05-26 0:00 ` Robert I. Eachus 1998-05-26 0:00 ` John Volan 0 siblings, 2 replies; 31+ messages in thread From: Matthew Heaney @ 1998-05-22 0:00 UTC (permalink / raw) In article <3565B105.9BFB4788@ac3i.dseg.ti.com>, John Volan <johnv@ac3i.dseg.ti.com> wrote: >> 3) The mutual dependency in operations is effected by making the other type >> have type Root'Class, in the spec. In the body, just downcast the object >> to the representation type. Of course, the compiler can't verify >> statically that the object is really of the required type, but you'll find >> out at runtime, because a tag check happens when you downcast. > >See http://bluemarble.net/~jvolan/WithingProblem/FAQ.html#strong_typing, >where I describe how the with-ing problem can be avoided, at the expense >of Ada95's static strong typing checks, by deriving all your object >classes from some universal root type (Objects.Object_Type was my >suggestion). Yes, but I don't agree that all tagged types must derive from the _same_ root type. Each hierarchy can have a different root type. >> This is out-of-the-box Ada 95. No complex language extensions or >> Unchecked_Conversion or implementing types using access types is required. > >Yes, but unfortunately it forces Ada95 to act a lot like Smalltalk. But only because you derive every tagged type from the same root type (a la Smalltalk). In that case, you really are throwing _all_ static type checks out the window. There is no need for that. Simply create a different root type for each hierarchy. >The >programmer presumably knows statically that O1 is always supposed to be >a T1 and O2 is always supposed to be a T2, but this coding style >prevents the compiler from knowing that. It is very possible that >someone could write code that tried to pass something other than a T2 >into P1.Op1. If you use different type hierarchies, then the client could only accidently pass types in the T2 hierarchy. But this is very unlikely, for example package T2_Roots is type NO_ONE_DERIVE_FROM_THIS_EXCEPT_P2_T2 is abstract tagged null record; end; with T2_Roots; package P2 is type T2 is new T2_Roots.NO_ONE_DERIVE_FROM_THIS_EXCEPT_P2_T2 with private; ... end; Hopefully it will be obvious that package T2_Roots is only for use by P2! Of course, if, during a code walk-through, you see this with T2_Roots; package P3 is type T is new T2_Roots.NO_ONE_DERIVE_FROM_THIS_EXCEPT_P2_T2 with private; ... end; then you have bigger problems than mere language omissions. >Such code would of course be a bug, but it wouldn't be >caught until the call to P1.Op1 was actually executed and the tag-check >was performed. Depending on the testing scheme used during development, >this call may or may not get executed. So this bug could actually go >undetected during testing and wind up being reported by an irate end >user. There are constraint checks for all additions, subtractions, etc. There are index checks for array dereferences. There are storage checks during allocation. etc. One extra tag check isn't going to make a difference (especially since you could suppress the check, knowing that ONLY P2.T2 derives from T2_Root). Remember, the chain of mutual dependencies only needs to be broken in one place. Adam's problem could have been solved simply as with T2_Roots; package P1 is type T1 is private; subtype T2 is T2_Roots.NO_ONE_DERIVE_FROM_THIS_EXCEPT_P2_T2'Class; procedure Op1 (O1 : T1; O2 : T2); ... end; with T2_Roots; with P1; package P2 is type T2 is new T2_Roots.NO_ONE_DERIVE_FROM_THIS_EXCEPT_P2_T2; procedure Op2 (O2 : T2; O1 : P1.T1); ... end; Only one type (here, T2) needs to be split into a root & derived type pair. >> I read John's paper, but I don't find his argument convincing. Doesn't the >> following code solve the putative with'ing "problem"? > >[snip Matt's example] > >The *_Rep types aren't even necessary, if you're going to go with the >universal root type strategy: > >package Adam is > type Root is abstract tagged null record; >end Adam; I argue that this is going too far. Each type can have it's own root type, as in package T1_Roots is type T1_Root is abstract tagged null record; end; with T1_Roots; package P1 is type T1 is new T1_Roots.T1_Root with private; ... end; package T2_Roots is type T2_Root is abstract tagged null record; end; with T2_Roots; package P2 is type T2 is new T2_Roots.T2_Root with private; ... end; Furthermore, this split is only required when there's mutual dependency. Most types are definately not mutually dependent, and so this workaround should only seldom be necessary. ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Mutually dependent private types 1998-05-22 0:00 ` Matthew Heaney @ 1998-05-26 0:00 ` Robert I. Eachus 1998-05-26 0:00 ` John Volan 1998-05-26 0:00 ` John Volan 1 sibling, 1 reply; 31+ messages in thread From: Robert I. Eachus @ 1998-05-26 0:00 UTC (permalink / raw) In article <matthew_heaney-ya023680002205981805440001@news.ni.net> matthew_heaney@acm.org (Matthew Heaney) writes: > Furthermore, this split is only required when there's mutual dependency. > Most types are definitely not mutually dependent, and so this workaround > should only seldom be necessary. I would argue, from experience, that every time I have found an apparent need for this mutual dependency, it indicates I have not fully studied the problem. For example in John's example of employees and offices, it quickly becomes obvious that not all rooms are offices, and not all people are employees, so you end up needing at least one of the parent classes for other reasons. Note also that once both types have been declared, the needed operations with the static checks can be declared, you just have this annoying artifact of a visible function which is implicitly required to do a static check at run time. What really is needed is a language independent way to "cast away 'CLASS" and move the check from within the subprogram to a (required) static type check at the location of the call. This certainly can be done by a pragma. In fact you would think that pragma Restrictions (No_Dispatch) would do precisely that, but it is too restrictive, outlawing all cases of 'CLASS. So I suggest that we need a pragma Static_Dispatch which directs the compiler to eliminate dispatching tables for the named unit and to reject any unit which calls the unit in a way that requires dispatching. Of course, if the unit is called from within any other primitive operation of the type, that operation will also require the pragma, so maybe a better solution is to have the pragma apply to (all the primitive operations of) a type, but not to primitive operations of types derived from that type. Since its indended use is for those, usually abstract, parent types in situations like this, the effect would be to outlaw dispatching call using Person'Class or Room'Class, but allow dispatching on Employee'Class or Office'Class. Obviously, implementations can add such a pragma, and if it does become standard, the only remaining question is whether it belongs in Annex H or as a part of the core. -- Robert I. Eachus with Standard_Disclaimer; use Standard_Disclaimer; function Message (Text: in Clever_Ideas) return Better_Ideas is... ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Mutually dependent private types 1998-05-26 0:00 ` Robert I. Eachus @ 1998-05-26 0:00 ` John Volan 1998-05-27 0:00 ` Robert I. Eachus 1998-05-27 0:00 ` Jerry van Dijk 0 siblings, 2 replies; 31+ messages in thread From: John Volan @ 1998-05-26 0:00 UTC (permalink / raw) Robert I. Eachus wrote: > > In article <matthew_heaney-ya023680002205981805440001@news.ni.net> matthew_heaney@acm.org (Matthew Heaney) writes: > > > Furthermore, this split is only required when there's mutual dependency. > > Most types are definitely not mutually dependent, and so this workaround > > should only seldom be necessary. > > I would argue, from experience, that every time I have found an > apparent need for this mutual dependency, it indicates I have not > fully studied the problem. For example in John's example of > employees and offices, it quickly becomes obvious that not all rooms > are offices, and not all people are employees, so you end up needing > at least one of the parent classes for other reasons. Hold on a minute there! You're falling into the "reality" fallacy: That's where one relies too heavily on philosophical (and supposedly "objective") knowledge about "real-world" objects to guide the design of software objects. Software objects shouldn't be grab-bags of irrelevant domain information, they should be encapsulations of the actual responsibilities of an actual target system. Suppose I were to tell you that, for a certain target application, the concepts of "employee" and "office" were entirely sufficient to encapsulate the responsibilities of that application; and that the notion of "person" as a generalization for "employee", and "room" as a generalization for "office", were totally unnecessary and irrelevant. In other words, that system has lots of functional requirements that apply to "employees" and "offices" specifically, but absolutely no requirements that apply to "people" and "rooms" in general. But suppose we still had an unresolvable mutual dependency between the "employee" and "office" classes. Under those circumstances, introducing a "Person_Type" and deriving "Employee_Type" from it would be specious; you might as well just have an "Employee_Forward_Type" and derive "Employee_Type" from that. But suppose for the sake of argument we consider a somewhat different target application, one where the "person" and "room" generalizations are relevant, and we do have some functional requirements that apply to those generalizations. For instance, our application is keeping track of "employees" and "customers", which are both kinds of "persons", and there's something common that we do for all "persons"; likewise, the application tracks "offices", "meeting rooms", "restrooms", etc., all as specializations of "room", and there's something common we do for all "rooms". But let's say we still have other functional requirements that apply to "employee" and "office" specifically, and among those functional requirements there's still a mutual dependency between "employee" and "office". For instance, let's say we can assign an office to an employee, for use as that employee's everyday workspace. For every office we keep track of the assigned employee, and for every employee, we keep track of the assigned office. But an assigned employee has to be an employee, not just any kind of person -- for instance, you can't assign an office to a customer. And an assigned office has to be an office, it can't just be any kind of room -- for instance, you can't put an employee's desk in a meeting room or a restroom. (Well, you might, but that employee wouldn't stay an employee for long... :-) Given the above requirements, the natural thing to do would be to declare the following packages, but they're unfortunately illegal because of the mutual spec dependency: with Persons; with Offices; package Employees is type Employee_Type is new Persons.Person_Type with private; procedure Assign_Office (Employee : in out Employee_Type; Office : in out Offices.Office_Type'Class); function Get_Assigned_Office (Employee : in Employee_Type) return Offices.Office_Type'Class; ... end Employees; with Rooms; with Employees; package Offices is type Office_Type is new Rooms.Room_Type with private; procedure Assign_Employee (Office : in out Office_Type; Employee : in out Employees.Employee_Type'Class); function Get_Assigned_Employee (Office : in Office_Type) return Employees.Employee_Type'Class; ... end Offices; The following avoids the mutual spec dependency, but it loses something: with Persons; with Rooms; package Employees is type Employee_Type is new Persons.Person_Type with private; procedure Assign_Office (Employee : in out Employee_Type; Office : in out Rooms.Room_Type'Class); -- precondition: Office in Offices.Office_Type'Class function Get_Assigned_Office (Employee : in Employee_Type) return Rooms.Room_Type'Class; -- postcondition: return result in Offices.Office_Type'Class; ... end Employees; with Rooms; with Persons; package Offices is type Office_Type is new Rooms.Room_Type with private; procedure Assign_Employee (Office : in out Office_Type; Employee : in out Persons.Person_Type'Class); -- precondition: Employee in Employees.Employee_Type function Get_Assigned_Employee (Office : in Office_Type) return Persons.Person_Type'Class; -- postcondition: return result in Employees.Employee_Type'Class ... end Offices; You might as well go with an approach a la Norman Cohen: with Persons; package Employees_Forward is type Employee_Forward_Type is abstract new Persons.Person_Type with null record; end Employees_Forward; with Rooms; package Offices_Forward is type Office_Forward_Type is abstract new Rooms.Room_Type with null record; end Offices_Forward; with Employees_Forward; with Offices_Forward; package Employees is type Employee_Type is new Employees_Forward.Employee_Forward_Type with private; procedure Assign_Office (Employee : in out Employee_Type; Office : in out Offices_Forward.Office_Forward_Type'Class); function Get_Assigned_Office (Employee : in Employee_Type) return Offices_Forward.Office_Forward_Type'Class; ... end Employees; with Offices_Forward; with Employees_Forward; package Offices is type Office_Type is new Offices_Forward.Office_Forward_Type with private; procedure Assign_Employee (Office : in out Office_Type; Employee : in out Employees_Forward.Employee_Forward_Type'Class); function Get_Assigned_Employee (Office : in Office_Type) return Employees_Forward.Employee_Forward_Type'Class; ... end Offices; > Note also that once both types have been declared, the needed > operations with the static checks can be declared, you just have this > annoying artifact of a visible function which is implicitly required > to do a static check at run time. What really is needed is a language > independent way to "cast away 'CLASS" and move the check from within > the subprogram to a (required) static type check at the location of > the call. This certainly can be done by a pragma. In fact you would > think that pragma Restrictions (No_Dispatch) would do precisely that, > but it is too restrictive, outlawing all cases of 'CLASS. > > So I suggest that we need a pragma Static_Dispatch which directs > the compiler to eliminate dispatching tables for the named unit and to > reject any unit which calls the unit in a way that requires > dispatching. Of course, if the unit is called from within any other > primitive operation of the type, that operation will also require the > pragma, so maybe a better solution is to have the pragma apply to (all > the primitive operations of) a type, but not to primitive operations > of types derived from that type. Since its indended use is for those, > usually abstract, parent types in situations like this, the effect > would be to outlaw dispatching call using Person'Class or Room'Class, > but allow dispatching on Employee'Class or Office'Class. This makes no sense to me. The entire raison d'etre for abstract classes such as Person and Room would be precisely for polymorphic dynamic dispatching! Surely the kind of pragma you suggest should be applied to "spurious" superclasses that are meant to act merely as forward type declarations, e.g., Employee_Forward_Type and Office_Forward_Type. But then neither of those Forward_Types would have any primitives. The whole point to the Forward_Types was to defer defining any primitives for employees or offices until the actual types could be declared. -- Signature volanSignature = new Signature ( /*name: */ "John G. Volan", /*employer: */ "Raytheon Advanced C3I Systems, San Jose", /*workEmail: */ "johnv@ac3i.dseg.ti.com", /*homeEmail: */ "johnvolan@sprintmail.com", /*selfPlug: */ "Sun Certified Java Programmer", /*twoCents: */ "Java would be even cooler with Ada95's " + "generics, enumerated types, function types, " + "named parameter passing, etc...", /*disclaimer:*/ "These views not packaged in COM.ti.dseg.ac3i, " + "so loading them throws DontQuoteMeError. :-)" ); ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Mutually dependent private types 1998-05-26 0:00 ` John Volan @ 1998-05-27 0:00 ` Robert I. Eachus 1998-05-29 0:00 ` John Volan 1998-05-27 0:00 ` Jerry van Dijk 1 sibling, 1 reply; 31+ messages in thread From: Robert I. Eachus @ 1998-05-27 0:00 UTC (permalink / raw) In article <356B6D65.BD34E3EF@ac3i.dseg.ti.com> John Volan <johnv@ac3i.dseg.ti.com> writes: > Hold on a minute there! You're falling into the "reality" fallacy: > That's where one relies too heavily on philosophical (and supposedly > "objective") knowledge about "real-world" objects to guide the design of > software objects. Software objects shouldn't be grab-bags of irrelevant > domain information, they should be encapsulations of the actual > responsibilities of an actual target system. You are reasoning from my specific example that I am relying on reality as an inspiration. Most often the need for an enclosing types comes from the need to add mix-ins. Yes you can hide the "real" parent type in the private part in some cases, but in Ada 95, the real parent type is often Ada.Finalization.Controlled. > This makes no sense to me. The entire raison d'etre for abstract > classes such as Person and Room would be precisely for polymorphic > dynamic dispatching! Surely the kind of pragma you suggest should be > applied to "spurious" superclasses that are meant to act merely as > forward type declarations, e.g., Employee_Forward_Type and > Office_Forward_Type. I may not have been clear in what I said, but that is exactly the intent. For example, take your declaration: function Get_Assigned_Employee (Office : in Office_Type) return Persons.Person_Type'Class; The pragma wouldn't disallow the declaration, and since this function does not dispatch on Employees.Employee_Type'Class, there is no problem in the declaration. In the body, you will return a value of Employees.Employee_Type'Class, again no problem. But assume that for some reason you want to have a class-wide variable in there, and dispatch on it: Employee: Persons.Person_Type'Class := Office.Occupant; So far also okay. But if you call some operation on Employee: Check_Assignment(Office,Employee); That call, even if it matched the template, would be illegal, you would have to say: Check_Assignment(Office, Employees.Employee_Type(Employee)); Or better, put the coercion in the local variable declaration: Employee: Employees.Employees_Type'Class := Office.Occupant; -- Robert I. Eachus with Standard_Disclaimer; use Standard_Disclaimer; function Message (Text: in Clever_Ideas) return Better_Ideas is... ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Mutually dependent private types 1998-05-27 0:00 ` Robert I. Eachus @ 1998-05-29 0:00 ` John Volan 0 siblings, 0 replies; 31+ messages in thread From: John Volan @ 1998-05-29 0:00 UTC (permalink / raw) Robert I. Eachus wrote: > > In article <356B6D65.BD34E3EF@ac3i.dseg.ti.com> John Volan <johnv@ac3i.dseg.ti.com> writes: > > > This makes no sense to me. The entire raison d'etre for abstract > > classes such as Person and Room would be precisely for polymorphic > > dynamic dispatching! Surely the kind of pragma you suggest should be > > applied to "spurious" superclasses that are meant to act merely as > > forward type declarations, e.g., Employee_Forward_Type and > > Office_Forward_Type. > > I may not have been clear in what I said, but that is exactly the > intent. For example, take your declaration: > > function Get_Assigned_Employee (Office : in Office_Type) > return Persons.Person_Type'Class; > > The pragma wouldn't disallow the declaration, and since this > function does not dispatch on Employees.Employee_Type'Class, there is > no problem in the declaration. Of course Ada95 has no "problem" with this declaration -- but I have a problem with this declaration, because what I'd really want is: function Get_Assigned_Employee (Office : in Office_Type) return Employees.Employee_Type'Class; > In the body, you will return a value > of Employees.Employee_Type'Class, again no problem. Again, Ada95 has no problem implicitly upcasting an Employee_Type'Class object to Person_Type'Class -- but I have a problem with that, because the upcast is spurious. This subprogram is contracting to return specifically an Employee, not just any kind of Person, but Ada95 prevents me from expressing that. > But assume that > for some reason you want to have a class-wide variable in there, and > dispatch on it: > > Employee: Persons.Person_Type'Class := Office.Occupant; I assume you mean that Office_Type was implemented as: type Office_Type is new Rooms.Room_Type with record ... Occupant : Persons.Person_Type'Class; ... end record; Actually it would probably be: type Office_Type is new Rooms.Room_Type with record ... Occupant : Persons.Person_Access_Type; -- (access to classwide) ... end record; Unfortunately, what I really wanted was: type Office_Type is new Rooms.Room_Type with record ... Occupant : Employees.Employee_Access_Type; ... end record; > So far also okay. But if you call some operation on Employee: > > Check_Assignment(Office,Employee); > > That call, even if it matched the template, would be illegal, you > would have to say: > > Check_Assignment(Office, Employees.Employee_Type(Employee)); You probably meant: Check_Assignment(Office, Employees.Employee_Type'Class(Employee)); But what is the "template"? If you're saying Check_Assignment is declared as: procedure Check_Assignment (Office : in Office_Type; Employee : in Employees.Employee_Type'Class); then of course you have to do an explicit downcast from Persons.Person_Type'Class to Emploeyes.Employee_Type'Class. That's Ada95, no new pragma needed. But it's impossible to declare Check_Assignment this way in the spec of package Offices. However, if you're saying Check_Assignment is declared as: procedure Check_Assignment (Office : in Office_Type; Employee : in Persons.Person_Type'Class); Then your pragma must somehow magically assert, without having any visibility to the Employees package, that the Employee parameter is actually Employee.Employee_Type'Class, even though Ada95 only allowed you to say Persons.Person_Type'Class. > Or better, put the coercion in the local variable declaration: > > Employee: Employees.Employees_Type'Class := Office.Occupant; You probably meant: Employee : Employees.Employee_Type'Class := Employees.Employee_Type'Class(Office.Occupant); However, the point is that I shouldn't have had to upcast and downcast at all. The Employee/Occupant should have just remained an Employees.Employee_Type'Class object at all times. -- Signature volanSignature = new Signature ( /*name: */ "John G. Volan", /*employer: */ "Raytheon Advanced C3I Systems, San Jose", /*workEmail: */ "johnv@ac3i.dseg.ti.com", /*homeEmail: */ "johnvolan@sprintmail.com", /*selfPlug: */ "Sun Certified Java Programmer", /*twoCents: */ "Java would be even cooler with Ada95's " + "generics, enumerated types, function types, " + "named parameter passing, etc...", /*disclaimer:*/ "These views not packaged in COM.ti.dseg.ac3i, " + "so loading them throws DontQuoteMeError. :-)" ); ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Mutually dependent private types 1998-05-26 0:00 ` John Volan 1998-05-27 0:00 ` Robert I. Eachus @ 1998-05-27 0:00 ` Jerry van Dijk 1998-05-29 0:00 ` John Volan 1 sibling, 1 reply; 31+ messages in thread From: Jerry van Dijk @ 1998-05-27 0:00 UTC (permalink / raw) John Volan (johnv@ac3i.dseg.ti.com) wrote: [...] : software objects. Software objects shouldn't be grab-bags of irrelevant : domain information, they should be encapsulations of the actual : responsibilities of an actual target system. [...] : "office". For instance, let's say we can assign an office to an : employee, for use as that employee's everyday workspace. For every : office we keep track of the assigned employee, and for every employee, : we keep track of the assigned office. But an assigned employee has to So perhaps you just found a new class: Office_Employee_List :-) Jerry. -- -- Jerry van Dijk | email: jdijk@acm.org -- Leiden, Holland | member Team-Ada ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Mutually dependent private types 1998-05-27 0:00 ` Jerry van Dijk @ 1998-05-29 0:00 ` John Volan 0 siblings, 0 replies; 31+ messages in thread From: John Volan @ 1998-05-29 0:00 UTC (permalink / raw) Jerry van Dijk wrote: > > So perhaps you just found a new class: > > Office_Employee_List > > :-) This was offered in jest, but let's consider it seriously: One way to implement an cross-referencing association between two classes like Employee and Office could be to keep both classes ignorant of the association and just have a third class that is entirely responsible for keeping track of all the cross-references (in some sort of table structure). Yes, that is a valid design alternative, but there are tradeoffs, and it doesn't work for all applications. In particular, if Office and Employee have primitive/inheritable/overridable operations that take parameters of each other's type, then there is no way that the two classes can avoid knowing about the association. Also, this kind of cross-referencing association isn't the only way two classes might be forced into mutual dependency. My Doctors and Patients example in my FAQ didn't involve any cross-referencing between Doctor and Patient objects, but it did involve mutually-dependent primitive operations. -- Signature volanSignature = new Signature ( /*name: */ "John G. Volan", /*employer: */ "Raytheon Advanced C3I Systems, San Jose", /*workEmail: */ "johnv@ac3i.dseg.ti.com", /*homeEmail: */ "johnvolan@sprintmail.com", /*selfPlug: */ "Sun Certified Java Programmer", /*twoCents: */ "Java would be even cooler with Ada95's " + "generics, enumerated types, function types, " + "named parameter passing, etc...", /*disclaimer:*/ "These views not packaged in COM.ti.dseg.ac3i, " + "so loading them throws DontQuoteMeError. :-)" ); ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Mutually dependent private types 1998-05-22 0:00 ` Matthew Heaney 1998-05-26 0:00 ` Robert I. Eachus @ 1998-05-26 0:00 ` John Volan 1998-05-26 0:00 ` Matthew Heaney 1 sibling, 1 reply; 31+ messages in thread From: John Volan @ 1998-05-26 0:00 UTC (permalink / raw) Matthew Heaney wrote: > > In article <3565B105.9BFB4788@ac3i.dseg.ti.com>, John Volan > <johnv@ac3i.dseg.ti.com> wrote: > > >> 3) The mutual dependency in operations is effected by making the other type > >> have type Root'Class, in the spec. In the body, just downcast the object > >> to the representation type. Of course, the compiler can't verify > >> statically that the object is really of the required type, but you'll find > >> out at runtime, because a tag check happens when you downcast. > > > >See http://bluemarble.net/~jvolan/WithingProblem/FAQ.html#strong_typing, > >where I describe how the with-ing problem can be avoided, at the expense > >of Ada95's static strong typing checks, by deriving all your object > >classes from some universal root type (Objects.Object_Type was my > >suggestion). > > Yes, but I don't agree that all tagged types must derive from the _same_ > root type. Each hierarchy can have a different root type. Matt, in your original example, you had T1 and T2 (and T1_Rep and T2_Rep) all derive from a common Root type. That looks a lot like the idea of having a universal root type that everything derives from. (My real objection to your example was that you declared T1_Rep and T2_Rep in a common *package*, which I thought was unnecessary.) But if you want to go in the other direction, sure, you could have these more focused "root" types act as forward declarations for specific "actual" types. Norman Cohen originally came up with that idea: Using inheritance as a mechanism for doing forward type declarations. I still have the same objections to that scheme that I had before: IMHO, Ada95's inheritance was really "intended" to support polymorphic programming, where your problem domain clearly calls for a common generalized abstraction implemented by multiple specialized abstractions. But a forward declaration is the exact *opposite* of polymorphism: A forward type declaration is supposed to bear a one-to-one correspondence with a full type declaration. Simulating a forward type declaration with a spurious extra level of inheritance creates a situation where the compiler thinks you want polymorphism, but you actually don't. As you point out yourself, this spurious polymorphism creates an opportunity where someone could try (either accidentally or maliciously) to provide more than one full type for the same "forward" type. Furthermore, what if you're already using inheritance to support true polymorphism, and then you discover you need to break a mutual dependency somewhere in your class hierarchy? You might end up with a situation I call "inheritance collision": That's where you're trying to apply Ada's inheritance mechanism for two competing purposes, but you're faced with the fact that Ada only supports single inheritance. Sure, it's possible to wiggle your inheritance hierarchy around so that it'll work and you can still keep your mutually-dependent types in different packages. But I find that to be awkward and artificial. Bottom line, I don't think anyone can legitimately claim that Ada95 offers a solution to mutual dependency problems "right out of the box". All you can say is that there are various workarounds, with varying degrees of awkwardness, that can be used to make up for a fundamental hole in the language. [Snip my objection to the loss of strong type checking and the spurious extra tag check...] > There are constraint checks for all additions, subtractions, etc. There > are index checks for array dereferences. There are storage checks during > allocation. etc. One extra tag check isn't going to make a difference I'm not sure you can make an analogy with constraint, index, and storage checks. In general, you can't know statically when an arithmetic operation is going to violate a constraint, or when you're going to run out of memory, and so on, so all of those dynamic checks are necessary. But you can certainly know statically what strong types you're expecting in your variables and subprogram parameters. Type-checking should only become a dynamic thing when you deliberately set up an inheritance hierarchy and do polymorphic class-wide programming. But a simple case of mutual dependency shouldn't call for polymorphism. > Furthermore, this split is only required when there's mutual dependency. > Most types are definately not mutually dependent, and so this workaround > should only seldom be necessary. Depends on the style of software design you're using, which depends a lot on your programming culture. Java, for instance, doesn't place any impediments on mutual dependencies, so there are several cases of mutual dependencies already in the standard Java API. My understanding is that this has caused problems for folks trying to use Ada95 as a front-end language for the Java Virtual Machine. I haven't done much with Ada95-to-JVM compilers (like AdaMagic), so I don't know what solutions they've come up with for that. Maybe Tucker can comment... -- Signature volanSignature = new Signature ( /*name: */ "John G. Volan", /*employer: */ "Raytheon Advanced C3I Systems, San Jose", /*workEmail: */ "johnv@ac3i.dseg.ti.com", /*homeEmail: */ "johnvolan@sprintmail.com", /*selfPlug: */ "Sun Certified Java Programmer", /*twoCents: */ "Java would be even cooler with Ada95's " + "generics, enumerated types, function types, " + "named parameter passing, etc...", /*disclaimer:*/ "These views not packaged in COM.ti.dseg.ac3i, " + "so loading them throws DontQuoteMeError. :-)" ); ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Mutually dependent private types 1998-05-26 0:00 ` John Volan @ 1998-05-26 0:00 ` Matthew Heaney 1998-05-27 0:00 ` John Volan 0 siblings, 1 reply; 31+ messages in thread From: Matthew Heaney @ 1998-05-26 0:00 UTC (permalink / raw) John Volan <johnv@ac3i.dseg.ti.com> writes: > Matt, in your original example, you had T1 and T2 (and T1_Rep and > T2_Rep) all derive from a common Root type. That looks a lot like the > idea of having a universal root type that everything derives from. (My > real objection to your example was that you declared T1_Rep and T2_Rep > in a common *package*, which I thought was unnecessary.) You are correct - I was wrong to do this is my original response. Note that I declared T1_Rep and T2_Rep in the same package because I thought Adam required that each type have visibility to the other type's representation. If that wasn't the case, then I agree with you that it's better to move each "forward" type into its own package. > As you point out yourself, this spurious > polymorphism creates an opportunity where someone could try (either > accidentally or maliciously) to provide more than one full type for the > same "forward" type. Yes, but let's talk about reality. While it's _theoretically_ possible for a programmer to wrongly inherit from a forward type, in practice it'll never happen. package Employee_Forward is type ONLY_EMPLOYEE_BETTER_INHERIT_FROM_THIS is abstract new Person_Type with null record; end; > Furthermore, what if you're already using inheritance to support true > polymorphism, and then you discover you need to break a mutual > dependency somewhere in your class hierarchy? You might end up with a > situation I call "inheritance collision": That's where you're trying to > apply Ada's inheritance mechanism for two competing purposes, but you're > faced with the fact that Ada only supports single inheritance. Sure, > it's possible to wiggle your inheritance hierarchy around so that it'll > work and you can still keep your mutually-dependent types in different > packages. But I find that to be awkward and artificial. If you have deep or otherwise "complex" type hierarchies in Ada, then you're doing something wrong. Hierarchies should be shallow. When I hear stories about problems someone is having with their inheritance hierarchies, I become suspicious, because it often indicates that the programmer is fighting the langauge. The "proper" style of object oriented design advocated in popular books and journals mostly does NOT apply to Ada 95. Much of the advice out there (especially wrt inheritance) applies only to languages in which the size of the object is known up front (ie the type has reference semantics, and therefore it's just an address). This is not the case for Ada, because types in Ada have value semantics, and so their size isn't known up front. This makes a type low in the hierarchy _extremely_sensitive_ to perturbations of ancestor types. As I explained in a post to cla a few months back, this seemingly innocuous detail has overarching consequences for the design of software systems written using Ada. Very few Ada programmers really understand this, and they often have "problems" with their inheritance hierarchies, compilation depencies, etc, etc. (A friend mine who came from a C background noted that you can always tell who the Ada programmers are, because "they're the ones reading a newspaper at their terminal." He was observing that Ada programmers often have to wait a long time for their compiles to finish - a sure sign of design problem.) > Bottom line, I don't think anyone can legitimately claim that Ada95 > offers a solution to mutual dependency problems "right out of the box". > All you can say is that there are various workarounds, with varying > degrees of awkwardness, that can be used to make up for a fundamental > hole in the language. I'm not disagreeing that "workarounds" are required, but I feel this whole "with'ing problem" has been blown way, way out of proportion. The technique described in Norm's book is very simple, and doesn't _in_practice_ compromise type safety. > But a simple case > of mutual dependency shouldn't call for polymorphism. I'm not disagreeing, but what are you going to do? This is the language we've got _today_, so accept it. If it bothers you that much, then turn the tag check off! > Depends on the style of software design you're using, which depends a > lot on your programming culture. I'm talking about Ada 95. That there are design idioms specific to Ada, and that one has to actually go out and learn these idioms, doesn't bother me. If your inheritance hierarchies are causing you problems, then it's a sign that maybe your design is not suitable for implementation in Ada. I'm a firm believer in designing _to_ the language. I'm not disagreeing that, because you can't have mutual dependencies in Ada, that this can cause an unnatural mapping to a language (as Java) that does allow mutual depencency. And I'm not disagreeing that this is a Bad Thing, especially since Ada is advertised as "easily" interfacing to other languages. But this "withing problem" is just not that big a deal. Matt ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Mutually dependent private types 1998-05-26 0:00 ` Matthew Heaney @ 1998-05-27 0:00 ` John Volan 1998-05-27 0:00 ` Matthew Heaney 0 siblings, 1 reply; 31+ messages in thread From: John Volan @ 1998-05-27 0:00 UTC (permalink / raw) Matthew Heaney wrote: > > John Volan <johnv@ac3i.dseg.ti.com> writes: > > > Furthermore, what if you're already using inheritance to support true > > polymorphism, and then you discover you need to break a mutual > > dependency somewhere in your class hierarchy? You might end up with a > > situation I call "inheritance collision": That's where you're trying to > > apply Ada's inheritance mechanism for two competing purposes, but you're > > faced with the fact that Ada only supports single inheritance. Sure, > > it's possible to wiggle your inheritance hierarchy around so that it'll > > work and you can still keep your mutually-dependent types in different > > packages. But I find that to be awkward and artificial. > > If you have deep or otherwise "complex" type hierarchies in Ada, then > you're doing something wrong. Hierarchies should be shallow. > > When I hear stories about problems someone is having with their > inheritance hierarchies, I become suspicious, because it often indicates > that the programmer is fighting the langauge. While I wholeheartedly agree with this sentiment, it has little to do with the situation we have here. Sure, if an inheritance collision is caused by a flawed analysis of the problem domain, you should question the designer's choice of classes. But here we're talking about inheritance collision caused by the attempt to apply Ada95's inheritance mechanism to resolve a mutual dependency *while at the same time* using it to implement a "legitimate" case of polymorphism -- "legitimate" because it's motivated by a real case of generalization/specialization in the problem domain. This polymorphism hierarchy could be quite simple and shallow and meet any criterion of "goodness" you can name, but the mutual dependency could still collide with it. I believe the example I used in the past was: Manager -- supervises -- Employee Manager -- is a kind of -- Employee This is a case where you might have a mutual dependency between a generalization (Employee) and one of its own specializations (Manager). You need Ada95's inheritance mechanism to implement the generalization/specialization relationship; but this might collide with resolving the mutual dependency. Here's our first cut, where we run into the illegal mutual spec dependency: with Managers; -- illegal package Employees is type Employee_Type is abstract tagged limited private; type Employee_Access_Type is access all Employee_Type'Class; function Get_Supervisor (Employee : in Employee_Type) return Managers.Manager_Access_Type; ... end Employees; with Sets; with Employees; package Employee_Sets is new Sets (Element => Employees.Employee_Access_Type); with Employees; -- illegal package Managers is type Manager_Type is new Employees.Employee_Type with private; type Manager_Access_Type is access all Manager_Type'Class; function Get_Subordinates (Manager : in Manager_Type) return Employee_Sets.Set_Type; ... end Managers; Here's the second cut, where we run into inheritance collision: package Employees_Forward is type Employee_Forward_Type is abstract tagged limited null record; type Employee_Access_Type is access all Employee_Forward_Type'Class; end Employees_Forward; package Managers_Forward is type Manager_Forward_Type is abstract tagged limited null record; type Manager_Access_Type is access all Manager_Forward_Type'Class; end Managers_Forward; with Employees_Forward; with Sets; package Employee_Sets is new Sets (Element => Employees_Forward.Employee_Access_Type); with Employees_Forward; with Managers_Forward; package Employees is type Employee_Type is abstract new Employees_Forward.Employee_Forward_Type with private; function Get_Supervisor (Employee : in Employee_Type) return Managers_Forward.Manager_Access_Type; ... end Employees; with Managers_Forward; with Employees; with Employee_Sets; package Managers is type Manager_Type is -- Need this to resolve the mutual dependency: new Managers_Forward.Manager_Forward_Type with private; -- But need this to implement the generalization/specialization: new Employees.Employee_Type with private; -- Inheritance Collision! function Get_Subordinates (Manager : in Manager_Type) return Employee_Sets.Set_Type; ... end Managers; To resolve the inheritance collision, we have to do some monkeying around: with Employees_Forward; package Employees is type Employee_Type is abstract new Employees_Forward.Employee_Forward_Type with private; -- actually, don't really need Employees_Forward unless there's -- a mutual dependency with something else... type Employee_Access_Type is access all Employee_Type'Class; type Manager_Forward_Type is abstract new Employee_Type with private; type Manager_Access_Type is access all Manager_Forward_Type'Class; function Get_Supervisor (Employee : in Employee_Type) return Manager_Access_Type; ... end Employees; with Employees; with Employee_Sets; package Managers is type Manager_Type is new Employees.Manager_Forward_Type with private; function Get_Subordinates (Manager : in Manager_Type) return Employee_Sets.Set_Type; ... end Managers; Following Norman Cohen's scheme, we had a nice pattern going there with those *_Forward packages. But to avoid inheritance collision, we had to break this pattern and move the Manager_Forward_Type from the Managers_Forward package into the Employees package. By rights, forward type declarations ought to be a language feature that's totally orthogonal to inheritance and polymorphism. This would indeed be true in Tucker's "with type" proposal: with type Managers.Manager_Type; package Managers_Access is type Manager_Access_Type is access all Managers.Manager_Type'Class; end Managers_Access; with type Employees.Employee_Type; package Employees_Access is type Employee_Access_Type is access all Employees.Employee_Type'Class; end Employees_Access; with Employees_Access; with Sets; package Employee_Sets is new Sets (Element => Employees_Access.Employee_Access_Type); with Managers_Access; package Employees is type Employee_Type is abstract tagged limited private; function Get_Supervisor (Employee : in Employee_Type) return Managers_Access.Manager_Access_Type; ... end Employees; with Employees; with Employee_Sets; package Managers is type Manager_Type is new Employees.Employee_Type with private; function Get_Subordinates (Manager : in Manager_Type) return Employee_Sets.Set_Type; ... end Managers; As a workaround, my generic Forward package also manages to keep itself orthogonal to inheritance: with Forward; package Employees_Forward is new Forward; with Forward; package Managers_Forward is new Forward; with Employees_Forward; with Sets; package Employee_Sets is new Sets (Element => Employees_Forward.Reference_Type); with Employees_Forward; with Managers_Forward; package Employees is package Forward renames Employees_Forward; type Employee_Type is abstract tagged limited private; type Employee_Access_Type is access all Employee_Type'Class; function Get_Supervisor (Employee : in Employee_Type) return Managers_Forward.Reference_Type; ... end Employees; with Managers_Forward with Employees; with Employee_Sets; package Managers is package Forward renames Managers_Forward; type Manager_Type is new Employees.Employee_Type with private; type Manager_Access_Type is access all Manager_Type'Class; function Get_Subordinates (Manager : in Manager_Type) return Employee_Sets.Set_Type; ... end Managers; package Employees.Binding is new Employees.Forward.Binding (Employee_Type, Employee_Access_Type); package Managers.Binding is new Managers.Forward.Binding (Manager_Type, Manager_Access_Type); But, although you can twist Norman Cohen's technique around so that it will still work even in the most extreme case, the fact that you have to do that shows that it isn't a very orthogonal workaround. -- Signature volanSignature = new Signature ( /*name: */ "John G. Volan", /*employer: */ "Raytheon Advanced C3I Systems, San Jose", /*workEmail: */ "johnv@ac3i.dseg.ti.com", /*homeEmail: */ "johnvolan@sprintmail.com", /*selfPlug: */ "Sun Certified Java Programmer", /*twoCents: */ "Java would be even cooler with Ada95's " + "generics, enumerated types, function types, " + "named parameter passing, etc...", /*disclaimer:*/ "These views not packaged in COM.ti.dseg.ac3i, " + "so loading them throws DontQuoteMeError. :-)" ); ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Mutually dependent private types 1998-05-27 0:00 ` John Volan @ 1998-05-27 0:00 ` Matthew Heaney 1998-05-28 0:00 ` John Volan 0 siblings, 1 reply; 31+ messages in thread From: Matthew Heaney @ 1998-05-27 0:00 UTC (permalink / raw) John Volan <johnv@ac3i.dseg.ti.com> writes: > I believe the example I used in the past was: > > Manager -- supervises -- Employee > Manager -- is a kind of -- Employee > > This is a case where you might have a mutual dependency between a > generalization (Employee) and one of its own specializations (Manager). > You need Ada95's inheritance mechanism to implement the > generalization/specialization relationship; but this might collide with > resolving the mutual dependency. [snip first cut] > Here's the second cut, where we run into inheritance collision: > > package Employees_Forward is > type Employee_Forward_Type is abstract tagged limited null record; > type Employee_Access_Type is access all Employee_Forward_Type'Class; > end Employees_Forward; > > package Managers_Forward is > type Manager_Forward_Type is abstract tagged limited null record; > type Manager_Access_Type is access all Manager_Forward_Type'Class; > end Managers_Forward; > > with Employees_Forward; > with Sets; > package Employee_Sets is > new Sets (Element => Employees_Forward.Employee_Access_Type); > > with Employees_Forward; > with Managers_Forward; > package Employees is > type Employee_Type is > abstract new Employees_Forward.Employee_Forward_Type with private; > function Get_Supervisor (Employee : in Employee_Type) > return Managers_Forward.Manager_Access_Type; > ... > end Employees; > > with Managers_Forward; > with Employees; > with Employee_Sets; > package Managers is > > type Manager_Type is > -- Need this to resolve the mutual dependency: > new Managers_Forward.Manager_Forward_Type with private; > -- But need this to implement the generalization/specialization: > new Employees.Employee_Type with private; > -- Inheritance Collision! > > function Get_Subordinates (Manager : in Manager_Type) > return Employee_Sets.Set_Type; > ... > end Managers; Let's analyze this. 1) Why do you need two forward declarations? You only need to cut the circle once, and so only N-1 forward declarations are required. 2) The package hierarchy should match the type hierarchy. Type Manager derives from Employee, so why isn't package Managers a child of Employees? 3) Implementing 2) obviates the need for step 1), since Manager can derive from Employee directly. The only thing you'll have to do - and this is where you may object - is do forward declare a root manager type in Employee, to provide the return value of the Get_Supervisor function. Here is code that implements these ideas: package Employees is type Employee_Type is tagged private; type Root_Manager_Type is new Employee_Type with private; type Manager_Access_Type is access all Root_Manager_Type'Class; function Get_Supervisor (Employee : in Employee_Type) return Manager_Access_Type; private type Employee_Type is tagged record Manager : Manager_Access_Type; end record; type Root_Manager_Type is new Employee_Type with null record; end Employees; with Employees.Sets; package Employees.Managers is type Manager_Type is new Root_Manager_Type with private; function Get_Subordinates (Manager : in Manager_Type) return Employees.Sets.Set_Type; private type Manager_Type is new Root_Manager_Type with record Set : Employees.Sets.Set_Type; end record; end Employees.Managers; Doesn't this do what you want? Where's the collision? > To resolve the inheritance collision, we have to do some monkeying > around: > > with Employees_Forward; > package Employees is > type Employee_Type is > abstract new Employees_Forward.Employee_Forward_Type with private; > -- actually, don't really need Employees_Forward unless there's > -- a mutual dependency with something else... > type Employee_Access_Type is access all Employee_Type'Class; > > type Manager_Forward_Type is > abstract new Employee_Type with private; > type Manager_Access_Type is access all Manager_Forward_Type'Class; > > function Get_Supervisor (Employee : in Employee_Type) > return Manager_Access_Type; > ... > end Employees; Oh! This is essentially what I have above, so we're more or less on the same wavelength. > with Employees; > with Employee_Sets; > package Managers is > type Manager_Type is > new Employees.Manager_Forward_Type with private; > function Get_Subordinates (Manager : in Manager_Type) > return Employee_Sets.Set_Type; > ... > end Managers; Again, more or less the same. > Following Norman Cohen's scheme, we had a nice pattern going there with > those *_Forward packages. But to avoid inheritance collision, we had to > break this pattern and move the Manager_Forward_Type from the > Managers_Forward package into the Employees package. Funny, I don't call this organization of types "avoiding inheritance collision by declaring Manager_Forward_Type in Employees package." I just call it "object-oriented programming." I wouldn't have thought about doing it any other way, as it seems perfectly natural to me. This is not unlike the example in my Active Iterators post a week or two ago. You have to "forward declare" the Root_Iterator type right there in the same package as the root stack, ie generic ... package Stacks is type Root_Stack is abstract tagged ...; type Root_Iterator is abstract tagged limited ...; type Iterator_Access is access all Root_Iterator'Class; function New_Iterator (Stack : Root_Stack) return Iterator_Access; ... How else would you do it? This seems like the correct, most natural place to declare Root_Iterator. You don't like declaring the Root_Manager type in the Employees package, but I don't consider this to be a "problem" at all. > By rights, forward type declarations ought to be a language feature > that's totally orthogonal to inheritance and polymorphism. This would > indeed be true in Tucker's "with type" proposal: > > with type Managers.Manager_Type; > package Managers_Access is > type Manager_Access_Type is access all Managers.Manager_Type'Class; > end Managers_Access; At first blush I don't think that "with type" is the way to go. The problem is that it conflicts with an already established pattern wrt to "with" and "use". A neophyte programmer is taught that get access to a package, you "with" the package. And then to get direct visibility to the declarations there, to "use" that package. So the pattern with P; use P; becomes ingrained. So now when he sees this with type P.T; he's going to think that he can say with type P.T; use type P.T; to get direct visibility to the primitive operators of the type P.T. But this is incorrect, because "use type P.T" already means something else. Right? I don't know what to do really. Maybe a pragma be better: with P; pragma Mutual (P); package Q is type QT is ...; procedure QOp (QO : QT; PO : access P.T); ... end Q; Actually, I'd like it _much_ better if I didn't have to pass P.T as an access param. Knowing that P.T is tagged (or just passed by reference), I'd like to state that in Q, so I can do this with P; pragma Mutual_Tagged (P.T); package Q is type QT is ...; procedure QOp (QO : QT; PO : P.T); ... No access parameter is required. I don't know how to handle the case of a type that is only privately tagged, however. Maybe we can allow that aspect of a private type to be stated publically, in the form of a pragma: with P; pragma Mutual_By_Reference (P.T); package Q is ...; with Q; pragma <whatever> (Q); package P is type T is private; ... private type T is new Controlled with ...; end P; That would mean that ... package P is type T is limited private; ... private type T is new Integer; end P; would be illegal for Mutual_By_Reference usage by Q, because T isn't passed by reference. ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Mutually dependent private types 1998-05-27 0:00 ` Matthew Heaney @ 1998-05-28 0:00 ` John Volan 1998-05-28 0:00 ` Matthew Heaney ` (2 more replies) 0 siblings, 3 replies; 31+ messages in thread From: John Volan @ 1998-05-28 0:00 UTC (permalink / raw) Matthew Heaney wrote: > > Let's analyze this. > > 1) Why do you need two forward declarations? You only need to cut the > circle once, and so only N-1 forward declarations are required. Two reasons: (1) Symmetry: Why cut the circle in one direction and not the other? In most bi-directional associations, there's no a priori reason to bias the design in a particular direction, so any such biasing tends to be arbitrary. Ada95's "with"-ing problem seems to force programmers into favoring an asymmetrical solution, whereas other languages tolerate a perfectly symmetrical pattern. I will concede though that an inheritance relationship between two classes does introduce a bias (in Ada95): In order to derive Manager_Type from Employee_Type, Manager_Type needs to see Employee_Type directly, not just a forward declaration of Employee_Type. However, as I've said before, the issue of mutual dependency to me is orthogonal to the issue of inheritance and polymorphism, so why should the fact that two classes happen to be related by inheritance affect the way mutual dependency is resolved? (2) Scalability: It's short-sighted to think that this would be the only "circle" that these particular classes might be participating in. Declaring Employee_Forward_Type and Manager_Forward_Type doesn't just help solve the mutual dependency dilemma between those two types, it solves any mutual dependency dilemma with any other third-party type that might come along. But why should such a third-party type have to look in package Employees for a Manager_Forward_Type, when Norman Cohen's pattern would suggest it should be found in a Managers_Forward package? > 2) The package hierarchy should match the type hierarchy. Type Manager > derives from Employee, so why isn't package Managers a child of > Employees? This is an entirely orthogonal issue you're bringing up. Should a type hierarchy always be mirrored by the package hierarchy that encapsulates it? No, not necessarily, IMHO. Ada95 is a modular language: Type hierarchies support issues of polymorphism and inheritance, whereas package hierarchies support issues of visibility and namespace control. A derived type should be declared in a child package *if* you want it to have visibility to its parent type's implementation details; it should be declared in a different package if you *don't* want it to have that visibility. Whether you want to grant such visibility or not ... depends. It's somewhat analogous to the question, in other OO languages, of whether to make a certain feature of a class "private" (visible to absolutely nobody else) or "protected" (visible only to subclasses). It's a design choice. But that's a very different issue from mutual spec dependency. > 3) Implementing 2) obviates the need for step 1), since Manager can > derive from Employee directly. I don't see how 2) has anything to do with it. Manager_Type can derive from Employee_Type regardless of whether the Managers package is a child of the Employees package, or whether it simply "withs" the Employees package. The only difference is whether private stuff about Employee_Type is directly visible to Manager_Type. > The only thing you'll have to do - and this is where you may object - is > do forward declare a root manager type in Employee, to provide the > return value of the Get_Supervisor function. [snip Matt's example, essentially the same as mine...] > Doesn't this do what you want? Where's the collision? > > > To resolve the inheritance collision, we have to do some monkeying > > around: [snip my example...] > Oh! This is essentially what I have above, so we're more or less > on the same wavelength. [snip] > > Following Norman Cohen's scheme, we had a nice pattern going there with > > those *_Forward packages. But to avoid inheritance collision, we had to > > break this pattern and move the Manager_Forward_Type from the > > Managers_Forward package into the Employees package. > > Funny, I don't call this organization of types "avoiding inheritance > collision by declaring Manager_Forward_Type in Employees package." I > just call it "object-oriented programming." I wouldn't have thought > about doing it any other way, as it seems perfectly natural to me. Look, these days the term "object-oriented" has become so overused that its meaning has been reduce to little more than "good". When someone wants to indicate that their favorite idiom is "good", they'll label it "object-oriented". So basically what you're saying is, you think it's "good programming" to use inheritance as a way of simulating a forward type declaration, even if inheritance collision forces you to break the pattern and put a forward type declaration in an unexpected place. Well, that's your opinion, but I have to disagree with it. > This is not unlike the example in my Active Iterators post a week or two > ago. You have to "forward declare" the Root_Iterator type right there > in the same package as the root stack, ie > > generic > ... > package Stacks is > > type Root_Stack is abstract tagged ...; > > type Root_Iterator is abstract tagged limited ...; > > type Iterator_Access is access all Root_Iterator'Class; > > function New_Iterator > (Stack : Root_Stack) return Iterator_Access; > ... > > How else would you do it? This seems like the correct, most natural > place to declare Root_Iterator. > > You don't like declaring the Root_Manager type in the Employees package, > but I don't consider this to be a "problem" at all. I don't think these cases are entirely comparable. First of all, your Stacks package is a generic, which throws a whole new factor into the equation. Separately encapsulating two abstractions is a lot harder when they both have to be governed by the same generic formal parameters. (To some extent, it can still be done: You can have two packages, but they'd both have to be nested inside the generic package.) Secondly, the Employee and Manager classes are related to each other by inheritance, but aside from that they are as loosely coupled to each other (conceptually) as any other pair of problem-domain classes (for instance, Employee and Office). Stack and Iterator are not related to each other by inheritance, but despite that they are very tightly coupled to each other. In fact, to implement Stack and Iterator, it's likely that both classes will need access to each other's implementation details, which argues for putting them in the same package. But I don't see any need for the Manager subclass to *necessarily* have access to private details about the Employee superclass, or vice versa. (They may or may not need such access, depending on other factors in the design, but not simply because there's a mutual spec dependency. The assumption in my examples has been that the classes involved *don't* need access to each other's implementation details.) > At first blush I don't think that "with type" is the way to go. > > The problem is that it conflicts with an already established pattern wrt > to "with" and "use". A neophyte programmer is taught that get access to > a package, you "with" the package. And then to get direct visibility to > the declarations there, to "use" that package. Come now, that's like objecting to using "for" in a rep spec in a declarative part because that would confuse neophytes who are used to using "for" in a loop statement in a statement sequence. You realize, of course, that it was Tucker Taft himself who originally came up with the "with type" idea. If anybody is a good judge of what language extensions would fit well into the Ada scheme of things, I'd imagine he'd be right up there... :-) > So the pattern > > with P; use P; > > becomes ingrained. Who says? There are at least as many Ada folks who would vigorously discourage a neophyte from getting into the "use" habit as there are those who would encourage him! (Probably a lot more.) Anyway, neophytes and expert programmers alike should be encouraged NOT to program simply from ingrained habit, but from a considered understanding of the rationale and tradeoffs behind the various constructs and idioms available in a given language. > So now when he sees this > > with type P.T; > > he's going to think that he can say > > with type P.T; use type P.T; > > to get direct visibility to the primitive operators of the type P.T. > > But this is incorrect, because "use type P.T" already means something > else. Right? No, it means exactly "direct visibility to the primitive operators of the type P.T". This situation was actually considered when "with type" was first discussed. I believe the conclusion was to allow "use type" but to make it ineffectual, on the grounds that there aren't yet any primitive P.T operators to make visible, since P.T is still just an incomplete type declaration at this point. > I don't know what to do really. Maybe a pragma be better: > > with P; > pragma Mutual (P); > package Q is It seems to me that if there's a mutual spec dependency going on between P and Q, then the "with" clause here is already illegal according to Ada95's language rules. So what exactly would this "Mutual" pragma mean? "I know the with clause is illegal, but ignore that and pretend to be a different language"? :-) What about Ada95's run-time elaboration mechanism? When would the elaboration code for the specs of packages P and Q get executed? In what order? > Actually, I'd like it _much_ better if I didn't have to pass P.T as an > access param. Knowing that P.T is tagged (or just passed by reference), > I'd like to state that in Q, so I can do this > > with P; > pragma Mutual_Tagged (P.T); See http://bluemarble.net/~jvolan/WithingProblem/FAQ.html#liberal_incompletes for a discussion of Tucker's proposal to liberalize the rules for incomplete types, to allow their use for parameters and return types. The idea is that you could go ahead and spec out a subprogram using a "with type", but when you actually write the subprogram body, or anywhere you actually call the subprogram, you have to do a full "with" clause to make the full type declaration available. Bottom line, when the compiler really needs to know exactly how to pass the parameters, it can know. -- Signature volanSignature = new Signature ( /*name: */ "John G. Volan", /*employer: */ "Raytheon Advanced C3I Systems, San Jose", /*workEmail: */ "johnv@ac3i.dseg.ti.com", /*homeEmail: */ "johnvolan@sprintmail.com", /*selfPlug: */ "Sun Certified Java Programmer", /*twoCents: */ "Java would be even cooler with Ada95's " + "generics, enumerated types, function types, " + "named parameter passing, etc...", /*disclaimer:*/ "These views not packaged in COM.ti.dseg.ac3i, " + "so loading them throws DontQuoteMeError. :-)" ); ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Mutually dependent private types 1998-05-28 0:00 ` John Volan @ 1998-05-28 0:00 ` Matthew Heaney 1998-05-29 0:00 ` John Volan 1998-05-29 0:00 ` Brian Rogoff 1998-05-30 0:00 ` Geoff Bull 2 siblings, 1 reply; 31+ messages in thread From: Matthew Heaney @ 1998-05-28 0:00 UTC (permalink / raw) John Volan <johnv@ac3i.dseg.ti.com> writes: > > I don't know what to do really. Maybe a pragma be better: > > > > with P; > > pragma Mutual (P); > > package Q is > > It seems to me that if there's a mutual spec dependency going on between > P and Q, then the "with" clause here is already illegal according to > Ada95's language rules. So what exactly would this "Mutual" pragma > mean? "I know the with clause is illegal, but ignore that and pretend > to be a different language"? :-) What about Ada95's run-time > elaboration mechanism? When would the elaboration code for the specs of > packages P and Q get executed? In what order? The pragma idea is more or less equivalent to "with P.T". Thinking about it more, you could name it thusly: with P; pragma Elaborate_None (P); package Q is ...; So the answer to your question is that the spec for P is not elaborated prior to its use (with certain constraints) by Q. Yes, the "with type" idea was Tuck's, but I'd be willing to bet this problem will be solved using a new set of pragmas. We already have a precedent for using pragmas for elaboration control, and since this is really an elaboration issue, an additional pragma seems like a more natural fit. ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Mutually dependent private types 1998-05-28 0:00 ` Matthew Heaney @ 1998-05-29 0:00 ` John Volan 0 siblings, 0 replies; 31+ messages in thread From: John Volan @ 1998-05-29 0:00 UTC (permalink / raw) Matthew Heaney wrote: > > The pragma idea is more or less equivalent to "with P.T". > > Thinking about it more, you could name it thusly: > > with P; > pragma Elaborate_None (P); > package Q is ...; > > So the answer to your question is that the spec for P is not elaborated > prior to its use (with certain constraints) by Q. Hmm... I'm not sure a pragma per se is the ultimate solution, but there's something interesting implied by your suggestion: Let the compiler do the work. Here's what I mean: So far, all the proposals for language extensions have placed the burden on the programmer to decide when and how to resolve a circular spec dependency. Tucker's "with type" clause, my "package abstracts", and Norman Cohen's "package parts" all require the programmer to decide that forward declarations are needed, and then to write those forward declarations in a separate place apart from the corresponding full declarations. (Yes, even Tucker's "with type" forces you into that, in a way. See footnote (*).) That seemed to be in keeping with Ada's language design principles, because Ada already forces you to write interfaces and implementations in separate places (specs and bodies). However, I can understand how having to pull out forward declarations can be something of an annoyance. (I'm sure the typical Eiffel or Java programmer would find it annoying to have to pull specs and bodies apart, too... :-) But another common element in these proposals is that they all basically boil down to finding a way to weaken the impact of the conventional with-clause. Well, why not just introduce a relaxed form of "with", and leave it at that? Such a clause should provide pretty much the same level of visibility to the contents of a package spec as the conventional with-clause does, but only in a "forward" manner that does not imply an elaboration-order dependency. Make it the compiler's job to determine what you can legally use out of the package spec when you import it with such a relaxed with-clause; don't require the author of that spec to pull the forward declarations out. Some care would need to be taken to come up with a good set of rules to put into the RM; they'd probably be comparable in complexity to the "freezing" rules. Compiler-writers would have to be careful to properly enforce those rules, but that's what compilers are for. One element that I think would need to be retained from the "with type" and "package abstract" proposals: If you use this relaxed form of with-clause to get "forward" visibility to a package spec, you should be forced to use the conventional with-clause to get "full" visibility, before being allowed to do things that would require elaboration of the package (for example, creating an instance of a type or invoking a subprogram). For that reason, I'm not sure a pragma is enough, I think you need some way to syntactically distinguish the relaxed vs. strong forms of "with". How about this: Adopt my "with abstract" clause, but without the "package abstract" construct. Just leave the package abstract as something implied, something the compiler could derive for itself from the package spec. (If you don't like my recycling of the "abstract" keyword, come up with some other syntax.) For example: with abstract Patients; -- does not force elaboration with Sets; package Doctors is type Doctor_Type is tagged limited private; type Doctor_Access_Type is access all Doctor_Type'Class; package Doctor_Sets is new Sets (Element_Type => Doctor_Access_Type); procedure Treat_Patient (Doctor : in out Doctor_Type; Patient : in out Patients.Patient_Type'Class); -- okay "forward" usage of Patient_Type, -- because type is pass-by-reference procedure Bill_Patient (Doctor : in out Doctor_Type; Patient : in out Patients.Patient_Type'Class); procedure Add_Regular_Patient (Doctor : in out Doctor_Type; Patient : in Patients.Patient_Access_Type); -- okay "forward" usage of Patient_Access_Type, -- even though pass-by-value, because type-size is -- statically determined without need for elaboration function Get_Regular_Patients (Doctor : in Doctor_Type) return Patients.Patient_Sets.Set_Type; -- Here's where things get dicey: Would this -- be an okay "forward" usage of a generic -- instantiation? ... end Doctors; with abstract Doctors; -- does not force elaboration with Sets; package Patients is type Patient_Type is tagged limited private; type Patient_Access_Type is access all Patient_Type'Class; package Patient_Sets is new Sets (Element_Type => Patient_Access_Type); procedure Visit_Doctor (Patient : in out Patient_Type; Doctor : in out Doctors.Doctor_Type'Class); -- okay "forward" usage of Doctor_Type, -- because type is pass-by-reference procedure Pay_Doctor (Patient : in out Patient_Type; Doctor : in out Doctors.Doctor_Type'Class); procedure Set_Family_Doctor (Patient : in out Patient_Type; Doctor : in Doctors.Doctor_Access_Type); -- okay "forward" usage of Doctor_Access_Type, -- even though pass-by-value, because type-size is -- statically determined without need for elaboration function Get_Family_Doctor (Patient : in Patient_Type) return Doctors.Doctor_Access_Type; ... end Patients; with Patients; -- forces elaboration package body Doctors is procedure Bill_Patient (Doctor : in out Doctor_Type; Patient : in out Patients.Patient_Type'Class) is begin ... Patients.Pay_Doctor (Patient, Doctor); -- requires fully-elaborated access to Patients end Bill_Patient; ... end Doctors; with Doctors; -- forces elaboration package body Patients is procedure Visit_Doctor (Patient : in out Patient_Type; Doctor : in out Doctors.Doctor_Type'Class) is begin ... Doctors.Treat_Patient (Doctor, Patient); -- requires fully-elaborated access to Doctors end Visit_Doctor; ... end Patients; One problem with this: It only works at the library level. What if the two packages involved are nested inside another package? There doesn't seem to be anything analogous you could do. In my FAQ, I felt it was important that any solution that could work with library-level packages should also work with nested packages. I demonstrated how my package abstracts could work even in a nested context, and I also showed that Tucker's proposal could also work, if you generalize his "with type" idea to include something I called a "forward incomplete type declaration." (*) Footnote: I claim that even Tucker's "with type" clause forces you to pull forward declarations out of a spec. At first glance, you might not think so. However, one of the difficulties people have had with Tucker's proposal is that it only gives you forward visibility to a single type. For example: with type Doctors.Doctor_Type; would give you forward visibility to the tagged type Doctor_Type (and its classwide type Doctor_Type'Class), but not to an auxiliary type like Doctor_Access_Type, or an ADT like Doctor_Sets instantiated off of that auxiliary type. Such auxiliary declarations might "belong" in the Doctors package, but to make them available without a full "with Doctors;" clause, you have to pull them out into another package: with type Doctors.Doctor_Type; with Sets; package Doctors_Access is type Doctor_Access_Type is access all Doctors.Doctor_Type'Class; package Doctor_Sets is new Sets (Element_Type => Doctor_Access_Type); end Doctors_Access; -- Signature volanSignature = new Signature ( /*name: */ "John G. Volan", /*employer: */ "Raytheon Advanced C3I Systems, San Jose", /*workEmail: */ "johnv@ac3i.dseg.ti.com", /*homeEmail: */ "johnvolan@sprintmail.com", /*selfPlug: */ "Sun Certified Java Programmer", /*twoCents: */ "Java would be even cooler with Ada95's " + "generics, enumerated types, function types, " + "named parameter passing, etc...", /*disclaimer:*/ "These views not packaged in COM.ti.dseg.ac3i, " + "so loading them throws DontQuoteMeError. :-)" ); ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Mutually dependent private types 1998-05-28 0:00 ` John Volan 1998-05-28 0:00 ` Matthew Heaney @ 1998-05-29 0:00 ` Brian Rogoff 1998-05-29 0:00 ` John Volan 1998-05-30 0:00 ` Geoff Bull 2 siblings, 1 reply; 31+ messages in thread From: Brian Rogoff @ 1998-05-29 0:00 UTC (permalink / raw) On Thu, 28 May 1998, John Volan wrote: > So basically what you're saying is, you think it's > "good programming" to use inheritance as a way of simulating a forward > type declaration, even if inheritance collision forces you to break the > pattern and put a forward type declaration in an unexpected place. > Well, that's your opinion, but I have to disagree with it. Lets explore the idea a little more though. If there were some better (read: more direct) support for multiple inheritance in Ada, the inheritance collision argument is weakened, and the workaround can become the blessed solution. Since an extension is being proposed anyways, perhaps the single inheritance restriction should be rethought. -- Brian ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Mutually dependent private types 1998-05-29 0:00 ` Brian Rogoff @ 1998-05-29 0:00 ` John Volan 1998-05-29 0:00 ` Brian Rogoff 0 siblings, 1 reply; 31+ messages in thread From: John Volan @ 1998-05-29 0:00 UTC (permalink / raw) Brian Rogoff wrote: > > Lets explore the idea a little more though. If there were some better > (read: more direct) support for multiple inheritance in Ada, the > inheritance collision argument is weakened, and the workaround can become > the blessed solution. Since an extension is being proposed anyways, > perhaps the single inheritance restriction should be rethought. I'd still object to using inheritance to simulate forward type declarations, on the grounds that it proliferates "spurious" extra parent types, and forces programmers to do unnecessary downcasting type conversions. A true forward type declaration doesn't introduce a different type, it's just a different view of the same type, in the same sense that a private type declaration and its full type declaration are just two views of the same type. -- Signature volanSignature = new Signature ( /*name: */ "John G. Volan", /*employer: */ "Raytheon Advanced C3I Systems, San Jose", /*workEmail: */ "johnv@ac3i.dseg.ti.com", /*homeEmail: */ "johnvolan@sprintmail.com", /*selfPlug: */ "Sun Certified Java Programmer", /*twoCents: */ "Java would be even cooler with Ada95's " + "generics, enumerated types, function types, " + "named parameter passing, etc...", /*disclaimer:*/ "These views not packaged in COM.ti.dseg.ac3i, " + "so loading them throws DontQuoteMeError. :-)" ); ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Mutually dependent private types 1998-05-29 0:00 ` John Volan @ 1998-05-29 0:00 ` Brian Rogoff 1998-05-29 0:00 ` John Volan 0 siblings, 1 reply; 31+ messages in thread From: Brian Rogoff @ 1998-05-29 0:00 UTC (permalink / raw) On Fri, 29 May 1998, John Volan wrote: > Brian Rogoff wrote: > > > > Lets explore the idea a little more though. If there were some better > > (read: more direct) support for multiple inheritance in Ada, the > > inheritance collision argument is weakened, and the workaround can become > > the blessed solution. Since an extension is being proposed anyways, > > perhaps the single inheritance restriction should be rethought. > > I'd still object to using inheritance to simulate forward type > declarations, on the grounds that it proliferates "spurious" extra > parent types, and forces programmers to do unnecessary downcasting type > conversions. A true forward type declaration doesn't introduce a > different type, it's just a different view of the same type, in the same > sense that a private type declaration and its full type declaration are > just two views of the same type. I agree on all points, but I think that your strongest argument against using inheritance for resolving forward type declarations is that they use up a valuable "line of inheritance" and that the inheritance collision problem forces reorganization of packages. I think that with better MI the withing problem could have a (only slightly inelegant) workaround, *and* there would be a bit more convenience in Ada OOP. The "withing problem" solutions you describe are only solutions to the withing problem and nothing else. There might be more bang for the language change buck in an MI extension. -- Brian ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Mutually dependent private types 1998-05-29 0:00 ` Brian Rogoff @ 1998-05-29 0:00 ` John Volan 0 siblings, 0 replies; 31+ messages in thread From: John Volan @ 1998-05-29 0:00 UTC (permalink / raw) Brian Rogoff wrote: > > I agree on all points, but I think that your strongest argument against > using inheritance for resolving forward type declarations is that they > use up a valuable "line of inheritance" and that the inheritance > collision problem forces reorganization of packages. I think that with > better MI the withing problem could have a (only slightly inelegant) > workaround, *and* there would be a bit more convenience in Ada OOP. The > "withing problem" solutions you describe are only solutions to the withing > problem and nothing else. There might be more bang for the language change > buck in an MI extension. MI is a separate issue. If someone could find a reasonable approach to MI in Ada that resolved all the complexities without doing serious damage to the rest of the language, then fine, I'm sure many people would find many useful ways to exploit it. (I'd encourage anybody researching this to take a good long look at Java's approach to this.) But I'd still see inheritance as the wrong solution for the withing problem. It would still be an inelegant workaround (and I wouldn't qualify it as "slightly" inelegant). If I have two types that are mutually dependent, a true solution would leave me with just two types to deal with. Any "solution" that forces me to deal with four types and do conversions back and forth is an inelegant workaround. (And I include my own generic Forward package in that reckoning: It forces you to add two opaque Reference_Types and use the conversions in the Binding.) Maybe the withing problem is just one problem, but it deserves a solution nevertheless. -- Signature volanSignature = new Signature ( /*name: */ "John G. Volan", /*employer: */ "Raytheon Advanced C3I Systems, San Jose", /*workEmail: */ "johnv@ac3i.dseg.ti.com", /*homeEmail: */ "johnvolan@sprintmail.com", /*selfPlug: */ "Sun Certified Java Programmer", /*twoCents: */ "Java would be even cooler with Ada95's " + "generics, enumerated types, function types, " + "named parameter passing, etc...", /*disclaimer:*/ "These views not packaged in COM.ti.dseg.ac3i, " + "so loading them throws DontQuoteMeError. :-)" ); ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Mutually dependent private types 1998-05-28 0:00 ` John Volan 1998-05-28 0:00 ` Matthew Heaney 1998-05-29 0:00 ` Brian Rogoff @ 1998-05-30 0:00 ` Geoff Bull 1998-05-30 0:00 ` Fergus Henderson 2 siblings, 1 reply; 31+ messages in thread From: Geoff Bull @ 1998-05-30 0:00 UTC (permalink / raw) John Volan wrote: >>Matthew Heaney wrote: [snip] > > So now when he sees this > > > > with type P.T; > > [snip] > > I don't know what to do really. Maybe a pragma be better: > > > > with P; > > pragma Mutual (P); > > package Q is > It seems to me that if there's a mutual spec dependency going on between > P and Q, then the "with" clause here is already illegal according to > Ada95's language rules. So what exactly would this "Mutual" pragma > mean? "I know the with clause is illegal, but ignore that and pretend > to be a different language"? :-) What about Ada95's run-time > elaboration mechanism? When would the elaboration code for the specs of > packages P and Q get executed? In what order? Isn't this whole thread about changing the language? Otherwise, put up with the language and use Cohen or similar method, end of thread. I agree that providing the occasional extra package to resolve circular dependencies is a pain. However this is probably less painful than working on poorly designed code that has used "pragma Mutual" or "with type" all through it. How does a compiler that uses a non source based compilation library "with" a package that hasn't been compiled yet? (i.e. would allowing circular dependencies introduce a big headache for just about every Ada compiler, except for Gnat?) ========================================== Geoff Bull Murray Bull Information Technology Pty Ltd ACN 056 703 274 ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Mutually dependent private types 1998-05-30 0:00 ` Geoff Bull @ 1998-05-30 0:00 ` Fergus Henderson 1998-06-01 0:00 ` John Volan 0 siblings, 1 reply; 31+ messages in thread From: Fergus Henderson @ 1998-05-30 0:00 UTC (permalink / raw) Geoff Bull <gbull@acenet.com.au> writes: >How does a compiler that uses a non source based compilation library >"with" a package that hasn't been compiled yet? >(i.e. would allowing circular dependencies introduce a big headache >for just about every Ada compiler, except for Gnat?) You can implement this by breaking up "compilation" into several stages. For example, the Mercury compiler (which supports a module system similar to that of Ada) supports circular dependencies with a non-source-based compilation library by breaking compilation into the following stages: mmc --make-short-interface foo Does rudimentary syntax checking. Records the names of types defined in the module foo in the compilation database. Preconditions: none. mmc --make-interface foo Checks the interface for type correctness (i.e. checks that there are no undefined types, etc.). Records the interface of module foo in the compilation database. Preconditions: the "short interfaces" of all imported modules must have already been generated. mmc --compile foo Does full semantic checking. Generates object code for the module foo. Preconditions: the interfaces of all imported modules must have already been generated. Actually our "compilation database" is just a subdirectory containing plain ASCII files. The above preconditions are recorded in dependency files, and then we use a tool based on GNU Make to ensure that the compiler is invoked with the appropriate options in the appropriate order. Whether or not doing this kind of thing would be a "big headache" for existing Ada compilers is another question -- that would depend a lot on exactly how they were implemented. But I thought that the other Ada95 front-ends all used the same kind of source based model that GNAT uses anyway. -- Fergus Henderson <fjh@cs.mu.oz.au> | "I have always known that the pursuit WWW: <http://www.cs.mu.oz.au/~fjh> | of excellence is a lethal habit" PGP: finger fjh@128.250.37.3 | -- the last words of T. S. Garp. ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Mutually dependent private types 1998-05-30 0:00 ` Fergus Henderson @ 1998-06-01 0:00 ` John Volan 1998-06-02 0:00 ` Fergus Henderson 1998-06-04 0:00 ` Robert Dewar 0 siblings, 2 replies; 31+ messages in thread From: John Volan @ 1998-06-01 0:00 UTC (permalink / raw) Fergus Henderson wrote: > > Geoff Bull <gbull@acenet.com.au> writes: > > >How does a compiler that uses a non source based compilation library > >"with" a package that hasn't been compiled yet? > >(i.e. would allowing circular dependencies introduce a big headache > >for just about every Ada compiler, except for Gnat?) > > You can implement this by breaking up "compilation" into several > stages. For example, the Mercury compiler (which supports a module > system similar to that of Ada) supports circular dependencies > with a non-source-based compilation library by breaking compilation > into the following stages: Without knowing anything about Mercury as a language, I can make the following observations: > mmc --make-short-interface foo > Does rudimentary syntax checking. > Records the names of types defined in the module foo > in the compilation database. > Preconditions: none. Roughly speaking, this corresponds to the compiler automatically extracting and processing forward class declarations (type names) out of the source code module. It's also taking the opportunity to do a syntax check, but neither of these processes require access to semantic information about other modules. > mmc --make-interface foo > Checks the interface for type correctness (i.e. > checks that there are no undefined types, etc.). > Records the interface of module foo in the compilation > database. > Preconditions: the "short interfaces" of all imported modules > must have already been generated. Here, the compiler is clearly processing the full interfaces ("specs") of the classes (types) in the module. When it encounters a parameter of a type from a different module, it has to be able to get access to semantic information about that other module, but that only has to be the processed forward declarations (short interfaces), and not necessarily the full interface semantics. > mmc --compile foo > Does full semantic checking. > Generates object code for the module foo. > Preconditions: the interfaces of all imported modules > must have already been generated. Here, the compiler is processing the implementations ("bodies") of the classes in the module. At this point it needs full semantic information about the interfaces of imported classes, because it actually needs to generate code for any calls it makes to imported methods, as well as for passing imported-type parameters into its own methods. These three levels of compilation are completely consistent with the inter-class dependency model I described in my diagram in http://bluemarble.net/~jvolan/WithingProblem/FAQ.html#missing_feature. I am certain that these three compilation passes are exactly what occurs implicitly inside compilers for languages such as Eiffel and Java, where each class only has one source file and there is no explicit separation of forward declaration, interface, and implementation. The only difference is that apparently the Mercury compiler needs to be explicitly told when to do which compilation passes on which modules in which order. This compilation-order dependency is definitely reminiscent of old-style Ada compilation systems. > Actually our "compilation database" is just a subdirectory containing > plain ASCII files. That's also reminiscent of an old-style Ada "library", which was essentially a database of compilation products. > The above preconditions are recorded in dependency > files, and then we use a tool based on GNU Make to ensure that the > compiler is invoked with the appropriate options in the appropriate order. Old-style Ada compilation systems usually included a "make" tool that was able to do approximately the same thing. These usually had to do an "analysis" pass over the source files in a library to determine what units they contained and what dependencies there were between the units. > Whether or not doing this kind of thing would be a "big headache" for > existing Ada compilers is another question -- that would depend a lot > on exactly how they were implemented. But I thought that the other > Ada95 front-ends all used the same kind of source based model that GNAT > uses anyway. Ada requires the programmer to explicitly separate interfaces (specs) from implementations (bodies). There's a clear distinction in how specs and bodies should be compiled. Obviously, Ada compilers have been designed from the bottom-up to deal with this distinction. But Ada doesn't provide a corresponding way to explicitly separate out forward declarations from full specs. In fact, the semantics of Ada don't even allow compilers to implicitly extract forward declarations out of a spec -- otherwise there'd be no such thing as a "withing problem". So obviously there's been nothing to motivate Ada compiler-writers to design in a separate pass for processing forward declarations. (In fact, if any have tried, they might have been criticized in some circles as "unnecessarily complicating" the compiler design.) Maybe the "make analysis" pass in old-style Ada compilation systems carried the germ of such a "forward-extraction" pass, but I'd be very surprised if source based compilation systems would have bothered with it. (Note that Matthew Heaney's "Mutual" pragma suggestion would essentially change the semantics of the Ada language to allow/require compilers to do a "forward-extracting" pass.) -- Signature volanSignature = new Signature ( /*name: */ "John G. Volan", /*employer: */ "Raytheon Advanced C3I Systems, San Jose", /*workEmail: */ "johnv@ac3i.dseg.ti.com", /*homeEmail: */ "johnvolan@sprintmail.com", /*selfPlug: */ "Sun Certified Java Programmer", /*twoCents: */ "Java would be even cooler with Ada95's " + "generics, enumerated types, function types, " + "named parameter passing, etc...", /*disclaimer:*/ "These views not packaged in COM.ti.dseg.ac3i, " + "so loading them throws DontQuoteMeError. :-)" ); ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Mutually dependent private types 1998-06-01 0:00 ` John Volan @ 1998-06-02 0:00 ` Fergus Henderson 1998-06-04 0:00 ` Robert Dewar 1 sibling, 0 replies; 31+ messages in thread From: Fergus Henderson @ 1998-06-02 0:00 UTC (permalink / raw) John Volan <johnv@ac3i.dseg.ti.com> writes: >Fergus Henderson wrote: >> >> Geoff Bull <gbull@acenet.com.au> writes: >> >> >How does a compiler that uses a non source based compilation library >> >"with" a package that hasn't been compiled yet? >> >> You can implement this by breaking up "compilation" into several >> stages. For example, the Mercury compiler (which supports a module >> system similar to that of Ada) supports circular dependencies >> with a non-source-based compilation library by breaking compilation >> into the following stages: > >Without knowing anything about Mercury as a language, I can make the >following observations: [...] Yes, your observations were all correct. >These three levels of compilation are completely consistent with the >inter-class dependency model I described in my diagram in >http://bluemarble.net/~jvolan/WithingProblem/FAQ.html#missing_feature. Right. Actually there is one additional complication in our latest development version. I've been extending the Mercury module system to support nested sub-modules with seperate compilation (a bit like Ada's "is separate", or like Ada's child modules). This means that some compilation units have access to the things declared in the implementation of the parent module. Thus in addition to the "short interface" and the ordinary interface files, the Mercury compiler also produces "private interface" files, which actually specify the interface that a parent module exports to its sub-modules. >I am certain that these three compilation passes are exactly what occurs >implicitly inside compilers for languages such as Eiffel and Java, where >each class only has one source file and there is no explicit separation >of forward declaration, interface, and implementation. The only >difference is that apparently the Mercury compiler needs to be >explicitly told when to do which compilation passes on which modules in >which order. This compilation-order dependency is definitely >reminiscent of old-style Ada compilation systems. Well, the compiler itself needs to be explicitly told. That's just because we made a design decision that resolving the dependency ordering issues would be the job of a seperate tool called "Mmake". Generally the user interacts with Mmake, rather than with the compiler directly, so the user never needs to specify dependencies explicitly. ... >> The above preconditions are recorded in dependency >> files, and then we use a tool based on GNU Make to ensure that the >> compiler is invoked with the appropriate options in the appropriate order. > >Old-style Ada compilation systems usually included a "make" tool that >was able to do approximately the same thing. These usually had to do an >"analysis" pass over the source files in a library to determine what >units they contained and what dependencies there were between the units. Yes, the Mercury compiler has an analysis pass (`mmc --generate-dependencies') which writes out dependency information that is use by Mmake (the Mercury make tool built on top of GNU Make.) I seem to remember the old Ada implementations getting a lot of bad press about problems with compilation order. Why did that happen? Was the criticism well-founded? Did some Ada implementations not provide a "make" tool? -- Fergus Henderson <fjh@cs.mu.oz.au> | "I have always known that the pursuit WWW: <http://www.cs.mu.oz.au/~fjh> | of excellence is a lethal habit" PGP: finger fjh@128.250.37.3 | -- the last words of T. S. Garp. ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Mutually dependent private types 1998-06-01 0:00 ` John Volan 1998-06-02 0:00 ` Fergus Henderson @ 1998-06-04 0:00 ` Robert Dewar 1 sibling, 0 replies; 31+ messages in thread From: Robert Dewar @ 1998-06-04 0:00 UTC (permalink / raw) <<> >How does a compiler that uses a non source based compilation library > >"with" a package that hasn't been compiled yet? > >(i.e. would allowing circular dependencies introduce a big headache > >for just about every Ada compiler, except for Gnat?) >> Just to correct a misimpression here, GNAT is not the only Ada compiler to use the source-based compilation library, all Ada compilers based on the Intermetrics front end (Aonix, Green-Hills, ...) also share this approach. ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Mutually dependent private types 1998-05-21 0:00 Mutually dependent private types adam 1998-05-21 0:00 ` Matthew Heaney @ 1998-05-21 0:00 ` John Volan 1 sibling, 0 replies; 31+ messages in thread From: John Volan @ 1998-05-21 0:00 UTC (permalink / raw) adam@irvine.com wrote: > > I've run into another problem trying to get something implemented in > Ada 95. This problem seems superficially similar to John Volan's > with-ing problem, but I don't think it's quite the same. Goodness, please don't refer to it as "John Volan's" with-ing problem ... I really don't want to lay claim to the problem itself, all I did was analyze it to death (see my FAQ web page at http://bluemarble.net/~jvolan/WithingProblem/FAQ.html). I suppose in medical circles they tend to name a disease after the first person to discover and characterize it, but I'm pretty sure my write-up wasn't the first time the with-ing problem (syndrome? :-) was discovered or described ... although it is no doubt the most *exhaustive* description to date ... :-) > (1) If I want to have two private types in two separate packages whose > *implementations* are mutually dependent on each other, this is > easy to do, if we're willing to make the private types accesses: > > package P1 is > type T1 is private; > private > type T1_Info; > type T1 is access T1_Info; > end P1; > > package P2 is > type T2 is private; > private > type T2_Info; > type T2 is access T2_Info; > end P2; > > and this will work since it's legal for P1's package body to > depend on P2 and P2's package body to depend on P1. The > definitions of P1.T1_Info and P2.T2_Info would contain references > to the other package's type. See http://bluemarble.net/~jvolan/WithingProblem/FAQ.html#interfaces, where I describe how the with-ing problem can be avoided if you can defer the mutual dependencies until the package bodies. > (2) Another mutual-dependency situation is this: Suppose I want to > have two types T1 and T2 in two different packages, and I want to > provide operations on T1 that take T2 as a parameter, and > operations on T2 that take T1 as a parameter. You can't do this > by declaring T1 and T1's operations in the same package and do the > same for T2, since then the two package specs would have to be > mutually dependent. Seems to me this isn't just superfically similar to the with-ing problem, it's exactly the with-ing problem! > However, you can solve this problem by > separating the type definition from the operations in two separate > packages (preferably using child units): > > package P1 is > type T1 is ... > end P1; > > package P2 is > type T2 is ... > end P2; > > with P2; > package P1.Operations is > procedure Some_Operation (X : T1; Y : P2.T2); > end P1.Operations; > > with P1; > package P2.Operations is > procedure Some_Operation (X : T2; Y : P1.T1); > end P2.Operations; > > Actually, you don't have to do this for both packages; you can > solve the problem by splitting the operations in just one of the > packages. See http://bluemarble.net/~jvolan/WithingProblem/FAQ.html#primitive, where I describe how you can avoid the with-ing problem if you don't need the mutually-dependent operations to be "primitives" for their respective object types (i.e., if they don't need to be declared in the same package specs with their types). But if you *do* need them to be primitives (i.e., your two types are the roots of two inheritance hierarchies and you want to do dynamic dispatching to different implementations of the mutually dependent operations) -- well, unfortunately, you're stuck in that case. > (3) But what happens if you want to do both (1) and (2)? Now the > whole thing seems to break down. If you try to do both of the > above, something like: > > package P1 is > type T1 is private; > private > type T1_Info; > type T1 is access T1_Info; > end P1; > > with P2; > package P1.Operations is > procedure Some_Operation (X : T1; Y : P2.T2); > end P1.Operations; > > you can't, because the body of Some_Operation cannot do anything > useful with X, since the definition of T1_Info is not visible to > it. Why hide the full implementation of T1_Info in the body of P1? Why not just declare it in the private part, since you want that declaration to be available to a child package anyway? > I've been trying to come up with some way around this problem; for > example, I thought about putting some routines in the private part of > P1 that P1.Operations could access to give it information about > T1_Info, or by setting up another private child package to declare > actual type info, but I couldn't quite get anything to work. Does > anyone know of a good way to accomplish this, without using > Unchecked_Conversion? (It's pretty easy to come up with a solution > using Unchecked_Conversion.) I might be missing something obvious > here due to my incomplete knowledge of Ada 95. Well, I'm not exactly sure what you're trying to do, but it's interesting that you're using access types to implement your private types. That implies that you don't have a problem dealing with "reference semantics" (i.e., if X1 and X2 are variables of type T1, and X1 = X2, then X1 and X2 aren't just referring to two T1_Info objects that contain similar values, they're both referring to the *same* T1_Info object). Sounds like you might be groping for a way to create some sort of deferred reference type that can stand in the place of an object type (so you can declare the specs of mutually-dependent operations) and then later "bind" that reference type to that object type (when you're ready to implement the operation bodies). All solutions to the with-ing problem (even Tucker's "with type" proposal) pretty much amount to this sort of "deferred binding". You might want to try using my generic Forward package (see my FAQ) to set yourself up with the reference types you need. Yes, it does use Unchecked_Conversion underneath the covers, but it guarantees type-safety despite that. If you're uncomfortable with the Unchecked_Conversions, I've also sketched out an alternative implementation of Forward that avoids them, at the expense of tying all your object types into a common inheritance hierarchy. Hope that helps. P.S. Of course, there's always the "outside-the-box" solution: Program in a language that's immune to this sort of syndrome ... ;-) -- Signature volanSignature = new Signature ( /*name: */ "John G. Volan", /*employer: */ "Raytheon Advanced C3I Systems, San Jose", /*workEmail: */ "johnv@ac3i.dseg.ti.com", /*homeEmail: */ "johnvolan@sprintmail.com", /*selfPlug: */ "Sun Certified Java Programmer", /*twoCents: */ "Java would be even cooler with Ada95's " + "generics, enumerated types, function types, " + "named parameter passing, etc...", /*disclaimer:*/ "These views not packaged in COM.ti.dseg.ac3i, " + "so loading them throws DontQuoteMeError. :-)" ); ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Mutually dependent private types @ 1998-05-22 0:00 adam 1998-05-22 0:00 ` John Volan ` (2 more replies) 0 siblings, 3 replies; 31+ messages in thread From: adam @ 1998-05-22 0:00 UTC (permalink / raw) Matthew Heaney wrote: > Here's is a solution to your problem. > > 1) No private child package is required. Move the representation of T1 and > T2 up into the private region of a common parent package. > > 2) Types T1 and T2 privately derive from the respective rep types. Each > type, because it's in a child package of package containing both rep types, > has direct visibility to the rep of the other type. > > 3) The mutual dependency in operations is effected by making the other type > have type Root'Class, in the spec. In the body, just downcast the object > to the representation type. Of course, the compiler can't verify > statically that the object is really of the required type, but you'll find > out at runtime, because a tag check happens when you downcast. > > This is out-of-the-box Ada 95. No complex language extensions or > Unchecked_Conversion or implementing types using access types is required. OK, this is helpful. It looks like it should work in my case. Here's the trivial thing I missed: if I write code like this, I can also add this compilation unit with Adam.P2; package P2 renames Adam.P2; so that I don't have to go back and modify all the other packages that used P2. What I hadn't mentioned in my previous post was that I had an objection to making major changes in the structure of P2. In my real situation, I had already written P1 and P2, and there were no mutual dependencies, and everything was fine. However, I needed to add a new operation to P1; this new function would operate on P1.T1 and needed access to the representation of P1.T1, but also needed to take an object of type P2.T2 as a parameter. What bothered me was that adding this functionality to P1 would require me to make such a major structural change to P2 that I'd have to modify every module that with'ed P2, which in this case is about a hundred modules. This was hard for me to swallow, particularly since I wasn't changing the functionality of P2 at all. But I forgot about the library-level rename, which lets me avoid having to make such wholesale changes. (I still have to recompile everything that with's P2, but this isn't as obnoxious.) IMHO, it still would be more ideal, and more in keeping with software engineering principles, if I were able to add new functionality to P1 without touching P2. Granted, we can't expect this to happen in every case, but if there are reasonable extensions to the language that would help in cases like this, I think they should be considered next time the language is enhanced. > I read John's paper, but I don't find his argument convincing. Doesn't the > following code solve the putative with'ing "problem"? I think John's objection to this solution is that it requires a runtime check to make sure the second parameter has the correct type. Also, one could object on philosophical grounds that requiring the two packages to be children of a common parent isn't appropriate for packages that really don't have much of a common purpose. (Then again, I suppose that with every language there are cases that require one to write code that goes against software engineering philosophy.) -- Adam > > --STX > package body Adam.P1 is > > procedure Op1 (O1 : in out T1; O2 : in Root'Class) is > begin > O1.F := Float (T2_Rep (O2).I); > end Op1; > > end Adam.P1; > package Adam.P1 is > > type T1 is new Root with private; > > procedure Op1 (O1 : in out T1; O2 : in Root'Class); > -- precondition: O2 in P2.T2'Class > > private > > type T1 is new T1_Rep with null record; > > end Adam.P1; > package body Adam.P2 is > > procedure Op2 (O2 : in out T2; O1 : in Root'Class) is > begin > O2.I := Integer (T1_Rep (O1).F); > end Op2; > > end Adam.P2; > package Adam.P2 is > > type T2 is new Root with private; > > procedure Op2 (O2 : in out T2; O1 : in Root'Class); > -- precondition: O1 in P1.T1'Class > > private > > type T2 is new T2_Rep with null record; > > end; > with Adam.P1; > with Adam.P2; > > procedure Adam.Test is > > O1 : P1.T1; > O2 : P2.T2; > > use P1, P2; > > begin > > Op1 (O1, O2); > Op2 (O2, O1); > > end Adam.Test; > > package Adam is > > pragma Pure; -- the package, not Adam! > > type Root is abstract tagged null record; > > private > > type T1_Rep is > new Root with record > F : Float; > end record; > > > type T2_Rep is > new Root with record > I : Integer; > end record; > > end Adam; > -----== Posted via Deja News, The Leader in Internet Discussion ==----- http://www.dejanews.com/ Now offering spam-free web-based newsreading ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Mutually dependent private types 1998-05-22 0:00 adam @ 1998-05-22 0:00 ` John Volan 1998-05-22 0:00 ` Brian Rogoff 1998-05-22 0:00 ` Matthew Heaney 2 siblings, 0 replies; 31+ messages in thread From: John Volan @ 1998-05-22 0:00 UTC (permalink / raw) adam@irvine.com wrote: > > IMHO, it still would be more ideal, and more in keeping with software > engineering principles, if I were able to add new functionality to P1 > without touching P2. Granted, we can't expect this to happen in every > case, but if there are reasonable extensions to the language that > would help in cases like this, I think they should be considered next > time the language is enhanced. Adam, the strategy Matt suggested -- sacrificing static strong type checking in order to avoid mutual spec dependencies -- does NOT require your two packages to be children of a common parent package. Your two types T1 and T2 can remain mutually private to each other, yet they can still be derived from a common root type. That type can come from a third package entirely unconnected to either package P1 or P2: package Objects is type Object_Type is abstract tagged limited null record; end Objects; with Objects; package P1 is type T1 is new Objects.Object_Type with private; procedure Op1 (O1 : in out T1; O2 : in Objects.Object_Type'Class); -- precondition: O2 in P2.T2'Class function Get_F (O1 : in T1) return Float; private type T1 is new Objects.Object_Type with record F : Float; end record; end P1; with Objects; package P2 is type T2 is new Objects.Object_Type with private; procedure Op2 (O2 : in out T2; O1 : in Objects.Object_Type'Class); -- precondition: O1 in P1.T1'Class function Get_I (O2 : in T2) return Integer; private type T2 is new Objects.Object_Type with record I : Integer; end record; end P2; The only changes you have to make are (1) add the "with Objects;" clauses; (2) add your new functionality (procedures Op1 and Op2); and (3) change the declarations of types T1 and T2 so that they're derived from the common root type (in this case, Objects.Object_Type), if they weren't already derived from that before. You don't have to change your overall packaging scheme; P1 and P2 can stay P1 and P2. All your other code that was calling P1 and P2 can stay the same. You don't need to play around with tricks like library-level renaming. You do have to recompile the universe, though, but that's not so big a deal. > > I read John's paper, but I don't find his argument convincing. Doesn't the > > following code solve the putative with'ing "problem"? > > I think John's objection to this solution is that it requires a > runtime check to make sure the second parameter has the correct type. Exactly. > Also, one could object on philosophical grounds that requiring the two > packages to be children of a common parent isn't appropriate for > packages that really don't have much of a common purpose. But they don't have to be children of a common parent after all, so this philosophical quandary doesn't have to arise in this case. > (Then > again, I suppose that with every language there are cases that require > one to write code that goes against software engineering philosophy.) In any reasonably powerful/complex language, there's usually more than one way to skin a cat. Some ways are easier and simpler than others. With enough creativity and ingenuity, anyone can devise a Rube Goldberg solution... ;-) -- Signature volanSignature = new Signature ( /*name: */ "John G. Volan", /*employer: */ "Raytheon Advanced C3I Systems, San Jose", /*workEmail: */ "johnv@ac3i.dseg.ti.com", /*homeEmail: */ "johnvolan@sprintmail.com", /*selfPlug: */ "Sun Certified Java Programmer", /*twoCents: */ "Java would be even cooler with Ada95's " + "generics, enumerated types, function types, " + "named parameter passing, etc...", /*disclaimer:*/ "These views not packaged in COM.ti.dseg.ac3i, " + "so loading them throws DontQuoteMeError. :-)" ); ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Mutually dependent private types 1998-05-22 0:00 adam 1998-05-22 0:00 ` John Volan @ 1998-05-22 0:00 ` Brian Rogoff 1998-05-22 0:00 ` Matthew Heaney 2 siblings, 0 replies; 31+ messages in thread From: Brian Rogoff @ 1998-05-22 0:00 UTC (permalink / raw) On Fri, 22 May 1998 adam@irvine.com wrote: > Matthew Heaney wrote: > > ... snip ... > > > I read John's paper, but I don't find his argument convincing. Doesn't the > > following code solve the putative with'ing "problem"? > > I think John's objection to this solution is that it requires a > runtime check to make sure the second parameter has the correct type. Yes, if you're willing to lose static typing, I don't see that you have any right to complain about access types or unchecked ops. I don't think that I'd switch languages over this issue :-), but its definitely a flaw in my eyes. I rather like the package parts approach mentioned in John's paper, since there I've had other problems that they'd solve, but its unlikely that such a drastic modification to Ada will take place. There was some discussion of proposals a while ago, and this question comes up very frequently on c.l.a. I hope this issue will be addressed in Ada 200X. -- Brian ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Mutually dependent private types 1998-05-22 0:00 adam 1998-05-22 0:00 ` John Volan 1998-05-22 0:00 ` Brian Rogoff @ 1998-05-22 0:00 ` Matthew Heaney 2 siblings, 0 replies; 31+ messages in thread From: Matthew Heaney @ 1998-05-22 0:00 UTC (permalink / raw) In article <6k4b7t$vhn$1@nnrp1.dejanews.com>, adam@irvine.com wrote: >> I read John's paper, but I don't find his argument convincing. Doesn't the >> following code solve the putative with'ing "problem"? > >I think John's objection to this solution is that it requires a >runtime check to make sure the second parameter has the correct type. There are many, many times when a run-time check is required. For example, declare X : Integer := ...; begin X := X + 1; end; requires a run-time check. >Also, one could object on philosophical grounds that requiring the two >packages to be children of a common parent isn't appropriate for >packages that really don't have much of a common purpose. (Then >again, I suppose that with every language there are cases that require >one to write code that goes against software engineering philosophy.) Perhaps so, but it was the constraints of your particular problem that necessitated that structure. Specifically, you required each type to have knowledge of the representation of the other. Had your problem been, Can we have types in different packages that take the other type as an argument in the spec?, then the answer is yes, and making them children of a common root would not have been required. ^ permalink raw reply [flat|nested] 31+ messages in thread
end of thread, other threads:[~1998-06-04 0:00 UTC | newest] Thread overview: 31+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 1998-05-21 0:00 Mutually dependent private types adam 1998-05-21 0:00 ` Matthew Heaney 1998-05-22 0:00 ` John Volan 1998-05-22 0:00 ` Matthew Heaney 1998-05-26 0:00 ` Robert I. Eachus 1998-05-26 0:00 ` John Volan 1998-05-27 0:00 ` Robert I. Eachus 1998-05-29 0:00 ` John Volan 1998-05-27 0:00 ` Jerry van Dijk 1998-05-29 0:00 ` John Volan 1998-05-26 0:00 ` John Volan 1998-05-26 0:00 ` Matthew Heaney 1998-05-27 0:00 ` John Volan 1998-05-27 0:00 ` Matthew Heaney 1998-05-28 0:00 ` John Volan 1998-05-28 0:00 ` Matthew Heaney 1998-05-29 0:00 ` John Volan 1998-05-29 0:00 ` Brian Rogoff 1998-05-29 0:00 ` John Volan 1998-05-29 0:00 ` Brian Rogoff 1998-05-29 0:00 ` John Volan 1998-05-30 0:00 ` Geoff Bull 1998-05-30 0:00 ` Fergus Henderson 1998-06-01 0:00 ` John Volan 1998-06-02 0:00 ` Fergus Henderson 1998-06-04 0:00 ` Robert Dewar 1998-05-21 0:00 ` John Volan -- strict thread matches above, loose matches on Subject: below -- 1998-05-22 0:00 adam 1998-05-22 0:00 ` John Volan 1998-05-22 0:00 ` Brian Rogoff 1998-05-22 0:00 ` Matthew Heaney
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox