Search code examples
initializationadaelaboration

Ada deferred constant finalized using complicated calculation; where to put the code?


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.


Solution

  • 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;