Aggregate to fill part of an array but leaving a range to the default value

I want a constructor to return a Queue with the array parameter initializing the first components (let us ignore the case the array length might exceed Capacity), and leave the rest of the elements to their default value, or any value for the matter.

pragma Ada_2022;
procedure Main is
   type Table is array (Positive range <>) of Integer;
   type Queue (Capacity: Positive) is record
      T: Table (1..Capacity):= [others => 5];
   end record;
   function MakeQueue (C: Positive; L: Table) return Queue is
     (Capacity => C,
      T => (for I in 1..L'Length => L(I),
                L'Length+1..C => <>), others => <>);
begin
   null;
end Main;

I wish we could write something like:

T => [1…C => 0] with delta T(C+1…T’Last) => (A of L => A)

But we’ll probably wait for years before it becomes a thing. I’m often frustrated by what should be expressible, but isn’t, as if we’ve only been dipping our toe in the functional world…
Anyway, how would you solve the issue above ?

I positively abhor this message now:

error: dynamic or empty choice in aggregate must be the only choice

This came up in another thread, dealing with strings, from someone coming from Turbo Pascal; so I’d typed up a bit of an example on how to implement TP-style strings., note the first "+"-function:

It’s not perfect, but you can also use extended return to do essentially the same thing.

       function MakeQueue (C: Positive; L: Table) return Queue is
       begin
          return Result : Queue(C) do
             for Index in  1 .. L'Length loop
                Result.T(Index) := L(Index);  -- need to rethink indexing L like this
             end loop;
          end return;
       end MakeQueue;

You also have to consider the case when the passed in table’s indexes are not in 1 … L’Length. I didn’t correct that part so that you could compare them better. You can also use a simple variable instead of extended return, but I kinda like the form.

If you are willing to adjust your table type definition a bit to:

type Table is array (Positive range <>) of Integer
   with Default_Component_Value => 5;

You can also try this:

       function MakeQueue (C: Positive; L: Table) return Queue is
          (Capacity => C,
           T => L & Table'[1 .. (C-L'Length) => <>],
           others => <>);

Full example in jdoodle: Online Compiler and Editor/IDE for Java, C, C++, PHP, Python, Ruby, Perl - Code and Run Online

1 Like

I ended up with just that, intuitively.
But now I stumbled onto to a much more ominous hurdle.
The fact that discriminant must appear alone in a type declaration. Seriously, why ? The expression already isn’t static, what difference would that make to add a static number to it ?

type Queue (Capacity : Positive) is limited record
    CurrentSize : Natural  := 0;
    Tails       : TailList := [others => 0];
    Head        : Natural  := 1;
    Store       : List (0 .. Capacity-1); --- THIS
end record;

I must absolutely have an array indexed from 0. I implemented a ring buffer, but to be a ring, it must loop back. I can only see changing “Capacity” to “Capacity_Plus_One”, but how uglier can It get, beside forcing me to rewrite the algorithm, which is delicate.

I think you can have the declaration use 1 … Capacity but then use an overlay that is indexed from 0.

Play with:

procedure Test(Thing : in out Queue) is
   subtype Table_0 is Table(0..Thing.Store'Length-1);
   Overlay : Table_0
      with Import, 
           Address => Thing.Store'Address;
begin
   -- Do stuff with overlay using the algorithm you want
end Test;

Table is from Thing, which is limited, so you know the Address is from the passed in object. And the original Thing.Store is constrained, just at a different index. So I think it should be ok, but haven’t tested thoroughly or parsed the RM for it.

It’s a bit of scaffolding, but it might allow you to keep your algorithms. And you might be able to localize this to a couple of procedures/functions and just call those from your other operations.

1 Like

Importing… from your own program ?
I didn’t even imagine the possibility… Absolutely a scaffolding, but actually elegant ! Thanks
If we can do that, it makes no sense that we can’t use Capacity-1 in the original declaration… the compiler could make that same arrangement you do but under the hood, if needed.

Yeah, you can import from anywhere that your program can see including it’s own objects. The name might confuse because it implies a specific type of thing but in reality using the Import aspect/pragma really just tells the compiler that the object/subprogram comes from somewhere other than this particular declaration. In the case of this example, the source comes from the parameter.

It’s up to you to ensure the overlay is safe though. This is sneaking into unchecked programming. You need to ensure the size/alignments match (They should since this is a subtype constraint of overlayed on the ancestor type and the size of the index range is the same). Definitely thoroughly unit test.

No, In Ada you could have an index of (-7…-3)… though, you may be thinking that “must” in the sense of making the math work more nicely. — In this case, I would suggest one of two methods: (1) looking at how you are specifying the problem, perhaps using modular types and generic in order to reify the actual/final type, and (2) consider a “by-parts” approach, wherein you decompose your public-type into several internal/private types and e.g. use Unchecked_Conversion to switch between views.

The second option, while not exactly recommended, is useful for e.g. implementing a Turbo Pascal style String:

Package TP_String_Example is
   Subtype Byte is Interfaces.Unsigned_8;
   -- Public view
   Type String( Length : Byte ) is private
     with Size => 256 * Byte'Size;
   --Operrations
Private
  Subtype Positive_Byte is Byte Range Byte'Succ(Byte'First)..Byte'Last;
  Type TP_Fixed is Array(Positive_Byte range <>) of Character;

   Type String (Length : Byte) is record
      Text : TP_Fixed(1..Length):= (others => ASCII.NUL);
   end type;
   For String use record
      Length at 0 range 0..7;
   end type;

   Type Internal_String is record
      Length : Byte;
      Text   : TP_Fixed(Positive_Byte);
   end record;
   For Internal_String use record
      Length at 0 range 0..7;
      Text at 0 range 8..255*8-1;
   end type;

End TP_String_Example;

I don’t want to think of the maintenance hassle. I use Import and it works fine now, though to be honest, it’s beyond my ability to visualize, and I certainly can’t prove it mathematically/SPARKly. but it behaves now and at least the dirty bits are all in one place !

I tried modular types first… But they’re not actually very useful, because they do NOT behave like a mathematical ring. If a variable is 3 and the modulo is 5, they will raise Constraint_Error instead of going round as it does with “rem 5”. It would get so ugly.
Also, I can’t define a modular type with generic object, because it’s always dynamic. That pissed me off to no end. Were the smart sharing of generic code object, that have been developed with success part of the standard, generics could behave much more than a macro system, with the benefit of strong typing/semantics.

It’s not quite as much of a maintenance hassle as it seems, so long as your type is solidified: you use it just like any other private-type, using it as a client, just program to the visible interface.

This is, perhaps, one thing that a lot of programmers coming from C or PHP or whatever might miss: using the public view of the type means you don’t have to be concerned with the implementation. (Barring working on that implementation, or the [generally rare] performance issues.)

?
I was sure that they did wrap-around properly.
Did you use Type Whatever is mod X;? Or Type Whatever is range 0..X;?

Is it?
Why can’t you pass that as the parameter?

Generic
   Type Thing is private;
   Type T is mod <>;
Package Whatever is
   Type Ring is Array(T) of Thing;
   -- …
End Whatever;

Perhaps read this paper, and think about the formal parameters a bit.

Can you expound some, I just tried it and it works as expected:

with Ada.Text_IO; use Ada.Text_IO;

procedure jdoodle is
    type Test is mod 5;
    v1 : Test := 0;
begin
    for Count in 1 .. 20 loop
        Put_Line(v1'Image);
        v1 := v1 + 1;
    end loop;
end jdoodle;

Yields the output:

 0
 1
 2
 3
 4
 0
 1
 2
 3
 4
 0
 1
 2
 3
 4
 0
 1
 2
 3
 4

This works, but seems more difficult to understand than

L & (L'Length + 1 .. C => <>)

ok, I withdraw what I say. Probably overworked, and overextended myself, I’ll rewrite it slowly.

1 Like

I wouldn’t worry too much. It happens to everyone!

What’s wrong with the syntax in this one:

function MakeQueue (L: List)
       return Queue is (CurrentSize => L'Length,
        Tails => (others => L'Length+1),
        Store => [for I in L'Range => L(I), others => <>),  others => <>);

You are missing a closing bracket for the Store parameter. I don’t know what List type looks like so not sure where that goes specifically, but that’s where the error is at least.

Found the solution:

List(ListParameter’(L & [L’Length+1…20 => <>]))

with

type Indextype is mod 20;
type List is array (Indextype) of Integer;
type ListParameter is array (Positive range <>) of Integer;

Feels like receiving god’s grace now…