1

I have a private type that internally uses a 2 dimensional array, while doing additional processing whenever the elements of that array are set. My package looks something like this:

with MyElements;
generic
    Width, Height : Positive;
package MyPackage is
    subtype MyXCoordinate is Positive range 1..Width;
    subtype MyYCoordinate is Positive range 1..height;
    type MyCoordinate is
        record
            x : MyXCoordinate;
            y : MyYCoordinate;
        end record;
    type MyType is private;
    function Create return MyType;
    function Get (this  : in MyType;
                  coord : in MyCoordinate) return MyElements.MyElementType;
    procedure Set (this    :    out MyType;
                   coord   : in     MyCoordinate;
                   element : in    MyElements.MyElementType);
private
    type MyTypeData is array (MyXCoordinate,MyYCoordinate)
      of MyElements.MyElementType;
    type MyType is 
        record
            data       : MyTypeData;
            otherstuff : ThatIsActuallyInThePackageButNotRelevant;
        end record;
end MyPackage

However this forces packages that interface with MyPackage to either be generic, or to dynamically instantiate a new MyPackage with the correct parameters. Granted in execution these parameters are available fairly early (they are read from a file which is passed in on command line), but it still seems like I'm missing a language element that would do this for me.

I considered just using unbound Positives for the coordinates, removing the generic and allowing a run-time outofbounds error to be generated if the bounds fail, but I can't help feeling like Ada must have some way to do this cleanly at compile time.

I know I could use SPARK, but its overkill for this project (this project is a game, written in Ada to showcase its qualities) and somewhat defeats its purpose (showcasing Ada's compile-time error checking!).

Is my generic instantiation solution the correct way to handle this, or is there something better?

My attempts so far, that don't work but feel like they might be close-ish:

type MyType is
    record
        Width,Height : Positive;
        subtype XCoordinate is Positive 1..Width;
        subtype YCoordinate is Positive 1..Height;
        data : array (XCoordinate,YCoordinate) of MyElements.MyElementType;
    end record;

And wrapping the generic package with a 'generator' package that takes width/height as parameters to functions and procedures that then call-through to the package. (lots of code, doesn't work, wont waste space here with it).

Jacob Sparre Andersen
  • 6,733
  • 17
  • 22
LambdaBeta
  • 1,479
  • 1
  • 13
  • 25
  • 1
    `Width` and `Height` seem more like [*discriminants*](http://www.ada-auth.org/standards/12rm/html/RM-3-7.html) than generic parameters; see [*objects with variable-sized array attribute*](http://stackoverflow.com/q/29155034/230513). – trashgod Sep 30 '16 at 16:21
  • 1
    Yes to discriminants - or even unconstrained arrays, constrained when you know (read) the size. If anything should be generic here, seems to me it should be MyElementType, allowing you to instantiate MyPackage for any element type. But your use case is not clear to me, so I may be misunderstanding. –  Sep 30 '16 at 16:26
  • That looks about right, so I guess my type would be: `private type MyType (Width,Height) : Positive is record data : array (1..Width,1..Height) of MyElements.MyElementType; end record;` and would be used like: `function Create(Width, Height : Positive) return MyType is retval : MyType(Width=>Width,Height=>Height); begin return retval; end Create;`? – LambdaBeta Sep 30 '16 at 16:29
  • Yep, discriminants are what I want, thanks. Just one quick question: Is there a way to find what Width/Height were used to create the object, or do I have to create a function that checks the size of the internal array? – LambdaBeta Sep 30 '16 at 16:42
  • 1
    You can use the array [attributes](http://www.ada-auth.org/standards/12rm/html/RM-K-2.html) on the constrained array. Please edit your question with your revised approach. You can [answer your own question](http://meta.stackoverflow.com/q/17463/163188). – trashgod Sep 30 '16 at 18:43
  • Yes, discriminants seem to *mostly* solve my problem. They still aren't as nice in terms of restricting type because I can't create an integer type that is restricted to a particular `MyType.Size`. In particular, I still end up with `type MyCoordinate is record x,y : Positive` instead of something equivalent to `type MyCoordinate is record x : Positive range 1..MyType.Width; y : Positive range 1..MyType.Height`. I don't want to answer my own question until I'm a bit more convinced that there's no better solution than generics or discriminants. – LambdaBeta Oct 01 '16 at 04:02
  • [`Mine_Detector`](http://pragmada.x10hosting.com/mindet.html) is an example that uses integer subtypes and array attributes in the context of a constrained array. – trashgod Oct 01 '16 at 08:09
  • I looked at that, and it declares the sizes at compile-time. Now that I think about it, its impossible to compile-time check the range of an array that will be allocated at run-time. The only thing that might work would be if its possible to have types refer to other parameters, i.e: `procedure myProcedure(this : in MyType; index : in array (this.startindex..this.endindex) of Something)` but afaik that is not legal? – LambdaBeta Oct 01 '16 at 17:47
  • @LambdaBeta: Yes; the bounds cannot be declared until they are know; that, in turn, depends on your use case; when your bounds are known, you specify them in the declaration of your array using any of the approaches discussed: constrained array, discriminated array, or generic instantiation—possibly in a [nested scope](http://stackoverflow.com/q/39175678/230513). – trashgod Oct 01 '16 at 23:34

1 Answers1

3

It looks like you need to read up on discriminants, unconstrained arrays and indefinite types.

Here's a generic package, which solves what appears to be your actual problem:

generic
   type Element_Type is private;
package Dynamic_Matrix is
   type Instance (Width, Height : Positive) is tagged private;

   procedure Set (Item  : in out Instance;
                  X, Y  : in     Positive;
                  Value : in     Element_Type)
     with Pre => X <= Item.Width and Y <= Item.Height;

   function Element (Item : in     Instance;
                     X, Y : in     Positive) return Element_Type
     with Pre => X <= Item.Width and Y <= Item.Height;

private
   type Element_Matrix is array (Integer range <>,
                                 Integer range <>) of Element_Type;

   type Instance (Width, Height : Positive) is tagged
      record
         Elements : Element_Matrix (1 .. Width, 1 .. Height);
      end record;
end Dynamic_Matrix;

Here's an example using the package to load a matrix of Booleans into a variable:

with Ada.Text_IO;

with Dynamic_Matrix;

procedure Dynamic_Matrix_Demo is
   package Positive_IO is new Ada.Text_IO.Integer_IO     (Positive);
   package Boolean_IO  is new Ada.Text_IO.Enumeration_IO (Boolean);

   package Boolean_Matrix is new Dynamic_Matrix (Element_Type => Boolean);

   Width, Height : Positive;

   use Ada.Text_IO, Positive_IO, Boolean_IO;
begin
   Get (Width);
   Get (Height);
   Skip_Line;

   declare
      Matrix : Boolean_Matrix.Instance (Width  => Width,
                                        Height => Height);
      Value  : Boolean;
   begin
      for Y in 1 .. Height loop
         for X in 1 .. Width loop
            Get (Value);
            Matrix.Set (X, Y, Value);
         end loop;
         Skip_Line;
      end loop;
   end;
end Dynamic_Matrix_Demo;
Jacob Sparre Andersen
  • 6,733
  • 17
  • 22
  • Nice example, but an illustration of its instantiation and use would be helpful. –  Oct 03 '16 at 11:12
  • Wow... this perfectly answers my question. So instead of actually creating a new type for the range of my indices, I should be just using `Positive` and allowing static analysis to check its bounds instead of static type checking. -- Now as I implement this I notice a circular dependency... gotta pull out my limited withs :) – LambdaBeta Oct 03 '16 at 15:40
  • Quick followup: How do I add a pre/post condition on a private object's data. EG: the object called 'this' has an element which is an array that I want each member to be null, `procedure Reset(this:in out MyThing)with Post => this.arraything = (others => null)` doesn't work. – LambdaBeta Oct 03 '16 at 15:55
  • Pre- and postconditions on public subprograms can only reference the public view of the type, so you have to make a public subprogram which checks the condition, and use that in the pre- or postcondition. – Jacob Sparre Andersen Oct 03 '16 at 17:18