comp.lang.ada
 help / color / mirror / Atom feed
* cross linking packages
@ 1994-11-28 12:56 Rene Burema
  1994-11-29 18:28 ` Norman H. Cohen
                   ` (2 more replies)
  0 siblings, 3 replies; 9+ messages in thread
From: Rene Burema @ 1994-11-28 12:56 UTC (permalink / raw)


Dear netlanders,

I have a problem specifing a seemingly simple problem in Ada. Anybody
want to help?

The problem is as follows:

-------------------- package A -----------------------
package A;
type tA is private;
procedure giveB(x: in tA,y: out tB);
end A;
-------------------- package B -----------------------
package B;
type tB is private;
procedure giveA(x: in tB,y: out tA);
end B;
---------------
where tA could be a reference to a  Doctor giving the reference to
a Patient (tB) and vice versa.

I need a with statement for B and A respectively. The Ada gnat compiler
however stumbles into a circular dependency. How can I solve this without
placing the two types tA and tB in a single package.

Many thanks for any help

	Arjen Duursma (arjen@capints.uucp)

PS: don't use the return adress in the message, as it is a shared account.



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: cross linking packages
  1994-11-28 12:56 Rene Burema
@ 1994-11-29 18:28 ` Norman H. Cohen
  1994-11-29 22:07 ` Robert Dewar
  1994-12-05 13:30 ` John Volan
  2 siblings, 0 replies; 9+ messages in thread
From: Norman H. Cohen @ 1994-11-29 18:28 UTC (permalink / raw)


In article <CzzAMz.K9F@inter.NL.net>, rburema@inter.NL.net (Rene Burema)
writes: 

|> I have a problem specifing a seemingly simple problem in Ada. Anybody
|> want to help?
|>
|> The problem is as follows: 
|>
|> -------------------- package A -----------------------
|> package A;
|> type tA is private;
|> procedure giveB(x: in tA,y: out tB);
|> end A;
|> -------------------- package B -----------------------
|> package B;
|> type tB is private;
|> procedure giveA(x: in tB,y: out tA);
|> end B;
|> ---------------
|> where tA could be a reference to a  Doctor giving the reference to
|> a Patient (tB) and vice versa.
|>
|> I need a with statement for B and A respectively. The Ada gnat compiler
|> however stumbles into a circular dependency. How can I solve this without
|> placing the two types tA and tB in a single package.

package Parents is
   type tA_Parent is abstract tagged null record;
   type tB_Parent is abstract tagged null record;
end Parents;

with Parents;
package A is
   type tA is new tA_Parent with private;
   procedure giveB(x: in tA_Parent'Class; y: out tB_Parent'Class);
private
   type tA is new tA_Parent with ...;
end A;

with Parents;
package B is
   type tB is new tB_Parent with private;
   procedure giveA (x: in tB_Parent'Class; y: out tA_Parent'Class);
private
   type tB is new tB_Parent with ...;
end B;

--
Norman H. Cohen    ncohen@watson.ibm.com



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: cross linking packages
  1994-11-28 12:56 Rene Burema
  1994-11-29 18:28 ` Norman H. Cohen
@ 1994-11-29 22:07 ` Robert Dewar
  1994-11-30 16:33   ` David Weller
  1994-11-30 23:03   ` Matt Kennel
  1994-12-05 13:30 ` John Volan
  2 siblings, 2 replies; 9+ messages in thread
From: Robert Dewar @ 1994-11-29 22:07 UTC (permalink / raw)


"The GNAT compiler stumbles into a circular ..."

How about instead

"The GNAT compiler correctly diagnoses an illegal circular dependency"

This is definitely wrong, you have to put both types in the same
package if they have this sort of mutual dependence. 

(and now we can rerun the long dialog on mutually recursive types :-)




^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: cross linking packages
  1994-11-29 22:07 ` Robert Dewar
@ 1994-11-30 16:33   ` David Weller
  1994-12-01 13:44     ` Robert I. Eachus
  1994-11-30 23:03   ` Matt Kennel
  1 sibling, 1 reply; 9+ messages in thread
From: David Weller @ 1994-11-30 16:33 UTC (permalink / raw)


In article <3bg8mp$i0g@gnat.cs.nyu.edu>, Robert Dewar <dewar@cs.nyu.edu> wrote:
>
>This is definitely wrong, you have to put both types in the same
>package if they have this sort of mutual dependence. 
>
>(and now we can rerun the long dialog on mutually recursive types :-)
>

No need for that, I've saved the entire diatribe to a single file,
which occupies 351K on my account, uncompressed.  If anybody wants
it, they can have it :-)

-- 
Proud (and vocal) member of Team Ada! (and Team OS/2)        ||This is not your
   	      Ada -- Very Cool.  Doesn't Suck.               ||  father's Ada 
For all sorts of interesting Ada tidbits, run the command:   ||________________
"finger dweller@starbase.neosoft.com | more" (or e-mail with "finger" as subj.)
	|"Quitting C++ isn't so difficult, provided you show as much |
	|	persistence stopping as you did starting." dweller   |



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: cross linking packages
  1994-11-29 22:07 ` Robert Dewar
  1994-11-30 16:33   ` David Weller
@ 1994-11-30 23:03   ` Matt Kennel
  1994-12-02 14:30     ` Norman H. Cohen
  1 sibling, 1 reply; 9+ messages in thread
From: Matt Kennel @ 1994-11-30 23:03 UTC (permalink / raw)


Robert Dewar (dewar@cs.nyu.edu) wrote:
: "The GNAT compiler stumbles into a circular ..."

: How about instead

: "The GNAT compiler correctly diagnoses an illegal circular dependency"

: This is definitely wrong, you have to put both types in the same
: package if they have this sort of mutual dependence. 

: (and now we can rerun the long dialog on mutually recursive types :-)

Can somebody answer this question:

  Why were cyclical dependencies among types defined illegal?

(I.e. type A defines features that use type B, and type B defines
 features that use type A.)

After all in a statically typed langauge like Ada, all variables
must be bound to types statically at compile time, so there
should be no ambiguity in "elaboration order" as there is no
run-time execution that must take place to bind variables
to types.

It apparently works fine when they are in the same package. 
Thus supposing you put two types each in their own package, both inside
an outer package.

Do the mutual dependencies now work fine because they are both inside
one package, the outer one?

--
-Matt Kennel  		mbk@inls1.ucsd.edu
-Institute for Nonlinear Science, University of California, San Diego
-*** AD: Archive for nonlinear dynamics papers & programs: FTP to
-***     lyapunov.ucsd.edu, username "anonymous".



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: cross linking packages
  1994-11-30 16:33   ` David Weller
@ 1994-12-01 13:44     ` Robert I. Eachus
  0 siblings, 0 replies; 9+ messages in thread
From: Robert I. Eachus @ 1994-12-01 13:44 UTC (permalink / raw)


In article <3bi9g2$lj@Starbase.NeoSoft.COM> dweller@Starbase.NeoSoft.COM (David Weller) writes:

 > No need for that, I've saved the entire diatribe to a single file,
 > which occupies 351K on my account, uncompressed.  If anybody wants
 > it, they can have it :-)

   I was thinking that somebody should turn this into a journal
article.  But at 50K words and counting, it is already a small book!



--

					Robert I. Eachus

with Standard_Disclaimer;
use  Standard_Disclaimer;
function Message (Text: in Clever_Ideas) return Better_Ideas is...



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: cross linking packages
  1994-11-30 23:03   ` Matt Kennel
@ 1994-12-02 14:30     ` Norman H. Cohen
  0 siblings, 0 replies; 9+ messages in thread
From: Norman H. Cohen @ 1994-12-02 14:30 UTC (permalink / raw)


In article <3bj0br$mri@network.ucsd.edu>, mbk@inls1.ucsd.edu
(Matt Kennel) writes: 

|> Can somebody answer this question: 
|>
|>   Why were cyclical dependencies among types defined illegal?
|>
|> (I.e. type A defines features that use type B, and type B defines
|>  features that use type A.)
...
|>
|> It apparently works fine when they are in the same package.
|> Thus supposing you put two types each in their own package, both inside
|> an outer package.
|>
|> Do the mutual dependencies now work fine because they are both inside
|> one package, the outer one?

It is straightforward to define different types in a mutually recursive
data structure in two or more packages.  The following works in Ada 83
or Ada 9X: 

   package A_Package is
      type A is private;
      ...
   private
      type A_Node;  -- incomplete type declaration, completed in body
      type A is access A_Node;
   end A_Package;

   package B_Package is
      type B is private;
      ...
   private
      type B_Node;  -- incomplete type declaration, completed in body
      type B is access B_Node;
   end B_Package;

  with B_Package;
  package body A_Package is
     type A_Node is
        record
           ...
           B_Part: B_Package.B;
           ...
        end record;
     ...
  end A_Package;

  with A_Package;
  package body B_Package is
     type B_Node is
        record
           ...
           A_Part: A_Package.A;
           ...
        end record;
     ...
  end B_Package;

In this case both package bodies depend on both package declarations, but
the package declarations do not depend on each other, so there is no
circularity.

The difficulty the original questioner had was not in defining a mutually
recursive DATA STRUCTURE, but mutually recursive INTERFACES: 

with B_Package;                     |with A_Package;
package A_Package is                |package B_Package is
   type A is ...;                   |   type B is ...;
   procedure Op                     |   procedure Op
      (...; X: in B_Package.B; ...);|      (...; X: in A_Package.A; ...);
   ...                              |   ...
end A_Package;                      |end B_Package;

There is no way to do this without making each of the package
declarations dependent on the other.  In Ada 9X, a way out is to make A
and B tagged types derived from abstract tagged types declared elsewhere,
and to replace references to A and B in each others' interfaces by
references to the parent types' classwide types, thus eliminating the
circular dependences between the A_Package declaration and the B_Package
declaration.

The circularity presents a difficulty because Ada programs are compiled
and checked for consistency incrementally, a compilation unit at a time.
It was a principle design goal of Ada to facilitate the development of
huge programs in this manner, with each compilation unit checked, when it
is compiled, for consistency with all units whose facilities it uses.
This requires that units be compiled in some order such that all
declarations of facilties to be provided by a unit have been compiled
before all uses of those facilities.  It might be possible, in theory, to
rewrite the language rules to allow the mutually dependent declarations
of A_Package and B_Package by deferring all consistency checks and code
generation until all required packages had been compiled.  However, this
would violate the requirement for incremental recompilation and
rechecking of small pieces of a program and return us to the era of "big
bang" integration of modules compiled in isolation from each other.

Allowing ciruclarity in with clauses would make it possible for other
embarassing circularities to arise, and some way would have to be found
to deal with those: 

with B_Package;                     |with A_Package;
package A_Package is                |package B_Package is
   type A is                        |   type B is
      array (1 .. B_Package.B'Last) |      array (1 .. A_Package.A'Last)
         of Integer;                |         of Integer;
   ...                              |   ...
end A_Package;                      |end B_Package;

|> After all in a statically typed langauge like Ada, all variables
|> must be bound to types statically at compile time, so there
|> should be no ambiguity in "elaboration order" as there is no
|> run-time execution that must take place to bind variables
|> to types.

The type of a variable is indeed a compile-time property, but the size of
a type (and thus the code that must be generated to manipulate it
efficiently) is not.  Type declarations are "elaborated" (executed) at
run time.  Given a function Next_Number_In_Input_File that reads and
returns a value from an input file, for example, a type can be declared
as

   type T is array (1 .. Next_Number_In_Input_File) of Integer;

--
Norman H. Cohen    ncohen@watson.ibm.com



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: cross linking packages
  1994-11-28 12:56 Rene Burema
  1994-11-29 18:28 ` Norman H. Cohen
  1994-11-29 22:07 ` Robert Dewar
@ 1994-12-05 13:30 ` John Volan
  2 siblings, 0 replies; 9+ messages in thread
From: John Volan @ 1994-12-05 13:30 UTC (permalink / raw)


rburema@inter.NL.net (Rene Burema) writes:

>Dear netlanders,
>
>I have a problem specifing a seemingly simple problem in Ada. Anybody
>want to help?
>
>The problem is as follows:
>
>-------------------- package A -----------------------
>package A;
>type tA is private;
>procedure giveB(x: in tA,y: out tB);
>end A;
>-------------------- package B -----------------------
>package B;
>type tB is private;
>procedure giveA(x: in tB,y: out tA);
>end B;
>---------------
>where tA could be a reference to a  Doctor giving the reference to
>a Patient (tB) and vice versa.
>
>I need a with statement for B and A respectively. The Ada gnat compiler
>however stumbles into a circular dependency. How can I solve this without
>placing the two types tA and tB in a single package.
>
>Many thanks for any help
>
>	Arjen Duursma (arjen@capints.uucp)
>
>PS: don't use the return adress in the message, as it is a shared account.

(To quote the bowl of petunias that materialized 100 miles above the surface
of Magrathea: "Oh no, not again!" :-)

Arjen,

I would characterize this as the problem of establishing
"separately-encapsulated," yet "mutually-coupled" classes of objects.
On the one hand, we'd like to be able to separately encapsulate
different object classes as tagged private types in different
packages, thereby fully exploiting Ada's mechanisms for information
hiding.  On the other hand, we occasionally need mutual coupling
between classes even at their *interface* level: In other words, class
A would have primitive subprograms that include parameters of class B
-- and vice versa.  Folks like Bill Beckwith call this the "withing"
problem, because in Ada it is impossible to have two package *specs*
that both "with" each other.  (Although it's perfecty okay to have two
package *bodies* "with" each other's specs.)  This problem is also
known as Ada's "chicken-and-the-egg" problem.

A couple of months ago, I kicked off a rather long-winded thread
concerning this very subject.  At the end if it all, I believe I was
able to find a reasonable Ada9X workaround (described below) that
achieves what you are looking for.  Unfortunately, during that thread
I focussed on a particularly strong case of "mutual coupling" --
namely, object classes that formed "mutually-recursive" data
structures (where objects of different classes actually contain
pointers to each other).  Many participants in that thread focussed on
the mutually-recursive data structure to the exclusion of all else,
insisting that such a data structure represented a single,
undecomposable abstraction -- so there would supposedly be no point in
trying to "separately encapsulate" the classes anyway.  I kept
insisting that "separate encapsulation" is both desirable and
feasible, even under these circumstances.  But this issue seemed to
get lost in all the verbiage being slung back and forth about mutual
recursion.

I view the problem of "separately-encapsulated" yet "mutually-coupled"
classes as really being an issue of "deferred coupling": We need to
couple two or more classes together, but if we want to keep them
separately encapsulated we are forced to somehow *defer* that coupling
until *after* all their interfaces can be established.  One way or
another (short of a language change), achieving this deferred coupling
will require some special tricks and some extra work.

Most of the Ada9X cognoscenti in this newsgroup seem to favor exploiting
abstract types and inheritance as a mechanism for "deferred coupling."
But, IMHO, I feel this is an "improper" use (and perhaps even an outright
abuse) of the mechanism of inheritance.  (I think it's a symptom of
"Hammer Syndrome", as in the proverb "When your favorite tool is a
hammer, every problem starts to look like a nail." :-) Personally, I
prefer achieving deferred coupling by exploiting other mechanisms that
Ada9X provides (see below), and I would rather reserve inheritance for
what it was originally "intended" for: representing true
generalization/specialization relationships that come directly from
the problem domain.

A discussion of my technique follows.  I hope this helps you out.

				-- John Volan

(LONG POST WARNING.  ^L inserted for all you tired souls who just want
to hit 'n' now and have done with it :-)
\f
================================================================================

"SEPARATELY-ENCAPSULATED" YET "MUTUALLY-COUPLED" OBJECT CLASSES IN Ada 9X
-------------------------------------------------------------------------

One possible solution: Use an extra level of indirection, and
establish your object classes in *three* steps ("Identity, Interface,
and Implementation") rather than just the classic two steps
("Interface and Implementation").

Step 1: ESTABLISH THE CLASS "IDENTITIES":

First, "forward-declare" each class.  In other words, for every class
X, establish that the class will *exist*, but do so before actually
specifying the *interface* to class X.  Do this by introducing a
package X_Id, containing little more than a private type X_Id.Val.  A
value of type X_Id.Val will represent an "opaque identity value" for
an X object.  In other words, it will somehow "designate" or
"reference" or "point to" an X object.  However, at the moment, such
references will be "opaque", because we haven't actually declared the
type for X objects themselves, yet.  We do promise to eventually
provide conversions between X objects and these X "identity values",
but we have to defer that until later, once we've had a chance to
establish the interface to the X class.

We also promise to preserve type-safety: X_Id.Val's will *only* be
interconvertible with X objects, and *not* with any other classes of
object.  The only way to generate an X_Id.Val will be from an access
value designating an X object.  And the only thing we'll be able to
extract out of an X_Id.Val will be an access value designating an X
object.  But again, all this is deferred until we can actually declare
the X object type.  (Depending on how we implement the X_Id.Val types,
guaranteeing type-safety may be a matter of programmer discipline.
Getting the language to 100% guarantee type safety here may be tricky
-- but more about this issue later.)

This step is really pretty simple. For instance, let's say we're going
to have a Doctor class and a Patient class (and maybe other classes, too).
Here's how we could "forward-declare" these classes:

  ----------------------------------------------------------------------
  package Doctor_Id is
    type Val is private;  -- That's all you need, really (for now).
  private
    type Val is ... -- more about implementation in a while
  end Doctor_Id;
  ----------------------------------------------------------------------
  package Patient_Id is
    type Val is private;  -- That's all you need, really (for now).
  private
    type Val is ... -- more about implementation in a while
  end Patient_Id;
  ----------------------------------------------------------------------
  ... -- similar packages for other classes


Step 2: ESTABLISH THE CLASS "INTERFACES":

Next, write the "interfaces" for your classes:  For each class X,
introduce a package spec X, containing a tagged type X.Obj (it can be
private, limited, abstract, however you please) that actually
represents your X objects.  Declare all the appropriate primitive
subprograms for your class.  This step is not much different than
what people normally do when they want to introduce an object-oriented
type in an Ada package.

However, there is one difference: Wherever one of these primitive
subprograms needs a parameter of some *other* class Y, don't try to
"with" the spec of Y.  Instead, "with" the corresponding Y_Id package,
and then declare parameters of type Y_Id.Val.  That way, you don't
have to wait until the interface of class Y is established, before
establishing the interface of class X.  Yet this still lets you
specify that class X and Y are coupled.  And if X and Y need to be
*mutually* coupled, this is not a problem: The *interfaces* of the two
class packages don't need to "with" each other -- they only need to
"with" each other's *identity* packages.

Also, fulfill the promise that you made when you introduced the X_Id
package: Declare a general-access-to-classwide-type X.Ptr, and then
declare functions that allow free interconversion between "opaque X
identity values" (X_Id.Val's) and "transparent X pointer values"
(X.Ptr's).

For instance, let's say we have class Doctor and class Patient (and
perhaps other classes, as well).  Let's say that class Doctor has a
method called Doctor.Treat_Patient, class Patient has a method called
Patient.Receive_Treatment, and Doctor.Treat_Patient must call
Patient.Receive_Treatment.  At the same time, class Patient has a
method called Patient.Pay_Doctor, class Doctor has a method called
Doctor.Receive_Payment, and Patient.Pay_Doctor must call
Doctor.Receive_Payment.  Consequently, we have a mutual-coupling
situation between the two classes.  But since the "existence" of the
two classes has already been established via their Id packages,
establishing this mutual coupling at the interface level is not a
problem:

  ----------------------------------------------------------------------
  with Doctor_Id, Patient_Id, ... ;  -- possibly other stuff, too
  package Doctor is  

    type Obj is ... tagged ... ; -- abstract, limited, private, what have you

    procedure Treat_Patient (The_Doctor : in out Doctor.Obj;
                             The_Patient_Id : in Patient_Id.Val);

    procedure Receive_Payment (The_Doctor : in out Doctor.Object;
                               From_Patient_Id : in Patient_Id.Val);

    ... -- possibly other Doctor primitives

    type Ptr is access all Doctor.Obj'Class;  -- fulfill the "promise"
    function To_Id (The_Doctor_Ptr : in Doctor.Ptr) return Doctor_Id.Val;
    function To_Ptr (The_Doctor_Id : in Doctor_Id.Val) return Doctor.Ptr;

    ...
  end Doctor;
  ----------------------------------------------------------------------
  with Patient_Id, Doctor_Id, ... ; -- possibly other stuff, too
  package Patient is  

    type Obj is ... tagged ... ; -- abstract, limited, private, what have you

    procedure Pay_Doctor (The_Patient : in out Patient.Obj;
                          The_Doctor_Id : in Doctor_Id.Val);

    procedure Receive_Treatment (The_Patient : in out Patient.Object;
                                 From_Doctor_Id : in Doctor_Id.Val);

    ... -- possibly other Patient primitives

    type Ptr is access all Patient.Obj'Class;  -- fulfill the "promise"
    function To_Id (The_Patient_Ptr : in Patient.Ptr) return Patient_Id.Val;
    function To_Ptr (The_Patient_Id : in Patient_Id.Val) return Patient.Ptr;

    ...
  end Patient;
  ----------------------------------------------------------------------
  ... -- similar package specs for other classes


Step 3: ESTABLISH THE CLASS "IMPLEMENTATIONS":

Finally, flesh out the "implementation" for each class X, in its
corresponding package body X.  For every other class Y which class X
needs to interact with, go ahead and "with" the spec of the
corresponding package Y.  Even if there is a mutual-coupling situation
between X and Y, there is no problem: It's always been possible for
the *bodies* of two packages to "with" each other's specs (even in Ada
83).  Thus, we have managed to achieve deferred coupling: We were able
to wait until the *bodies* of these packages to actually couple them
together.

If an X subprogram includes a parameter of type Y_Id.Val, and it needs
to invoke some Y subprogram upon the Y object designated by this
Y_Id.Val, then make use of the conversion function Y.To_Ptr to get
access to the actual Y object.  If the Y subprogram needs to be passed
a parameter of type X_Id.Val, make use of the conversion function
X.To_Id to pack an X'Access into an "opaque" X identity value.

Finally, provide some implementation for those X.Ptr <=> X_Id.Val
interconversion functions.  (More about how to achieve this in a
while.)

For instance, here's how we can implement the bodies of Doctor and Patient,
including getting Doctor.Treat_Patient to call Patient.Receive_Treatment,
and getting Patient.Pay_Doctor to call Doctor.Receive_Payment:

  ----------------------------------------------------------------------
  with Patient, ... ;  -- possibly other stuff, too
  package body Doctor is  

    procedure Treat_Patient (The_Doctor : in out Doctor.Obj;
                             The_Patient_Id : in Patient_Id.Val)
    is
      The_Patient : Patient.Obj'Class renames Patient.To_Ptr(The_Patient_Id).all
      The_Doctor_Id : constant Doctor.Id := Doctor.To_Id(The_Doctor'Access);
    begin
      ... -- possibly do other stuff with The_Doctor
      Patient.Receive_Treatment (The_Patient, From_Doctor_Id => The_Doctor_Id);
    end Treat_Patient;

    procedure Receive_Payment (The_Doctor : in out Doctor.Object;
                               From_Patient_Id : in Patient_Id.Val) is
    begin
      ... -- do whatever
    end Receive_Payment;

    ... -- possibly other Doctor primitives

    -- more about these in a moment:
    function To_Id (The_Doctor_Ptr : in Doctor.Ptr) return Doctor_Id.Val ...
    function To_Ptr (The_Doctor_Id : in Doctor_Id.Val) return Doctor.Ptr ...

  end Doctor;
  ----------------------------------------------------------------------
  with Doctor, ... ;  -- possibly other stuff, too
  package body Patient is  

    procedure Pay_Doctor (The_Patient : in out Patient.Obj;
                          The_Doctor_Id : in Doctor_Id.Val)
    is
      The_Doctor : Doctor.Obj'Class renames Doctor.To_Ptr(The_Doctor_Id).all
      The_Patient_Id : constant Patient.Id := Patient.To_Id(The_Patient'Access);
    begin
      ... -- possibly do other stuff with The_Patient
      Doctor.Receive_Payment (The_Doctor, From_Patient_Id => The_Patient_Id);
    end Pay_Doctor;

    procedure Receive_Treatment (The_Patient : in out Patient.Object;
                                 From_Doctor_Id : in Doctor_Id.Val) is
    begin
      ... -- do whatever
    end Receive_Treatment;

    ... -- possibly other Patient primitives

    -- more about these in a moment:
    function To_Id (The_Patient_Ptr : in Patient.Ptr) return Patient_Id.Val ...
    function To_Ptr (The_Patient_Id : in Patient_Id.Val) return Patient.Ptr ...

  end Patient;
  ----------------------------------------------------------------------
  ... -- similar package bodies for other classes

================================================================================

IMPLEMENTATION NOTES:

How do we actually implement those X_Id.Val <=> X.Ptr interconversions?  

VISIBILITY ISSUE: Before considering mechanisms for doing this, let's
first tackle the issue of visibility.  These functions need to have
visibility to the full implementation of both X_Id.Val and X.Ptr.  One
way we might solve this would be to introduce a child package called,
say, X_Id.Conversion, and have it "with" spec of package X.  This
gives X_Id.Conversion visibility both to the full X_Id.Val type and
the X.Ptr type.  X_Id.Conversion could then provide implementations
for To_Id and To_Ptr.  The *body* of the X package could then "with"
X_Id.Conversion and could then use the functions from there as the actual
implementations for X.To_Id and X.To_Ptr, via renaming-as-body
declarations.  So, for instance:

  ----------------------------------------------------------------------
  with Doctor;
  package Doctor_Id.Conversion is
    function To_Id (The_Doctor_Ptr : in Doctor.Ptr) return Doctor_Id.Val;
    function To_Ptr (The_Doctor_Id : in Doctor_Id.Val) return Doctor.Ptr;
  end Doctor_Id.Conversion;
  ----------------------------------------------------------------------
  package body Doctor_Id.Conversion is

    function To_Id (The_Doctor_Ptr : in Doctor.Ptr) return Doctor_Id.Val is
    begin
      return ... -- whatever it takes to convert a Doctor.Ptr into a
                 -- Doctor_Id.Val, knowing the implementation of both
    end To_Id;

    function To_Ptr (The_Doctor_Id : in Doctor_Id.Val) return Doctor.Ptr ...
    begin
      return ... -- whatever it takes to convert a Doctor_Id.Val into a
                 -- Doctor.Ptr, knowing the implementation of both
    end To_Ptr;

    -- (more about these function in a while...)
  end Doctor_Id.Conversion;
  ----------------------------------------------------------------------
  with Doctor_Id.Conversion;
  with Patient, ...; -- and whatever else, as before
  package body Doctor is
    ... -- all the Doctor primitives, as before

    function To_Id (The_Doctor_Ptr : in Doctor.Ptr) return Doctor_Id.Val
      renames Doctor_Id.Conversion.To_Id;

    function To_Ptr (The_Doctor_Id : in Doctor_Id.Val) return Doctor.Ptr
      renames Doctor_Id.Conversion.To_Ptr;

  end Doctor;
  ----------------------------------------------------------------------
  ... -- similarly for Patient, and any other class


In other words, Doctor.To_Id and Doctor.To_Ptr just become convenient
repackagings of Doctor_Id.Conversion.To_Id and Doctor_Id.Conversion.To_Ptr, 
respectively.  Unfortunately, this scheme is a bit complicated, and it
adds extra entries into our Ada library, which we now have to manage.

Another way to attack this would be to exploit generics.  You should
already have noticed that there is a lot of repetitiveness in all
those X_Id packages.  That makes them a prime candidate for some
reusable generic solution.  Indeed, we can easily genericize the
notion of an Id package.  Moreover, we can even genericize the notion
of a providing a Conversion package that can be instantiated at a
later time, once there's an Obj and Ptr type available.  To achieve
all this, I favor using nested generics:

  --------------------------------------------------------------------
  generic
  package Id is

    type Val is private;

    generic
      type Obj (<>) is abstract tagged limited private;
      type Ptr is access all Obj'Class;
    package Conversion is
      function To_Id (The_Ptr : in Ptr) return Id.Val;
      function To_Ptr (The_Id : in Id.Val) return Ptr;
    end Conversion;

  private
    type Val is ... ; -- still holding my cards close to the vest for now :-)
  end Id;
  ----------------------------------------------------------------------

Thus, instead of having to laboriously spell out all those X_Id packages,
we can get the same effect by just doing some quick instantiations:

  ----------------------------------------------------------------------
  with Id;
  package Doctor_Id is new Id;
  ----------------------------------------------------------------------
  with Id;
  package Patient_Id is new Id;
  ----------------------------------------------------------------------
  ... -- etc for other classes


We could have made Id.Conversion a generic child package, but that
would only force us to instantiate it as a library-level child
package.  With this nesting, we can choose to instantiate
X_Id.Conversion right in the spec of package X if we wish, and then
directly declare X.To_Id and X.To_Ptr as renamings.  For instance:

  ----------------------------------------------------------------------
  with Doctor_Id, Patient_Id, ... ; -- etc, as before
  package Doctor is
    type Obj is ... -- as before

    ... -- primitive Doctor subprograms, as before

    type Ptr is access all Obj'Class;

    package Conversion is new Doctor_Id.Conversion (Doctor.Obj, Doctor.Ptr);

    function To_Id (The_Doctor_Ptr : in Doctor.Ptr) return Doctor_Id.Val
      renames Conversion.To_Id;

    function To_Ptr (The_Doctor_Id : in Doctor_Id.Val) return Doctor.Ptr
      renames Conversion.To_Ptr;

    ...
  end Doctor;
  ----------------------------------------------------------------------

Then there would be no need for any bodies for To_Id and To_Ptr within
the body of package Doctor.


TYPE-SAFETY ISSUE: Another issue we must address is the promise we
made to preserve type-safety.  We're supposed to guarantee that a
given X_Id.Val type will be interchangeable with one and *only* one
Ptr type (hopefully, type X.Ptr).  If we can guarantee that valid
X.Ptr values are the *only* things we'll stuff into X_Id.Val's, then
we can guarantee that X.Ptr values will be the only things to come out
of them again.

But the existence of this Conversion generic seems to fly in the face
of that.  What if we try instantiating a given X_Id.Conversion generic
more than once, for different Ptr types?  Obviously, general type
insanity would ensue.  Clearly, to avoid breaking type safety, we must
guarantee that a given X_Id.Conversion generic will be instantiated
once and *only* once.

But how do we prevent people from instantiating a given generic
X_Id.Conversion more than once?  One possibility is to just make it a
point of software engineering discipline:  Get a big mallet and pummel
anyone who tries this. :-)

Or perhaps we could introduce a special Once_Only pragma that can be
applied to a generic template.  Then we could hack up the compiler and
the binder/linker to make it illegal to create an executable that contains
more than one instantiation of a Once_Only generic.  Maybe someone could
experiment with this with the GNAT compiler.  However, such a pragma
would be compiler-specific, and thus not very portable.

Another possibility is to simply add a run-time check that raises an
exception if more than one instantiation of X_Id.Conversion is
elaborated.  This is pretty easy to do:

  ----------------------------------------------------------------------
  package body Id is

    One_Conversion_Already : Boolean := False;
    Dual_Conversion_Error : exception;

    package body Conversion is

      function To_Id ...
      function To_Ptr ...

    begin -- Conversion
      if One_Conversion_Already then
        raise Dual_Conversion_Error;
      else
        One_Conversion_Already := True;
      end if;
    end Conversion;

  end Id;
  ----------------------------------------------------------------------

Unfortunately, this doesn't give us a mechanism for detecting this
error at compilation time or bind/link time. We have to actually execute
the program to detect the error.  But at least this scheme will
eventually detect this error.  Moreover, it has the advantage of being
100% portable.  And a bit of programmer discipline will generally keep
this exception from cropping up at all.


NOW FOR SOME MAGIC: At this point you may be chomping at the bit and
saying, "This is all very nice, but how do we actually *implement* the
Id.Val type and the Id.Conversion functions?"  Well, all we really
need is some type that can somehow "magically" hold the value of a
general access-to-classwide-type, without loss of information.  However,
we need to establish this magical type long before we've actually
declared any of our tagged types.

One way to finesse this would be to simply hack up the compiler and
make the whole Id generic an intrinsic (like Ada.Unchecked_Conversion,
and so forth). I suppose somebody could experiement with the GNAT
compiler to provide this capability.  However, once again, this would not 
constitute a very portable solution.

So in the meantime we need something we could use today, on any Ada9X
compiler.  I suggest implementing the Id.Conversion functions using
Unchecked_Conversion.

That's right, Unchecked_Conversion.

Yes, it's controversial.  However, one very nice feature is that,
since Unchecked_Conversion is intrinsic, it is very likely that 
calls to our Id.Conversion functions would have *zero* run-time cost.

I know what you're thinking: What about type safety?  Doesn't
Unchecked_Conversion throw all that out the window?  Well, normally it
would.  But we just went to great pains to guarantee type-safety by
assuring that there would be a strict one-to-one correspondence
between a given X_Id.Val type and a given X.Ptr type.  Also, remember
that each X_Id.Val type is a private type, and so the repertoire of
things we can do with it is severely limited: Basically, all we'll be
able to do is flip back and forth between X_Id.Val's and X.Ptr's.

So, all that remains is to make sure that an X_Id.Val can reliably
hold the bit-pattern of an X.Ptr type (i.e., a general-access-to-classwide
 type) without any loss of information.  Essentially, X_Id.Val could
be any type at all, as long as X_Id.Val'Size = X.Ptr'Size.

Probably the best way to assure this would be to implement X_Id.Val
itself as a general-access-to-classwide type.  The actual designated
tagged type would not matter -- it could just be some "dummy" type,
hidden in the private part, and never really used for anything.

The only issue is portability.  Will a given Ada compiler implement
all general-access-to-classwide types with the same 'Size?  I believe
this is a reasonably safe assumption.  However, we could very easily
double-check this at elaboration time and raise an exception if we
discover Id.Val'Size /= Ptr'Size.  (At which point, we could perhaps
try some other tricks.)

Thus, the complete, fully bullet-proofed implementation of my generic
Id package could be written as follows:

  ----------------------------------------------------------------------
  generic
  package Id is

    type Val is private;
    None : constant Id.Val;

    generic
      type Obj (<>) is abstract tagged limited private;
      type Ptr is access all Obj'Class;
    package Conversion is
      function To_Id (The_Ptr : in Ptr) return Id.Val;
      function To_Ptr (The_Id : in Id.Val) return Ptr;
    end Conversion;

  private -- Id

    type Dummy is abstract tagged limited null record; -- never really used
    type Val is access all Dummy'Class;
    None : constant Id.Val := null;

  end Id;
  ----------------------------------------------------------------------
  with Ada.Unchecked_Conversion;
  package body Id is
    
    One_Conversion_Already : Boolean := False;
    Dual_Conversion_Error : exception;
    Portability_Problem : exception;

    package body Conversion is

      function Ptr_To_Id is new Ada.Unchecked_Conversion (Ptr, Id.Val);
      function To_Id (The_Ptr : in Ptr) return Id.Val renames Ptr_To_Id;

      function Id_To_Ptr is new Ada.Unchecked_Conversion (Id.Val, Ptr);
      function To_Ptr (The_Id : in Id.Val) return Ptr renames Id_To_Ptr;

    begin -- Conversion
      if Id.Val'Size /= Ptr'Size then
        raise Portability_Problem;
      elsif One_Conversion_Already then
        raise Dual_Conversion_Error;
      else
        One_Conversion_Already := True;
      end if;
    end Conversion;

  end Id;
  ----------------------------------------------------------------------

All you need to do now is install this little support package into your
library, and then start cranking out those mutually-coupled class
packages!  :-)

(FYI: As of version 1.83, bugs in GNAT make it impossible to use this
generic in this form, so you may need to wait a bit to try this. I'll
give version 2.00 a shot before I file any bug reports, though.)

Happy OOPing!  :-)

				-- John Volan

--------------------------------------------------------------------------------
--  Me : Person := (Name                => "John Volan",
--                  Company             => "Raytheon Missile Systems Division",
--                  E_Mail_Address      => "jgv@swl.msd.ray.com",
--                  Affiliation         => "Enthusiastic member of Team Ada!",
--                  Humorous_Disclaimer => "These opinions are undefined " &
--                                         "by my employer and therefore " &
--                                         "any use of them would be "     &
--                                         "totally erroneous.");
--------------------------------------------------------------------------------




^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: cross linking packages
@ 1994-12-06 16:34 Bob Wells #402
  0 siblings, 0 replies; 9+ messages in thread
From: Bob Wells #402 @ 1994-12-06 16:34 UTC (permalink / raw)


Robert Dewar <dewar@CS.NYU.EDU> writes

> Matt a brief answer to your question is that order of elaboration of
> types is critical, and in particular if package a with's package b,
> it is essential that the spec of b be elaborated before the spec of a.
> Remember that elaboration of types in Ada can have all sorts of side
> effects.

Not to mention the obvious extension of this where the body of b must be
elaborated before the spec of a if the spec of a contains declarations of
initialised variables or constants whose values are set by functions
declared in the spec of b. I guess this would also be try for named
numbers aswell.

Bob W.



^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~1994-12-06 16:34 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
1994-12-06 16:34 cross linking packages Bob Wells #402
  -- strict thread matches above, loose matches on Subject: below --
1994-11-28 12:56 Rene Burema
1994-11-29 18:28 ` Norman H. Cohen
1994-11-29 22:07 ` Robert Dewar
1994-11-30 16:33   ` David Weller
1994-12-01 13:44     ` Robert I. Eachus
1994-11-30 23:03   ` Matt Kennel
1994-12-02 14:30     ` Norman H. Cohen
1994-12-05 13:30 ` John Volan

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