comp.lang.ada
 help / color / mirror / Atom feed
* Re: Mutually dependent private types
  1998-05-21  0:00 adam
@ 1998-05-21  0:00 ` John Volan
  1998-05-21  0:00 ` Matthew Heaney
  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-21  0:00 adam
  1998-05-21  0:00 ` John Volan
@ 1998-05-21  0:00 ` Matthew Heaney
  1998-05-22  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

* Mutually dependent private types
@ 1998-05-21  0:00 adam
  1998-05-21  0:00 ` John Volan
  1998-05-21  0:00 ` Matthew Heaney
  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-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 Mutually dependent private types 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 Mutually dependent private types 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 Mutually dependent private types 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

* 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-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       ` Robert I. Eachus
@ 1998-05-26  0:00         ` John Volan
  1998-05-27  0:00           ` Jerry van Dijk
  1998-05-27  0:00           ` Robert I. Eachus
  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-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         ` John Volan
  1998-05-27  0:00           ` Jerry van Dijk
@ 1998-05-27  0:00           ` Robert I. Eachus
  1998-05-29  0:00             ` John Volan
  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-26  0:00         ` John Volan
@ 1998-05-27  0:00           ` Jerry van Dijk
  1998-05-29  0:00             ` John Volan
  1998-05-27  0:00           ` Robert I. Eachus
  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-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-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-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                 ` 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-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-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-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-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-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

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-22  0:00 Mutually dependent private types adam
1998-05-22  0:00 ` John Volan
1998-05-22  0:00 ` Brian Rogoff
1998-05-22  0:00 ` Matthew Heaney
  -- strict thread matches above, loose matches on Subject: below --
1998-05-21  0:00 adam
1998-05-21  0:00 ` John Volan
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           ` Jerry van Dijk
1998-05-29  0:00             ` John Volan
1998-05-27  0:00           ` Robert I. Eachus
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

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox