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 => <>);
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.
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;
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.