So, I’m working on a Gnoga project, and within the project I want to have a simple sitemap compiled into the server; from this sitemap I want to generate navigation and an XML-sitemap file — constrained as a simple sequence of a Link (Text, Ref) or else a Group (Text, List_of_Link)… but I simply cannot seem to get 'Reduce to work—
A simplified example:
with
Ada.Containers.Indefinite_Vectors,
Ada.text_IO;
procedure Example is
package listing is new Ada.Containers.Indefinite_Vectors(
Index_Type => Positive, Element_Type => String
);
Data : constant listing.vector:=
[ "this", "that", "the_other", "~~~TEST!!" ];
type ACCUM is null record;
procedure R(A : in out Accum; X : String) is
begin
Ada.Text_IO.Put_Line( X );
end;
function R(Left, Right : String) return String is
( Left & Right );
A : Accum;
Final_Text : String renames Data.Last_Element;
begin
Ada.Text_IO.Put_Line( Final_Text );
declare
begin
Ada.text_IO.put_Line( Data'reduce( R, "" ) ); -- Causes constraint-error.
null;
A:= Data'reduce( R, (null record) ); -- type-error.
end;
end Example;
According to the LRM, 4.5.10, I should be able to use either of these:
function Reducer(Accumulator : Accum_Type;
Value : Value_Type) return Accum_Type;
procedure Reducer(Accumulator : in out Accum_Type;
Value : in Value_Type);
So, what am I doing wrong?
Yes, I know it’s odd to try to use a null record, but this should be a quick/easy test the application to the elements.
The Constraint_Error for Data’Reduce(R, “”) is because you are initializing the accumulator to a String of length 0, and then trying to change it to some bigger length, which results in a Constraint_Error. You could use an Unbounded_String instead.
For the “type error” case, perhaps the compiler is getting confused because you have both a reducer function and a reducer procedure with the same name. You might try changing the name of the procedure to Red or some such thing, and see if the compiler gives you a more helpful error message, and then share the message with us.
declare
procedure P( Left : in out Accum;
Right : in String) is
begin
Ada.Text_IO.Put_Line( Right & '.' );
end;
function F( Left : Accum;
Right : String
) return Accum is
begin
return Result : Accum do
Ada.Text_IO.Put_Line( Right & '!' );
end return;
end;
NR : Constant Accum := (null record);
--X : Accum renames Data'reduce( F, NR );
Y : Accum renames Data'reduce( p, NR );
begin
null;
end;
It yields the following error for whichever line is not commented out:
example.adb:41:31: error: expected type "Standard.String"
example.adb:41:31: error: found type "ACCUM" defined at line 13
Data being the same vector of strings as originally posted.
It looks like it’s choking on the container; the following works:
G : Accum:= [ for X of Data => X ]'reduce(F, nr);
Which is what the RM says the prefix is equivalent to:
X’Reduce is a reduction expression that yields a result equivalent to replacing the prefix of the attribute with the value_sequence: [for Item of X => Item]
Not really that weird. These sorts of transformations may seem straightforward to a human, but for the compiler they can involve jumping through several hoops any one of which might trip it up, especially if you have an overarching goal of generating correct yet efficient code.
Sure.
But you can also handle some at the IR-level, collapsing alternates into a single item so that it matches the language-definition’s “behaves/is the same as” because internally it is the same.
A small RM-error, the accumulator-type should be allowed to be limited, and likely unconstrained as well, for the procedure-case.
The following works as expected, allowing all elements of Data to be applied via P to the accumulator:
with
Ada.Containers.Indefinite_Vectors,
Ada.text_IO;
procedure Example is
package listing is new Ada.Containers.Indefinite_Vectors(
Index_Type => Positive, Element_Type => String
);
Data : constant listing.vector:=
[ "this", "that", "the_other", "Done" ];
package Limited_Accumulator is
type ACCUM(<>) is limited private;
function Default return ACCUM;
private
type ACCUM is null record;
function Default return ACCUM is ( ACCUM'(null record) );
end Limited_Accumulator;
use Limited_Accumulator;
begin
Ada.Text_IO.Put_Line( "[[[START]]]" );
declare
procedure P( Left : in out Accum;
Right : in String) is
begin
Ada.Text_IO.Put_Line( Right & '.' );
end;
G : Accum renames Accum'( [ for X of Data => X ]'reduce(p, Default ) );
begin
null;
end;
Ada.Text_IO.Put_Line( "[[[STOP]]]" );
end Example;
… though, technically the RM says that the accumulator-type must be non-limited. (Using GNOGA’s types, having this capability would be very good for allowing 'Reduce on collections to be applied [via transformation] to GUI-elements; e.g. a menu-listing.)
Interesting that a limited Accum type would work with the procedural reducer. I would encourage you to suggest the RM change to allow that, as an ARG GitHub issue (Issues · Ada-Rapporteur-Group/User-Community-Input · GitHub). It looks like GNAT might already allow it …