2

I needed the result of a rather complex calculation to define a private type, something like this:

generic
    top_m : Positive;
package Mystic is
    type T is private;
private
    type Table is array (Positive range <>) of Positive;
    function reallyComplicatedFunction(x : Positive) return Table;

    mytable : constant Table := reallyComplicatedFunction(top_m);
    -- I will need mytable for calculations later

    type T is array (mytable'Range) of Natural;
    -- T is very different from Table, except they share their Range
end Mystic;

I needed the type T to depend on generic parameter top_m in a really complicated way, embodied by reallyComplicatedFunction(.). The function is defined in the body of package Mystic, but it does not use anything else declared by the package.

To my surprise this setup works just fine. Maybe I'm just lucky, because as far as I can tell from decyphering the ARM, this kind of invocation of the function is merely 'legal' but it is not guaranteed not to throw a Program_Error. I interpret this as "it would be too restrictive to forbid this sort of stuff entirely, but the compiler can't be expected to determine its feasibility in all cases either, so we'll just allow you to experiment with it". And then there's the rather significant possibility that I'm completely misreading the reference manual. Anyway, books on Ada give rather stern warnings about this sort of thing, typically around the discussion of pragma Elaborate et al., such that I almost didn't try this solution.

I've also tried to put the function in a private child package of Mystic, but I could not resolve circularities between the child implicitly depending on the parent and the parent specification depending on the child. Anyway, this function is not an extension of Mystic but a necessary code to initialize it.

My question would then be: where is the proper place for such a function?

ETA: at the request of Simon Wright, here's the part of the ARM I struggle with: http://www.ada-auth.org/standards/12rm/html/RM-3-11.html entries 9, 10/1 and 14:

For a construct that attempts to use a body, a check (Elaboration_Check) is performed, as follows:

  • For a call to a (non-protected) subprogram that has an explicit body, a check is made that the body is already elaborated. This check and the evaluations of any actual parameters of the call are done in an arbitrary order.

...

The exception Program_Error is raised if any of these checks fails.

As far as I understand, the construct mytable : constant Table := etc. tries to use the body of reallyComplicatedFunction, so it must check whether it was elaborated or not. I assume - this is a weak point in my reasoning but this is what I understood - that elaboration of the body of reallyComplicatedFunction only occurs during the elaboration of the body of package Mystic, so my function won't be elaborated at the time it is called from the private part of the package specification. Nonetheless, I don't receive the Program_Error as promised when using (an instance of) the package.

ETA2: following a remark from trashgod, I've tried turning package Mystic into a non-generic one; made top_m into a visible constant and removed the genericity part. The compiler now catches the circularity I was worried about from the beginning and the program exits with Program_Error: access before elaboration. It's as if the body of a generic package was elaborated before the first instantiation, or rather before the elaboration of the specification of said package during instantiation. Since I'd expect Ada to cater to this kind of need (of hiding complex calculations needed to instantiate a package in the body of said package), I'd not be surprised if it worked as per the standard, but I don't recall reading anything like this anywhere and would like to know the exact rules. Something very smart is going on because if I make the body of the function dependent on type T, the compiler warns about 'call to Tt may occur before body is seen' at the point of package instantiation (I guess 'Tt' is some internality of type T), and when the program is run, it throws a Program_Error complaining about access before elaboration, pointing to the first place where an object of type T is instantiated by another function I have called by reallyComplicatedFunction for testing purposes.

Kryptozoon
  • 133
  • 6
  • Can you use [package body initialization](http://www.ada-auth.org/standards/12rm/html/RM-7-2.html), suggested [here](http://stackoverflow.com/q/39585364/230513)? – trashgod Apr 12 '17 at 02:00
  • Even though it looks like doing this in body initialization is way too late in the game, I've tried to make this idea work, but I cannot figure out a way to get the array size from the body initialization part - if I make mytable a variable instead of a constant and initialize it in the body initialization part, the compiler still rightly complains that the type of mytable must be constrained. – Kryptozoon Apr 12 '17 at 06:07
  • Could you [edit] your question to include links to the section(s) of the ARM that are worrying you? – Simon Wright Apr 12 '17 at 06:58
  • 1
    I defer to @SimonWright about your update; IIUC, the initialization executes at the time of generic instantiation. – trashgod Apr 12 '17 at 18:38
  • 1
    Thanks for the idea; I went and turned top_m into a public constant and removed genericity, whereby the compiler issued two warnings: 'cannot call reallyComplicatedFunction before body seen' and 'Program_Error will be raised at run time'. Running the program indeed results in 'raised Program_Error: access before elaboration'. It's as if a function in the body of a generic package was elaborated before the elaboration of the specification during instantiation. I don't recall seeing this fact mentioned anywhere. Will update the question with this result. – Kryptozoon Apr 12 '17 at 19:05
  • The `Tt` is worrying. I wonder whether GNAT is relying on ARM 3.11(13)? I’ve raised the question on comp.lang.ada. – Simon Wright Apr 12 '17 at 19:43
  • Would having the actual code help? If yes, how can I get it to you? What I have posted is a bit abbreviated, omits stuff inessential for the question but essential for the test leading to 'Tt' showing up. I have a parameterless function returning a T (not shown in the example), and for test purposes I simply added to reallyComplicatedFunction a variable of type T, initialized it with this function and then later compared the variable to the result of the same function, just to have an use. – Kryptozoon Apr 12 '17 at 20:20
  • 1
    The [thread on comp.lang.ada](https://groups.google.com/forum/#!topic/comp.lang.ada/vzx4-BGaBnY) indicates you’ve probably picked up a compiler bug, and the ABE check should have been there even in the generic case. Worth a bug report to AdaCore, I think; I can do that if you don’t want to? – Simon Wright Apr 13 '17 at 06:44
  • Thanks for following up on this. I think it would be better if you submitted the bug report if you got the spare time; I'm kind of a newbie to all this and don't yet speak ARM-legalese at any acceptable level. – Kryptozoon Apr 13 '17 at 10:02
  • OK. By the way, is there a reason you don’t move all the stuff in the private part of the spec into the body? – Simon Wright Apr 13 '17 at 11:01
  • As in, making T an unconstrained type and declaring/initializing mytable in the body? I wanted T to be a constrained type because it is known that all T's coming from the same instance will have the same range and I felt leaving T unconstrained would send the message that T's of different sizes can potentially show up. It's a case of me trying to give full information to the compiler and the human reader of my code; perhaps it will also pay in performance. – Kryptozoon Apr 13 '17 at 11:36
  • Oops, my mistake. My eye skipped over `type T is private`. – Simon Wright Apr 14 '17 at 08:58
  • Only taken me a fortnight to submit the bug report to AdaCore. – Simon Wright Apr 27 '17 at 16:56
  • Well, if it's a bug, it has been there for ages I'd guess, so two weeks should not matter that much. OTOH I am having doubts now, since while your example in the comp.lang.ada thread - with the uninitialized variable - is totally a bug, mine might not be one after all, as a couple of people kept hinting. Could you share a link to the bug report, can it be viewed publicly? I'd like to update the question with it, in the spirit of having everything in one place. – Kryptozoon Apr 28 '17 at 20:09
  • Sorry, AdaCore don’t make bug reports public - I got a "we will investigate" response. – Simon Wright May 21 '17 at 21:28

2 Answers2

1

Edit, reflecting the comment that explain why reallyComplicatedFunction should not be public.

If I understand correctly, the function does not really not depend on Mystic, but reallyComplicatedFunction should be private. In that case, I'd try putting it elsewhere, and keep the dependency on top_m. I have assumed that Table could also be moved, even though formally it is creating a dependence of reallyComplicatedFunction, Table being in its parameter profile. To resolve, a new private place is created in a hierarchy, a private sibling gets the declarations and will only be used in the private part of original Mystic. Hence, private with in the latter's context clause.

package Top is end;

private generic
    top_m : Positive;
package Top.Outsourced is

    type Table is array (Positive range <>) of Positive;
    function reallyComplicatedFunction(x : Positive) return Table;

end Top.Outsourced;

private with Top.Outsourced;
generic
    Top_M : Positive;
package Top.Mystic is
    type T is private;
private
    package Initializer is new Top.Outsourced (top_m);
    subtype Table is Initializer.Table;

    mytable : constant Table := Initializer.reallyComplicatedFunction (top_m);
    -- I will need mytable for calculations later

    type T is array (mytable'Range) of Natural;
    -- T is very different from Table, except they share their Range
end Top.Mystic;


package body Top.Outsourced is

    function reallyComplicatedFunction(x : Positive) return Table is
        Limit : Positive;
    begin
        Limit := Positive'Min (top_m, 1) + x/2;
        return Result : Table (1 .. Limit);
    end reallyComplicatedFunction;

end Top.Outsourced;
B98
  • 1,229
  • 12
  • 20
  • Yes, in fact this was my original design, but I am going to appeal to gut feel here: reallyComplicatedFunction is not useful for anything else but figuring out mytable and its size, so having it out in the public feels wrong. So you are right that it does not _have_ to be in package Mystic, as in, no dependency demands it, but I wanted to express with its placement the fact that it is a specific implementation detail of that particular package. I'm trying to figure out how to express my intent as directly as possible in Ada. (I'm a mostly C++ guy just re-learning Ada, if that context helps.) – Kryptozoon Apr 12 '17 at 06:47
  • Oh, I just noticed your edit; thanks for the help. I could not make something like this work, because I did not think of having an empty Top with Mystic and Outsourced as siblings but tried in vain to ram the function and the Table type into a private child of Mystic, and of course could not remove the circularity the compiler rightly complained about. I guess this remains the canonical solution unless someone comes up with an explanation as of why genericity enables my code to work and it turns out to be a feature. – Kryptozoon Apr 12 '17 at 19:34
0

Assuming (since you didn't specify it) the array returned from reallyComplicatedFunction has the range 1..top_m, you could define a new subtype and use that as the range for both arrays:

subtype My_Range is Positive range 1..m_top
type Table is array (My_Range) of Positive;

type T is array (My_Range) of Natural;

and move both my_table and reallyComplicatedFunction inside the package body.

egilhh
  • 6,464
  • 1
  • 18
  • 19
  • Unfortunately, the array returned from reallyComplicatedFunction always has a shorter range than 1..m_top and its 'Last depends on m_top in a really inelegant (i.e. no formula) fashion. I could perhaps have shortened the code for the sake of example and have the function only return the upper limit, but the current way of using its result might have been important in figuring out why does it work at all, seeing how the body is not elaborated at the point mytable is initialized. – Kryptozoon Apr 11 '17 at 17:46