From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on polar.synack.me X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00 autolearn=ham autolearn_force=no version=3.4.4 X-Google-Language: ENGLISH,ASCII-7-bit X-Google-Thread: 103376,56dbd715fa74735a X-Google-Attributes: gid103376,public From: John Volan Subject: Re: Mutually dependent private types Date: 1998/05/28 Message-ID: <356E09A1.B493FE89@ac3i.dseg.ti.com> X-Deja-AN: 357482551 Content-Transfer-Encoding: 7bit References: <6k25ra$6j7$1@nnrp1.dejanews.com> <3565B105.9BFB4788@ac3i.dseg.ti.com> <356B226F.EF05E927@ac3i.dseg.ti.com> <356C8A02.44354B09@ac3i.dseg.ti.com> Content-Type: text/plain; charset=us-ascii Organization: Raytheon Systems Company, Advanced C3I Systems Mime-Version: 1.0 Newsgroups: comp.lang.ada Date: 1998-05-28T00:00:00+00:00 List-Id: 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. :-)" );