Why doesn't 'reduce work?

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.

This might help:

ARG member, ARM editor, and Janus/Ada compiler developer Randy Brukardt said on comp.lang.ada that the accumulator type has to be definite.

The accumulator type is definite: type ACCUM is null record;.

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.

It doesn’t work even with either of the following

    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 is possible that GNAT’s implementation is incomplete, and that it doesn’t handle cases where the accumulator and the value type are different.

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]

Weird.

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.

Sorry, I looked at the first use of 'Reduce and saw that it used an indefinite type.

I might suggest that you try declaring your initial value elsewhere

Init : constant Accum := <>;
... 'Reduce (R, Init);

but I suspect that you’ve encountered a compiler error.

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 … :wink: