Ada Conformity Assessment Authority      Home Conformity Assessment   Test Suite ARGAda Standard
Annotated Ada Reference Manual (Ada 202x Draft 28)Legal Information
Contents   Index   References   Search   Previous   Next 

4.5.10 Reduction Expressions

{AI12-0242-1} {AI12-0262-1} Reduction expressions provide a way to map or transform a collection of values into a new set of values, and then summarize the values produced by applying an operation to reduce the set to a single value result. A reduction expression is represented as an attribute_reference of the reduction attributes Reduce or Parallel_Reduce.
Glossary entry: A reduction expression is an expression that defines how to map or transform a collection of values into a new set of values, and then summarize the values by applying an operation to reduce the set to a single value.


{AI12-0242-1} {AI12-0262-1} reduction_attribute_reference ::= 
  | prefix'reduction_attribute_designator
{AI12-0262-1} {AI12-0355-2} value_sequence ::= 
    '[' [parallel[(chunk_specification)] [aspect_specification]]
        iterated_element_association ']'
{AI12-0262-1} reduction_attribute_designator ::= reduction_identifier(reduction_specification)
{AI12-0262-1} {AI12-0348-1} reduction_specification ::= reducer_nameinitial_value_expression
{AI12-0250-1} {AI12-0262-1} The iterated_element_association of a value_sequence shall not have a key_expression, nor shall it have a loop_parameter_specification that has the reserved word reverse.
Reason: The intent is that the syntax matches as closely as possible array or container aggregate notation. Syntax that matches a loop_parameter_specification with the reverse reserved word would not be permitted in an array aggregate, so we disallow that here. 
{AI12-0262-1} The chunk_specification, if any, of a value_sequence shall be an integer_simple_expression.

Name Resolution Rules

{AI12-0262-1} The expected type for a reduction_attribute_reference shall be a single nonlimited type.
{AI12-0262-1} In the remainder of this subclause, we will refer to nonlimited subtypes Value_Type and Accum_Type of a reduction_attribute_reference. These subtypes and interpretations of the names and expressions of a reduction_attribute_reference are determined by the following rules:
Discussion: Accum_Type represents the result of the reduction (the accumulator type), and Value_Type represents the type of the input values to the reduction. 
{AI12-0262-1} Accum_Type is a subtype of the expected type of the reduction_attribute_reference.
{AI12-0262-1} A reducer subprogram is subtype conformant with one of the following specifications:
   function Reducer(Accumulator : Accum_Type;
                    Value : Value_Typereturn Accum_Type;
   procedure Reducer(Accumulator : in out Accum_Type;
                     Value : in Value_Type);
{AI12-0262-1} The reducer_name of a reduction_specification denotes a reducer subprogram.
{AI12-0262-1} The expected type of an initial_value_expression of a reduction_specification is that of subtype Accum_Type.
{AI12-0250-1} {AI12-0262-1} The expected type of the expression of the iterated_element_association of a value_sequence is that of subtype Value_Type. 

Legality Rules

{AI12-0262-1} {AI12-0348-1} If the reduction_attribute_reference has a value_sequence with the reserved word parallel, the subtypes Accum_Type and Value_Type shall statically match.
{AI12-0262-1} {AI12-0348-1} If the identifier of a reduction_attribute_designator is Parallel_Reduce, the subtypes Accum_Type and Value_Type shall statically match.
Reason: For a reduction_attribute_reference with a value_sequence that does not have the reserved word parallel or has a prefix and the identifier of the reduction_attribute_designator is Reduce, the subtypes Accum_Type and Value_Type can be different because only one logical thread of control is presumed so there is no need to combine multiple results. 

Static Semantics

{AI12-0262-1} A reduction_attribute_reference denotes a value, with its nominal subtype being the subtype of the first parameter of the subprogram denoted by the reducer_name.

Dynamic Semantics

{AI12-0250-1} {AI12-0262-1} {AI12-0327-1} {AI12-0355-2} For the evaluation of a value_sequence, the iterated_element_association, the chunk_specification, and the aspect_specification, if any, are elaborated in an arbitrary order. Next an iteration is performed, and for each value conditionally produced by the iteration (see 5.5 and 5.5.2), the associated expression is evaluated with the loop parameter having this value, which produces a result that is converted to Value_Type and is used to define the next value in the sequence.
{AI12-0262-1} If the value_sequence does not have the reserved word parallel, it is produced as a single sequence of values by a single logical thread of control. If the reserved word parallel is present in the value_sequence, the enclosing reduction_attribute_reference is a parallel construct, and the sequence of values is generated by a parallel iteration (as defined in 5.5, 5.5.1, and 5.5.2), as a set of non-empty, non-overlapping contiguous chunks (subsequences) with one logical thread of control (see clause 9) associated with each subsequence. If there is a chunk_specification, it determines the maximum number of chunks, as defined in 5.5; otherwise the maximum number of chunks is implementation defined.
Implementation defined: The maximum number of chunks for a parallel reduction expression without a chunk_specification.
{AI12-0262-1} For a value_sequence V, the following attribute is defined:
V'Reduce(Reducer, Initial_Value)

{AI12-0262-1} {AI12-0348-1} This attribute represents a reduction expression, and is in the form of a reduction_attribute_reference.
{AI12-0262-1} {AI12-0348-1} The evaluation of a use of this attribute begins by evaluating the parts of the reduction_attribute_designator (the reducer_name Reducer and the initial_value_expression Initial_Value), in an arbitrary order. It then initializes the accumulator of the reduction expression to the value of the initial_value_expression (the initial value). The value_sequence V is then evaluated.
{AI12-0262-1} {AI12-0348-1} If the value_sequence does not have the reserved word parallel, each value of the value_sequence is passed, in order, as the second (Value) parameter to a call on Reducer, with the first (Accumulator) parameter being the prior value of the accumulator, saving the result as the new value of the accumulator. The reduction expression yields the final value of the accumulator.
{AI12-0262-1} If the reserved word parallel is present in a value_sequence, then the (parallel) reduction expression is a parallel construct and the sequence has been partitioned into one or more subsequences (see above) each with its own separate logical thread of control.
{AI12-0262-1} {AI12-0348-1} Each logical thread of control creates a local accumulator for processing its subsequence. The accumulator for a subsequence is initialized to the first value of the subsequence, and calls on Reducer start with the second value of the subsequence (if any). The result for the subsequence is the final value of its local accumulator.
{AI12-0262-1} {AI12-0348-1} After all logical threads of control of a parallel reduction expression have completed, Reducer is called for each subsequence, in the original sequence order, passing the local accumulator for that subsequence as the second (Value) parameter, and the overall accumulator [(initialized above to the initial value)] as the first (Accumulator) parameter, with the result saved back in the overall accumulator. The parallel reduction expression yields the final value of the overall accumulator.
{AI12-0262-1} If the evaluation of the value_sequence yields an empty sequence of values, the reduction expression yields the initial value.
{AI12-0262-1} {AI12-0348-1} If an exception is propagated by one of the calls on Reducer, that exception is propagated from the reduction expression. If different exceptions are propagated in different logical threads of control, one is chosen arbitrarily to be propagated from the reduction expression as a whole.
Implementation Note: {AI12-0262-1} For a reduction_attribute_reference that has a value_sequence without the reserved word parallel or a prefix where the identifier of the reduction_attribute_designator is Reduce (see below), generally the compiler can still choose to execute the reduction in parallel, presuming doing so would not change the results. However sequential execution is necessary if the subtypes of the parameters of Reducer do not statically match, since there is no subprogram identified in the construct that could be used for combining the results in parallel.
Discussion: {AI12-0262-1} We say the calls to Reducer that combine the results of parallel execution are sequentially ordered in increasing order because certain reductions, such as vector concatenation, can be non-commutative (but still associative) operations. In order to return a deterministic result for parallel execution that is consistent with sequential execution, we need to specify an order for the iteration, and for the combination of results from the logical threads of control. It is also necessary that combining calls to Reducer are issued sequentially with respect to each other, which may require extra synchronization if the calls to Reducer are being executed by different logical threads of control.
{AI12-0242-1} For a prefix X of an array type[ (after any implicit dereference)], or that denotes an iterable container object (see 5.5.1), the following attributes are defined:
X'Reduce(Reducer, Initial_Value)

{AI12-0242-1} {AI12-0348-1} 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]
X'Parallel_Reduce(Reducer, Initial_Value)

{AI12-0242-1} {AI12-0348-1} X'Parallel_Reduce is a reduction expression that yields a result equivalent to replacing the attribute identifier with Reduce and the prefix of the attribute with the value_sequence:
[parallel for Item of X => Item]

Bounded (Run-Time) Errors

{AI12-0262-1} {AI12-0348-1} For a parallel reduction expression, it is a bounded error if the reducer subprogram is not associative. That is, for any arbitrary values of subtype Value_Type A, B, C and a reducer function R, the result of R (A, R (B, C)) should produce a result equal to R (R (A, B), C)). The possible consequences are Program_Error, or a result that does not match the equivalent sequential reduction expression due to the order of calls on the reducer subprogram being unspecified in the overall reduction. Analogous rules apply in the case of a reduction procedure.
Reason: In a sequential reduction expression, the reducer subprogram is called in a left-to-right order, whereas in a parallel reduction expression, the reducer subprogram is called in an order that depends on the number of logical threads of control that execute the reduction and on the elements/components given to each chunk. If the reducer is associative, this order does not matter, but in other cases, very different results are possible. While one can specify the maximum number of chunks, the actual number of chunks is unspecified. Similarly, the split of elements has only weak requirements. Thus, to get a consistent and portable result, an associative reducer is required for a parallel reduction. We define this as a Bounded (Run-Time) Errors to provide a stern warning about the required nature of the reducer subprogram and to let compilers detect the problem when possible.
To be honest: In this rule, “equal” means semantically equal. We don't care if the bit patterns differ but that the results mean the same thing. In particular, if the primitive equal is user-defined, that equality would be the one used to determine if this rule is violated. 


{AI12-0262-1} An expression function that returns its result as a Reduction Expression:
function Factorial(N : Natural) return Natural is
   ([for J in 1..N => J]'Reduce("*", 1));
{AI12-0262-1} An expression function that computes the Sine of X using a Taylor expansion:
function Sine (X : Float; Num_Terms : Positive := 5) return Float is
   ([for I in 1..Num_Terms =>
      (-1.0)**(I-1) * X**(2*I-1)/Float(Factorial(2*I-1))]'Reduce("+", 0.0));
{AI12-0262-1} {AI12-0379-1} A reduction expression that outputs the sum of squares:
Put_Line ("Sum of Squares is" &
          Integer'Image([for I in 1 .. 10 => I**2]'Reduce("+", 0)));
{AI12-0262-1} {AI12-0379-1} An expression function to compute the value of Pi:
--  See 3.5.7.
function Pi (Number_Of_Steps : Natural := 10_000) return Real is
  (1.0 / Real (Number_Of_Steps) *
    [for I in 1 .. Number_Of_Steps =>
        (4.0 / (1.0 + ((Real (I) - 0.5) *
           (1.0 / Real (Number_Of_Steps)))**2))]
              'Reduce("+", 0.0));
{AI12-0242-1} Calculate the sum of elements of an array of integers:
A'Reduce("+",0)  -- See 4.3.3.
{AI12-0242-1} Determine if all elements in a two-dimensional array of booleans are set to true:
Grid'Reduce("and", True)  -- See 3.6.
{AI12-0242-1} Calculate the minimum value of an array of integers in parallel:
A'Parallel_Reduce(Integer'Min, Integer'Last)
{AI12-0312-1} A parallel reduction expression used to calculate the mean of the elements of a two-dimensional array of subtype Matrix (see 3.6) that are greater than 100.0:
type Accumulator is record
   Sum   : Real; -- See 3.5.7.
   Count : Integer;
end record;
function Accumulate (L, R : Accumulator) return Accumulator is
  (Sum   => L.Sum   + R.Sum,
   Count => L.Count + R.Count);
function Average_of_Values_Greater_Than_100 (M : Matrix) return Real is
       Acc : constant Accumulator :=
          [parallel for Val of M when Val > 100.0 => (Val, 1)]
             'Reduce(Accumulate, (Sum => 0, Count => 0));
       Acc.Sum / Real(Acc.Count));

Extensions to Ada 2012

{AI12-0242-1} {AI12-0262-1} {AI12-0348-1} Reduction expressions attributes are new. 

Contents   Index   References   Search   Previous   Next 
Ada-Europe Ada 2005 and 2012 Editions sponsored in part by Ada-Europe