[comp.lang.ada] Pre-elaboration Problems

wilson%anchor.DECnet@lll-icdc.arpa ("ANCHOR::WILSON") (12/30/86)

I have run into a problem with Ada and both of the solutions I have found 
bother me.

I have a generic package which a user (i.e., programmer) can use to easily 
perform DECnet network I/O.  The package can support many similtaneous 
I/O channels.  The package imports a MESSAGE_TYPE and a HANDLE_MESSAGE 
procedure and exports an ADDRESS_TYPE and a SEND procedure.  Here's the
spec.:

	package NET_READER is

	    type ADDRESS_TYPE is private;

	    generic
		type MESSAGE_TYPE is private;
		with procedure HANDLE_MESSAGE(
					FROM	: in ADDRESS_TYPE;
					MESSAGE	: in MESSAGE_TYPE
					     );

	    package READER is

		procedure SEND(
				TO	: in ADDRESS_TYPE;
				MESSAGE	: in MESSAGE_TYPE
			      );

	    end READER;

	private . . .

	end NET_READER;


The idea is that the package user just needs to supply a HANDLE_MESSAGE 
procedure and all the network stuff will be taken care of for him.  It is 
expected that HANDLE_MESSAGE will sometimes call SEND to send a reply back 
to the originator of the current message.

The user program looks something like this:

	with NET_READER;

	procedure TEST is

	    procedure HANDLE_MESSAGE(
			FROM	: in NET_READER.ADDRESS_TYPE;
			MESSAGE	: in MESSAGE_TYPE
				    );

	    package NET is
		new NET_READER.READER( INTEGER, HANDLE_MESSAGE );

	    procedure HANDLE_MESSAGE(
			FROM	: in NET_READER.ADDRESS_TYPE;
			MESSAGE	: in MESSAGE_TYPE
				    ) is

	    begin

		... NET.SEND ...

	    end HANDLE_MESSAGE;

	begin null; end TEST;

The problem is this:  if a message is "waiting at the door," HANDLE_MESSAGE 
gets called before it is elaborated, resulting in a PROGRAM_ERROR.  This is
because the declarative part of TEST is elaborated in top-to-bottom order.  
Unfortunately, HANDLE_MESSAGE needs visibility of NET to call SEND.

My solutions are these:

	1)  Put a loop and an exception handler in the body of 
	    NET_READER.READER and wait for PROGRAM_ERROR to go away.

	2)  Put a START procedure in NET_READER.READER which the user
	    has to call to start things up.

Solution 1 is a problem for a few reasons.  Should the loop be infinite?
How long should we wait?  What if the user's HANDLE_MESSAGE routine is
raising another kind of PROGRAM_ERROR and we're just getting a propogated 
version?  The nice side of this way is that all the messy details are 
hidden in the package body.  The user couldn't care less about the order of
elaboration, and likely won't understand the problem in the first place.

Solution 2 has the problem of requiring explicit action on the user's 
part.  START looks like an initialization routine, and in all the rest of 
our packages initialization is done inside the package.  Asthetically, we 
loose the "begin null; end" main program body which has looked so 
appealing.

A more general problem is one where the generic package imports a user 
routine and doesn't export anything.  In this case, the user could order 
his declarative part such that the package instantiation comes after the 
procedure body, but *I* wouldn't have naturally done that anyway.  I'd have 
made the forward declaration of my procedure just so all my declarations 
would appear together on a small page.  Then, everything would have worked 
nicely until the window was hit a few months down the road . . . .

My questions to the Ada community are these:

	o  Is there a Solution 3?

	o  Can anyone make me feel better about Solutions 1 or 2?

	o  How can I convince myself that my program isn't some day going
	   to call an unelaborated routine?  Is there a simple checklist
	   I can use?

Finally, why don't I get a PROGRAM_ERROR on the following program?  It seems
like as soon as the main task has elaborated task T, T should start running
(given the higher priority) before P is elaborated . . . .
(I compiled and ran this using VAX Ada on a VAX 11/750.)

	with TEXT_IO;	use TEXT_IO;
	with SYSTEM;

	procedure TEST_1 is

	    task T is
		pragma PRIORITY( SYSTEM.PRIORITY'last );
	    end T;

	    procedure P;

	    task body T is
	    begin  P;  end T;

	    procedure P is
	    begin  PUT_LINE( "Hi!" );  end P;

	begin null; end TEST_1;

Thanks for your interest,

			--- Rick Wilson

wilson%anchor.decnet@lll-icdc.arpa
(415) 423-6662
------

wilson%anchor.DECnet%lll-icdc.arpa%taurus.BITNET@WISCVM.WISC.EDU ("ANCHO) (12/30/86)

I have run into a problem with Ada and both of the solutions I have found
bother me.

I have a generic package which a user (i.e., programmer) can use to easily
perform DECnet network I/O.  The package can support many similtaneous
I/O channels.  The package imports a MESSAGE_TYPE and a HANDLE_MESSAGE
procedure and exports an ADDRESS_TYPE and a SEND procedure.  Here's the
spec.:

        package NET_READER is

            type ADDRESS_TYPE is private;

            generic
                type MESSAGE_TYPE is private;
                with procedure HANDLE_MESSAGE(
                                        FROM    : in ADDRESS_TYPE;
                                        MESSAGE : in MESSAGE_TYPE
                                             );

            package READER is

                procedure SEND(
                                TO      : in ADDRESS_TYPE;
                                MESSAGE : in MESSAGE_TYPE
                              );

            end READER;

        private . . .

        end NET_READER;


The idea is that the package user just needs to supply a HANDLE_MESSAGE
procedure and all the network stuff will be taken care of for him.  It is
expected that HANDLE_MESSAGE will sometimes call SEND to send a reply back
to the originator of the current message.

The user program looks something like this:

        with NET_READER;

        procedure TEST is

            procedure HANDLE_MESSAGE(
                        FROM    : in NET_READER.ADDRESS_TYPE;
                        MESSAGE : in MESSAGE_TYPE
                                    );

            package NET is
                new NET_READER.READER( INTEGER, HANDLE_MESSAGE );

            procedure HANDLE_MESSAGE(
                        FROM    : in NET_READER.ADDRESS_TYPE;
                        MESSAGE : in MESSAGE_TYPE
                                    ) is

            begin

                ... NET.SEND ...

            end HANDLE_MESSAGE;

        begin null; end TEST;

The problem is this:  if a message is "waiting at the door," HANDLE_MESSAGE
gets called before it is elaborated, resulting in a PROGRAM_ERROR.  This is
because the declarative part of TEST is elaborated in top-to-bottom order.
Unfortunately, HANDLE_MESSAGE needs visibility of NET to call SEND.

My solutions are these:

        1)  Put a loop and an exception handler in the body of
            NET_READER.READER and wait for PROGRAM_ERROR to go away.

        2)  Put a START procedure in NET_READER.READER which the user
            has to call to start things up.

Solution 1 is a problem for a few reasons.  Should the loop be infinite?
How long should we wait?  What if the user's HANDLE_MESSAGE routine is
raising another kind of PROGRAM_ERROR and we're just getting a propogated
version?  The nice side of this way is that all the messy details are
hidden in the package body.  The user couldn't care less about the order of
elaboration, and likely won't understand the problem in the first place.

Solution 2 has the problem of requiring explicit action on the user's
part.  START looks like an initialization routine, and in all the rest of
our packages initialization is done inside the package.  Asthetically, we
loose the "begin null; end" main program body which has looked so
appealing.

A more general problem is one where the generic package imports a user
routine and doesn't export anything.  In this case, the user could order
his declarative part such that the package instantiation comes after the
procedure body, but *I* wouldn't have naturally done that anyway.  I'd have
made the forward declaration of my procedure just so all my declarations
would appear together on a small page.  Then, everything would have worked
nicely until the window was hit a few months down the road . . . .

My questions to the Ada community are these:

        o  Is there a Solution 3?

        o  Can anyone make me feel better about Solutions 1 or 2?

        o  How can I convince myself that my program isn't some day going
           to call an unelaborated routine?  Is there a simple checklist
           I can use?

Finally, why don't I get a PROGRAM_ERROR on the following program?  It seems
like as soon as the main task has elaborated task T, T should start running
(given the higher priority) before P is elaborated . . . .
(I compiled and ran this using VAX Ada on a VAX 11/750.)

        with TEXT_IO;   use TEXT_IO;
        with SYSTEM;

        procedure TEST_1 is

            task T is
                pragma PRIORITY( SYSTEM.PRIORITY'last );
            end T;

            procedure P;

            task body T is
            begin  P;  end T;

            procedure P is
            begin  PUT_LINE( "Hi!" );  end P;

        begin null; end TEST_1;

Thanks for your interest,

                        --- Rick Wilson

wilson%anchor.decnet@lll-icdc.arpa
(415) 423-6662
------

arny@wayback.UUCP (Arny B. Engelson) (01/06/87)

In article <8701010253.AA21054@ucbvax.Berkeley.EDU>, wilson%anchor.DECnet@lll-icdc.arpa ("ANCHOR::WILSON") writes:

(Package spec here containing generic package with generic formal subprogram
as one of the parameters, and a procedure within the generic package that is
called by the subprogram supplied by the user as the actual to the generic).

(main program contains declaration of subprogram to be passed, instantiation
of generic package, then the body of the previously declared subprogram.
Program_Error is raised because something in the instantiated generic invokes
the user's subprogram before the body of the subprogram is elaborated).

(two unsatisfactory solutions here).

> My questions to the Ada community are these:
> 
> 	o  Is there a Solution 3?
Yes, I think so.  See my comments on the program below.
> 
> 	o  Can anyone make me feel better about Solutions 1 or 2?
Solution 2 is better than solution 1.
> 
> 	o  How can I convince myself that my program isn't some day going
> 	   to call an unelaborated routine?  Is there a simple checklist
> 	   I can use?
Use careful design, keep reading the LRM, and hope the rules don't change
too often.  I've run into similar order-of-elaboration problems and have no
checklist to offer.

> Finally, why don't I get a PROGRAM_ERROR on the following program?  It seems
> like as soon as the main task has elaborated task T, T should start running
> (given the higher priority) before P is elaborated . . . .
> (I compiled and ran this using VAX Ada on a VAX 11/750.)
> 
> 	with TEXT_IO;	use TEXT_IO;
> 	with SYSTEM;
> 	procedure TEST_1 is
> 	    task T is
> 		pragma PRIORITY( SYSTEM.PRIORITY'last );
> 	    end T;
> 	    procedure P;
> 	    task body T is
> 	    begin  P;  end T;
> 	    procedure P is
> 	    begin  PUT_LINE( "Hi!" );  end P;
> 	begin null; end TEST_1;
> 
> 			--- Rick Wilson
> wilson%anchor.decnet@lll-icdc.arpa
> (415) 423-6662


   I believe the appropriate reference is LRM 9.3:2, which states:
"activation of the task object starts after the elaboration of the declarative
part (that is, after passing the reserved word BEGIN following the declarative
part)".
   Therefore, task T doesn't begin executing until after the body of procedure
P is elaborated.
   It would follow that you could solve the first problem by using a task to
delay the execution of the instantiated package until everything was
elaborated.  Perhaps by encapsulating the instantiation of the generic within
a task.  I haven't the time to search for an exact solution, but I think this
is a worthwhile direction to explore.

Arny B. Engelson
{bonnie|clyde|ihnp4}!wayback!arny
AT&T Bell Laboratories, Whippany, N.J.
(201) 386-4816