comp.lang.ada
 help / color / mirror / Atom feed
* Generic instantiation before actual subprogram body
@ 2014-12-04  8:48 Natasha Kerensikova
  2014-12-04  9:31 ` J-P. Rosen
  2014-12-04 15:49 ` Robert A Duff
  0 siblings, 2 replies; 8+ messages in thread
From: Natasha Kerensikova @ 2014-12-04  8:48 UTC (permalink / raw)


Hello,

I have fallen into the pattern of refactoring common using generics
instead of cut-and-paste. One of my earliest example of this is
https://github.com/faelys/natools/blob/trunk/src/natools-s_expressions-interpreter_loop.adb

I think it's a good thing, even for only a few tens of lines of code,
mostly for readers who discover the code: explicit common patterns reduce
(I think) the cognitive overhead.
Please enlighten me if I'm wrong on this.

At some early point when I used the pattern, I encountered a GNAT
warning telling that a particular generic instantiation would raise
Program_Error at run-time because the body of actual subprograms in the
instantiation were found later in the source text.

Since then, I always put instantiations after the bodies of the actual
subprograms, even though it makes it a bit awkward.

A lot of time passed, and I'm now feeling the need to refactor a
cut-and-paste-able code structure involved in a recursive cycle.
Recursivity means at some point something has to be called before its
body. This led me to question that instantiation-before-body warning,
so I started experimented, but I couldn't reproduce the warning.

My question is now, in which set of circumstances is it allowed (or
forbidden) to instantiate a generic with a subprogram whose body has not
yet been seen by the compiler?

I'm pretty sure I was using an older GNAT in Ada 2005 mode back then,
could it be something that has changed in Ada 2012?

I tried to read the ARM and then the AARM, on chapters related to
generic instantiation and/or subprogram elaboration (I can't think of
any not-elaboration-related reason to raise Program_Error because of
subprogram body position in the source). I also tried reading GCC
sources, looking for "Program_Error [" and "body seen". However all
these resources are way out of my depth, so I'm asking here.


On a related but almost out-of-topic point, I have always instantiated
generic on a nesting level as shallow as possible. This is due to some
superstitious fear about trampolines and accessibility levels and a few
other nesting-related concept I don't understand.

In the particular case of my alternative-to-cut-and-paste generics, it's
often a generic procedure, whose instances are each called only once, in
a public subprogram whose body consists of almost only the generic
instance call, sometimes with a little bit of argument shuffling or type
adjustments to fit the generic model when the API has a slightly
different logic.

For example I have somewhere code similar to:

package body Whatever is

   procedure Append (...);
   procedure Render_Page_Element (...);

   procedure Render_Page is new Natools.S_Expressions.Interpreter_Loop
     (Output_Accumulator, Page_Type, Render_Page_Element, Append);

   procedure Public_Render
     (Page : in Page_Type;
      Output : in out Output_Accumulator;
      Template : in out Natools.S_Expressions.Lockable.Descriptor'Class) is
   begin
      Render_Page (Template, Output, Page);
   end Public_Render;

end Whatever

Wouldn't it be better in such a case to instantiate the generic inside
Public_Render? That would solve any elaboration issue, and that would
limit the scope of the generic instance to where it is actually use,
which I find is generally a good guideline to follow.
I guess the compiler can easily tell there is no special nesting
mechanism needed, since all actual parameters have a large scope.

Is there any particular rational reason to prefer top-level
instantiation to nested instantiation, or vice versa?


Thanks in advance for your insights,
Natasha

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

* Re: Generic instantiation before actual subprogram body
  2014-12-04  8:48 Generic instantiation before actual subprogram body Natasha Kerensikova
@ 2014-12-04  9:31 ` J-P. Rosen
  2014-12-04 10:03   ` Natasha Kerensikova
  2014-12-04 15:49 ` Robert A Duff
  1 sibling, 1 reply; 8+ messages in thread
From: J-P. Rosen @ 2014-12-04  9:31 UTC (permalink / raw)


Le 04/12/2014 09:48, Natasha Kerensikova a écrit :
> My question is now, in which set of circumstances is it allowed (or
> forbidden) to instantiate a generic with a subprogram whose body has not
> yet been seen by the compiler?
RM2012(11.5(20)):
"When [...] a generic instantiation is elaborated, check that the body
of the corresponding unit has already been elaborated."

FYI: to find it, go to the index, and follow the references for
"Program_Error".
-- 
J-P. Rosen
Adalog
2 rue du Docteur Lombard, 92441 Issy-les-Moulineaux CEDEX
Tel: +33 1 45 29 21 52, Fax: +33 1 45 29 25 00
http://www.adalog.fr

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

* Re: Generic instantiation before actual subprogram body
  2014-12-04  9:31 ` J-P. Rosen
@ 2014-12-04 10:03   ` Natasha Kerensikova
  2014-12-04 10:40     ` Georg Bauhaus
                       ` (2 more replies)
  0 siblings, 3 replies; 8+ messages in thread
From: Natasha Kerensikova @ 2014-12-04 10:03 UTC (permalink / raw)


On 2014-12-04, J-P. Rosen <rosen@adalog.fr> wrote:
> Le 04/12/2014 09:48, Natasha Kerensikova a écrit :
>> My question is now, in which set of circumstances is it allowed (or
>> forbidden) to instantiate a generic with a subprogram whose body has not
>> yet been seen by the compiler?
> RM2012(11.5(20)):
> "When [...] a generic instantiation is elaborated, check that the body
> of the corresponding unit has already been elaborated."

This is the body of the generic unit, while my question was about the
body of the actual subprograms use in the generic instantiation to match
for the formal subprogram parameters in the generic.

Since the generic unit is another unit, I don't expect moving the
instantiation in the source text to have any effect. While the
Program_Error I mentioned was about the position of the instantiation
with regard to actual subprogram bodies.

It really looked like that:

with Generic_Procedure;

package body Which_Raises_Program_Error is

   procedure Local_Procedure;

   procedure Instantiated is new Generic_Procedure (Local_Procedure);

   procedure Local_Procedure is
   begin
      <some code here>
   end Local_Procedure;

   procedure Public_Procedure is
   begin
      Instantiated;
   end Public_Procedure;

end Which_Raises_Program_Error;


package body Which_Does_Not_Raise_Program_Error is

   procedure Local_Procedure;

   procedure Local_Procedure is
   begin
      <some code here>
   end Local_Procedure;

   procedure Instantiated is new Generic_Procedure (Local_Procedure);

   procedure Public_Procedure is
   begin
      Instantiated;
   end Public_Procedure;

end Which_Does_Not_Raise_Program_Error;


It was really a problem related to the position of the instantiation
(Instantiated in the example) relative to the body of an actual
subprogram (here Local_Procedure).

If I understand correctly, one obvious way to reach this effect is when
a generic package uses a formal function to initialize a global
variable. The function call would be elaborated when the generic package
is instantiated, so that would be a function call whose body has not yet
been seen, so Program_Error.

I'm quite sure this wasn't the case I encountered, so I'm looking for
other ways an actual body can influence a generic instance.
Are there any?


Or maybe my memory is playing tricks on me, confusing actual subprogram
body and generic unit body? Though I don't remember having ever tried to
instantiate a generic in the same compilation unit where I define it.

Would that mean that a subprogram whose body has not yet been seen can
always be used as an actual for a generic instantiation, except when
used immediately like in the package example above?

> FYI: to find it, go to the index, and follow the references for
> "Program_Error".

I tried that, though it was after I lost hope of understanding the
influences between elaboration and instantiation using only the ARM.
I'll try again more thoroughly when I have some free time.


Thanks for your insights,
Natasha

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

* Re: Generic instantiation before actual subprogram body
  2014-12-04 10:03   ` Natasha Kerensikova
@ 2014-12-04 10:40     ` Georg Bauhaus
  2014-12-04 14:12     ` J-P. Rosen
  2014-12-04 15:34     ` Robert A Duff
  2 siblings, 0 replies; 8+ messages in thread
From: Georg Bauhaus @ 2014-12-04 10:40 UTC (permalink / raw)


On 04.12.14 11:03, Natasha Kerensikova wrote:
> package body Which_Does_Not_Raise_Program_Error is
>
>     procedure Local_Procedure;
>
>     procedure Local_Procedure is
>     begin
>        <some code here>
>     end Local_Procedure;
>
>     procedure Instantiated is new Generic_Procedure (Local_Procedure);
>
>     procedure Public_Procedure is
>     begin
>        Instantiated;
>     end Public_Procedure;
>
> end Which_Does_Not_Raise_Program_Error;


While the following does not answer the question, Ada's "separate" facility
can provide for some source text reordering. It can keeping bodies out
of sight when they are not needed to be seen by the reader. Maybe this helps:

package body Which_Does_Not_Raise_Program_Error is

    procedure Local_Procedure;  -- or drop this

    procedure Local_Procedure is separate;

    procedure Instantiated is new Generic_Procedure (Local_Procedure);

    procedure Public_Procedure is
    begin
       Instantiated;
    end Public_Procedure;

end Which_Does_Not_Raise_Program_Error;

separate (Which_Does_Not_Raise_Program_Error)
procedure Local_Procedure is
begin
    null;
end Local_Procedure;

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

* Re: Generic instantiation before actual subprogram body
  2014-12-04 10:03   ` Natasha Kerensikova
  2014-12-04 10:40     ` Georg Bauhaus
@ 2014-12-04 14:12     ` J-P. Rosen
  2014-12-04 15:34     ` Robert A Duff
  2 siblings, 0 replies; 8+ messages in thread
From: J-P. Rosen @ 2014-12-04 14:12 UTC (permalink / raw)


Le 04/12/2014 11:03, Natasha Kerensikova a écrit :
> This is the body of the generic unit, while my question was about the
> body of the actual subprograms use in the generic instantiation to match
> for the formal subprogram parameters in the generic.

OK, my mistake. I don't see any reason while the actual should be
elaborated when you instantiate the generic. Of course, it has to be
elaborated when you call it, and such a call can happen as part of the
elaboration of the body of the generic, but you noticed that already.

-- 
J-P. Rosen
Adalog
2 rue du Docteur Lombard, 92441 Issy-les-Moulineaux CEDEX
Tel: +33 1 45 29 21 52, Fax: +33 1 45 29 25 00
http://www.adalog.fr


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

* Re: Generic instantiation before actual subprogram body
  2014-12-04 10:03   ` Natasha Kerensikova
  2014-12-04 10:40     ` Georg Bauhaus
  2014-12-04 14:12     ` J-P. Rosen
@ 2014-12-04 15:34     ` Robert A Duff
  2 siblings, 0 replies; 8+ messages in thread
From: Robert A Duff @ 2014-12-04 15:34 UTC (permalink / raw)


Natasha Kerensikova <lithiumcat@instinctive.eu> writes:

> This is the body of the generic unit, while my question was about the
> body of the actual subprograms use in the generic instantiation to match
> for the formal subprogram parameters in the generic.
>
> Since the generic unit is another unit, I don't expect moving the
> instantiation in the source text to have any effect. While the
> Program_Error I mentioned was about the position of the instantiation
> with regard to actual subprogram bodies.
>
> It really looked like that:
>
> with Generic_Procedure;
>
> package body Which_Raises_Program_Error is
>
>    procedure Local_Procedure;
>
>    procedure Instantiated is new Generic_Procedure (Local_Procedure);

Procedures don't do anything during elaboration, other than to take note
of the fact that they've been elaborated (i.e. set a flag).  So it
can't call Local_Procedure during elaboration, so there's no problem
here.

If Generic_Procedure were a generic package, then it might call
Local_Procedure during elaboration, in which case you'd get P_E.  That's
unusual -- in my experience, generic packages don't contain a lot of
elaboration code.

>    procedure Local_Procedure is
>    begin
>       <some code here>
>    end Local_Procedure;
>
>    procedure Public_Procedure is
>    begin
>       Instantiated;
>    end Public_Procedure;
>
> end Which_Raises_Program_Error;

> It was really a problem related to the position of the instantiation
> (Instantiated in the example) relative to the body of an actual
> subprogram (here Local_Procedure).
>
> If I understand correctly, one obvious way to reach this effect is when
> a generic package uses a formal function to initialize a global
> variable. The function call would be elaborated when the generic package
> is instantiated, so that would be a function call whose body has not yet
> been seen, so Program_Error.

Yes, that's one example, but it can happen only for generic packages,
not generic procedures.  Another would be if the generic package body
ends with:

    begin
        Formal_Procedure(...);
    end My_Generic;

> I'm quite sure this wasn't the case I encountered, so I'm looking for
> other ways an actual body can influence a generic instance.
> Are there any?

Any call during elaboration of a generic package body (well, I mean
elaboration of the instance).

> Or maybe my memory is playing tricks on me, confusing actual subprogram
> body and generic unit body? Though I don't remember having ever tried to
> instantiate a generic in the same compilation unit where I define it.

If you use static elaboration (which is the default; the -gnatE switch
uses the inferior standard Ada way), then you are correct that
inter-unit instantiations can never fail due to unelaborated generic
body (but you might get a link-time complaint about elaboration cycles).
Within the same compilation unit, it can fail, and I think GNAT will
always warn about it.

> Would that mean that a subprogram whose body has not yet been seen can
> always be used as an actual for a generic instantiation, except when
> used immediately like in the package example above?

Yes.

There is a long section in the GNAT docs explaining elaboration in great
detail.  It's worth reading, and is a lot easier to understand than the
RM.

- Bob


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

* Re: Generic instantiation before actual subprogram body
  2014-12-04  8:48 Generic instantiation before actual subprogram body Natasha Kerensikova
  2014-12-04  9:31 ` J-P. Rosen
@ 2014-12-04 15:49 ` Robert A Duff
  2014-12-04 22:49   ` Randy Brukardt
  1 sibling, 1 reply; 8+ messages in thread
From: Robert A Duff @ 2014-12-04 15:49 UTC (permalink / raw)


Natasha Kerensikova <lithiumcat@instinctive.eu> writes:

> Hello,
>
> I have fallen into the pattern of refactoring common using generics
> instead of cut-and-paste. One of my earliest example of this is
> https://github.com/faelys/natools/blob/trunk/src/natools-s_expressions-interpreter_loop.adb
>
> I think it's a good thing, even for only a few tens of lines of code,
> mostly for readers who discover the code: explicit common patterns reduce
> (I think) the cognitive overhead.
> Please enlighten me if I'm wrong on this.

I didn't look at your code, but you seem to be advocating the DRY
principle, which is usually a good idea.

> On a related but almost out-of-topic point, I have always instantiated
> generic on a nesting level as shallow as possible. This is due to some
> superstitious fear about trampolines and accessibility levels and a few
> other nesting-related concept I don't understand.

In recent versions of GNAT, trampolines are very rare.  Search the GNAT
docs for "trampoline" to see when they're used.  And you can use
the No_Implicit_Dynamic_Code restriction to make sure you don't use
them.

I don't see anything in your code that would involve trampolines --
nested instantiations don't involve trampolines.

If you avoid anonymous access types, you won't get dynamic accessibility
checks.  You might get compile-time errors when doing 'Access, but
that's just an annoyance -- it won't cause bugs.  So yes, I think
you're being "superstitious".  ;-)

> In the particular case of my alternative-to-cut-and-paste generics, it's
> often a generic procedure, whose instances are each called only once, in
> a public subprogram whose body consists of almost only the generic
> instance call, sometimes with a little bit of argument shuffling or type
> adjustments to fit the generic model when the API has a slightly
> different logic.
>
> For example I have somewhere code similar to:
>
> package body Whatever is
>
>    procedure Append (...);
>    procedure Render_Page_Element (...);
>
>    procedure Render_Page is new Natools.S_Expressions.Interpreter_Loop
>      (Output_Accumulator, Page_Type, Render_Page_Element, Append);
>
>    procedure Public_Render
>      (Page : in Page_Type;
>       Output : in out Output_Accumulator;
>       Template : in out Natools.S_Expressions.Lockable.Descriptor'Class) is
>    begin
>       Render_Page (Template, Output, Page);
>    end Public_Render;
>
> end Whatever
>
> Wouldn't it be better in such a case to instantiate the generic inside
> Public_Render? That would solve any elaboration issue, and that would
> limit the scope of the generic instance to where it is actually use,
> which I find is generally a good guideline to follow.
> I guess the compiler can easily tell there is no special nesting
> mechanism needed, since all actual parameters have a large scope.

I'm not sure what special mechanisms you're worried about, but yes, you
can move Render_Page inside Public_Render, and yes that has the
advantage of limiting its scope.  I don't see any trampoline or
accessibility issues in the above code.

> Is there any particular rational reason to prefer top-level
> instantiation to nested instantiation, or vice versa?

If you're using the static elab model, and you only have one call to the
instance, then you can (should?) nest the instance.  No big deal either
way.

- Bob

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

* Re: Generic instantiation before actual subprogram body
  2014-12-04 15:49 ` Robert A Duff
@ 2014-12-04 22:49   ` Randy Brukardt
  0 siblings, 0 replies; 8+ messages in thread
From: Randy Brukardt @ 2014-12-04 22:49 UTC (permalink / raw)


"Robert A Duff" <bobduff@shell01.TheWorld.com> wrote in message 
news:wccegsfwh2j.fsf@shell01.TheWorld.com...
> Natasha Kerensikova <lithiumcat@instinctive.eu> writes:
...
> If you avoid anonymous access types, you won't get dynamic accessibility
> checks.  You might get compile-time errors when doing 'Access, but
> that's just an annoyance -- it won't cause bugs.  So yes, I think
> you're being "superstitious".  ;-)

That's not quite true, in that (some?) accessibility checks in generic 
bodies are (formally) dynamic checks. GNAT can (and most likely does, I 
didn't try it) warn on such errors (since it expands bodies), but they're 
still runtime errors (the program can be executed and it would raise 
Program_Error). You don't need any anonymous access types to trigger those 
checks.

The check in question is 3.10.2(29), and the AARM note 3.10.2(29.a/2) 
mentions instance bodies specifically.

Since the OP's question is related to generics, this is potentially 
significant (although I think in practice it won't matter).

                                                  Randy.









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

end of thread, other threads:[~2014-12-04 22:49 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2014-12-04  8:48 Generic instantiation before actual subprogram body Natasha Kerensikova
2014-12-04  9:31 ` J-P. Rosen
2014-12-04 10:03   ` Natasha Kerensikova
2014-12-04 10:40     ` Georg Bauhaus
2014-12-04 14:12     ` J-P. Rosen
2014-12-04 15:34     ` Robert A Duff
2014-12-04 15:49 ` Robert A Duff
2014-12-04 22:49   ` Randy Brukardt

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