[net.lang.ada] Procedure/function parameters

andy@Shasta.UUCP (07/15/86)

Forgive my pascal-eze, but the following partial program is quite
difficult to implement in Ada(TM) without drastic changes.  Generics
don't work because both top level procedures have state; reinstantiating
either loses that state.  Passing a tag that says which procedure to
call (the enumeration scheme) doesn't work; the procedures being
passed are not visible to the called procedure (and besides, they
refer to variables of private types that the called procedure
shouldn't know about).

Notice how simple the procedure parameter solution is.  (I reused
names to discourage Ada implementations that change the scope of
various names.  A real program might use distinct names but still
want to maintain separate scopes.)

I recently wrote a compiler where I used procedure parameters
extensively because they allowed me to maintain local state.  Without
this local state, the program would not have been understandable.

procedure a (procedure b (procedure c));
type foo : <something>;
var bar : foo;

procedure c;
begin	{c}
<manipulate bar>
end;	{c}

begin	{a}
<init bar>
b(c);
<manipulate bar>
end;	{a}

procedure b;	{intential name reuse throughout}
type foo : <something>;
var bar : foo;

procedure c (procedure a);
begin	{c}
a;
<manipulate bar>
end;	{c}

begin	{b}
<init bar>
a(c);
<manipulate bar>
end;	{b}

-andy
decwrl!glacier!shasta!andy
andy@sushi.stanford.edu

richw@ada-uts (07/19/86)

First of all, I'd rather not comment about the style of programming
you've used in your example -- I personally find it to be hard to
follow, but I'd rather discuss a MUCH more important issue which
your example touches on...

>>  ...  Passing a tag that says which procedure to
>>  call (the enumeration scheme) doesn't work; the procedures being
>>  passed are not visible to the called procedure (and besides, they
>>  refer to variables of private types that the called procedure
>>  shouldn't know about).

You're right!  The enumeration scheme does not work in this case --
AND FOR VERY GOOD REASON!  The enumeration method fails, as you say,
because of scoping limitations -- HOWEVER, those limitations come
in handy, as Robert Firth alluded to in an earlier posting: procedure
passing in languages which include nesting of procedure definitions
can lead to very nasty consequences...

>>  Procedure variables can create a loophole in the scoping
>>  structure in the same way that reference variables can: it is
>>  possible to assign to a variable of greater extent a value of
>>  smaller extent, and hence access via the variable an object that
>>  no longer exists.  There is no good way reliably to guard
>>  against this at compile time, for which reason some people
>>  dislike procedure and reference variables....
>>       -- Robert Firth

When I first read this, I had some trouble imagining what he meant,
but then I managed to come up with a small example:

package P is

    type One_Argument_Procedure is procedure (i : in Integer);
         -- The above is not legal Ada, but you know what I mean

    function A return One_Argument_Procedure;
end P;

package body P is

    function A return One_Argument_Procedure
    is
        local_variable : Integer;

        procedure Local_Proc (i : in Integer)
        is begin
            local_variable := i;
        end;
    begin
        return Local_Proc;
    end A;

end P;


The question is: What in the WORLD happens when someone tries to
call the result of function A?  You don't REALLY want to be able to
modify the stack-frame of a procedure that has ALREADY RETURNED, now
do you?  ESPECIALLY since by the time you call that result of function
A, there might be other data in the stack which has NOTHING to do
with "local_variable".  (You C programmers out there -- ever been
bitten by this sort of bug when you return a pointer to a variable
in the stack?  Damn hard to track down, isn't it?)

This illustrates that, if pointers to subprograms were added to Ada,
only pointers to "outer" or "first level" subprograms could be valid
subprogram pointers.  The price that Ada would pay if pointers to
nested procedures COULD be used would be a price in safety and program
correctness -- one of Ada's MAJOR design goals was to provide a safe
language.  So, in order for you counter-example to be used as an
argument for adding procedure passing to Ada, one must concede that
Ada would also become less safe.  Oh well...

-- Rich Wagner

andy@Shasta.UUCP (07/22/86)

In article <4700072@ada-uts> richw@ada-uts writes:
[I sketched a program that illustrated how procedure parameters make
 it possible to maintain separate scopes for things that should be
 separate, unlike the enumeration method.  In his reply, he wrote a
 program where one procedure returns a local procedure that will
 modify a local variable of the original procedure if it is ever
 called.  He then concludes with:]

>The question is: What in the WORLD happens when someone tries to
>call the result of function A?  You don't REALLY want to be able to
>modify the stack-frame of a procedure that has ALREADY RETURNED, now
>do you?  ESPECIALLY since by the time you call that result of function
>A, there might be other data in the stack which has NOTHING to do
>with "local_variable".  (You C programmers out there -- ever been
>bitten by this sort of bug when you return a pointer to a variable
>in the stack?  Damn hard to track down, isn't it?)
>
>This illustrates that, if pointers to subprograms were added to Ada,
>only pointers to "outer" or "first level" subprograms could be valid
>subprogram pointers.  The price that Ada would pay if pointers to
>nested procedures COULD be used would be a price in safety and program
>correctness -- one of Ada's MAJOR design goals was to provide a safe
>language.  So, in order for you counter-example to be used as an
>argument for adding procedure passing to Ada, one must concede that
>Ada would also become less safe.  Oh well...
>
>-- Rich Wagner

Wrong conclusion.  If a language allows the program you describe
then any implementation that behaves the way you're worried about
is wrong.  (In C, returning a pointer to a variable on the stack
is an error.  C isn't required to detect this error, but C isn't
an example of a safe language.)

If a language makes it possible to RETURN procedures, then an
implementation must be able to heap-allocate activation records.
(Note the difference between "be able to" and "always".)  Some
Extended Pascal dialects allow procedures as PARAMETERS, but not
values (you can't put them in records or variables); their
implementations can stack-allocate activation records without
running into the problem you describe.  If the language also
requires proper declarations of these parameters, the language
will be type-safe.

If you've written large programs, you know how important it is
to maintain the appropriate scope, as defined by the problem.
That isn't an issue in small programs.  (Ada is supposed to be
good for large programs ....)

BTW - there are languages where a procedure can return multiple times.
Some of them even allow stack-allocation of activation records; you
just have to be careful when you pop.

-andy

decwrl!glacier!shasta!andy
andy@sushi.stanford.edu

ps - I think it is bad style to use scoping that is wider than
necessary.  Procedure as parameters (and as values) are one tool
to help me; you can use all globals if you prefer.