Expressions
Expressions: Definition
According to the Ada Reference Manual, an expression "is a formula that defines the computation or retrieval of a value." Also, when an expression is evaluated, the computed or retrieved value always has an associated type known at compile-time.
Even though the definition above is very simple, Ada expressions are actually very flexible — and they can also be very complex. In fact, if you read the corresponding section of the Ada Reference Manual, you'll quickly discover that they include elements such as relations, membership choices, terms and primaries. Some of these are classic elements of expressions in programming languages, although some of their forms are unique to Ada. In this section, we present examples of just some of these elements. For a complete overview, please refer to the Reference Manual.
In the Ada Reference Manual
Relations and simple expressions
Expressions usually consist of relations, which in turn consist of simple expressions. (There are more details to this, but we'll keep it simple for the moment.) Let's see a code example with a few expressions, which we dissect into the corresponding grammatical elements (we're going to discuss them later):
procedure Show_Expression_Elements is type Mode is (Off, A, B, C, D); pragma Unreferenced (B, C, D); subtype Active_Mode is Mode range Mode'Succ (Off) .. Mode'Last; M1, M2 : Mode; Dummy : Boolean; begin M1 := A; Dummy := M1 in Active_Mode and then M2 in Off | A; -- -- ^^^^^^^^^^^^^^^^^ relation -- -- ^^^^^^^^^^^^^^ relation -- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- expression Dummy := M1 in Active_Mode; -- ^^ name -- ^^ primary -- ^^ factor -- ^^ term -- ^^ simple expression -- -- ^^^^^^^^^^^ membership choice -- ^^^^^^^^^^^ membership choice list -- -- ^^^^^^^^^^^^^^^^^ relation -- ^^^^^^^^^^^^^^^^^ expression Dummy := M2 in Off | A; -- ^^ name -- ^^ primary -- ^^ factor -- ^^ term -- ^^ simple expression -- -- ^^^ membership choice -- ^ membership choice -- ^^^^^^^ membership choice list -- -- ^^^^^^^^^^^^^ relation -- ^^^^^^^^^^^^^ expression end Show_Expression_Elements;
In this code example, we see three expressions. As we mentioned earlier, every
expression has a type; here, the type of each expression is Boolean
.
The first expression (M1 in Active_Mode and then M2 in Off | A
) consists
of two relations: M1 in Active_Mode
and M2 in Off | A
. Let's
discuss some of the details.
The M1 in Active_Mode
relation consists of the simple expression
M1
and the membership choice list Active_Mode
. (Here, the
in
keyword is part of the relation definition.) Also, as we see in the
comments of the source code, the simple expression M1
is, at the same
time, a term, a factor, a primary and a name.
Let's briefly talk about this chain of syntactic elements for simple expressions. Very roughly said, this is how we can break up simple expressions:
a simple expression consists of terms;
a term consists of factors;
a factor consists of primaries;
a primary can be one of those:
a numeric literal;
null
;a string literal;
a name;
an allocator (like
new Integer
);
For further reading...
The definition of simple expressions we've just seen is very simplified. In actuality, these are the grammatical elements specified in the Ada Reference Manual:
simple_expression ::=
[unary_adding_operator] term {binary_adding_operator term}
term ::= factor {multiplying_operator factor}
factor ::= primary [** primary] | abs primary | not primary
primary ::=
numeric_literal | null | string_literal | aggregate
| name | allocator | (expression)
| (conditional_expression) | (quantified_expression)
| (declare_expression)
Later on in this chapter, we discuss conditional expressions, quantified expressions and declare expressions in more details.
In the relation M2 in Off | A
from the code example, Off | A
is
a membership choice list, and Off
and A
are membership choices.
For further reading...
Relations can actually be much more complicated than the one we just saw. In fact, this is the definition from the Ada Reference Manual:
expression ::=
relation {and relation}
| relation {and then relation}
| relation {or relation}
| relation {or else relation}
| relation {xor relation}
relation ::=
simple_expression
[relational_operator simple_expression]
| simple_expression [not] in
membership_choice_list
| raise_expression
Again, for more details, please refer to the section on expressions of the Ada Reference Manual.
In the Ada Reference Manual
Numeric expressions
The expressions we've seen so far had the Boolean
type. Although much
of the grammar described in the Manual exists exclusively for Boolean
operations, we can also write numeric expressions such as the following one:
procedure Show_Numeric_Expressions is C1 : constant Integer := 5; Dummy : Integer; begin Dummy := -2 ** 4 + 3 * C1 ** 8; -- ^ numeric literal -- ^ primary -- ^^ name -- ^^ primary -- ^^^^^^^ factor -- ^ multiplying operator -- ^ numeric literal -- ^ primary -- ^ factor -- ^^^^^^^^^^^ term -- -- ^ numeric literal -- ^ primary -- ^ numeric literal -- ^ primary -- ^^^^^^ factor -- ^^^^^^ term -- ^ binary adding operator -- ^ unary adding operator -- -- ^^^^^^^^^^^^^^^^^^^^^^ simple expression -- -- ^^^^^^^^^^^^^^^^^^^^^^ expression end Show_Numeric_Expressions;
In this code example, the expression - 2 ** 4 + 3 * C1 ** 8
consists of
just a single simple expression. (Note that simple expressions do not have to
be "simple".) This simple expression consists of two terms: 2 ** 4
and
3 * C1 ** 8
. While the 2 ** 4
term is also a single factor, the
3 * C1 ** 8
term consists of two factors: 3
and C1 ** 8
.
Both the 2 ** 4
and the C1 ** 8
factors consists of two primaries
each:
the
2 ** 4
factor has the primaries2
and4
,the
C1 ** 8
factor has the primariesC1
and8
.
In the Ada Reference Manual
Other expressions
Expressions aren't limited to the Boolean
type or to numeric types.
Indeed, expressions can be of any type, and the definition of primaries we've
seen earlier on already hints in this direction — as it includes elements
such as allocators. Because expressions are very flexible, covering all possible
variations and combinations in this section is out of scope. Again, please refer
to the section on expressions of the Ada Reference Manual for
further details.
Parenthesized expression
An interesting aspect of primaries is that, by using parentheses, we can embed an expression inside another expression. As an example, let's discuss the following expression and its elements:
procedure Show_Parenthesized_Expressions is C1 : constant Integer := 4; C2 : constant Integer := 5; Dummy : Integer; begin Dummy := (2 + C1) * C2; -- ^^ name -- ^^ primary -- ^^ factor -- ^^ term -- -- ^ numeric literal -- ^ primary -- ^ factor -- ^ term -- -- ^ binary adding operator -- ^^^^^^^^ simple expression -- -- ^^^^^^^^ expression -- ^^^^^^^^ primary -- ^^^^^^^^ factor -- -- ^^ factor -- ^^^^^^^^^^^^^ term -- -- ^^^^^^^^^^^^^ simple expression -- -- ^^^^^^^^^^^^^ expression end Show_Parenthesized_Expressions;
In this example, we first start with the single expression (2 + C1) * C2
,
which is also a simple expression consisting of just one term, which consists of
two factors: (2 + C1)
and C2
. The (2 + C1)
factor is also a
primary. Now, because of the parentheses, we identify that the primary
(2 + C1)
is an expression that is embedded in another expression.
Important
To be fair, the existence of parentheses in a primary could also indicate other kinds of expressions, such as conditional or quantified expressions. However, differentiating between them is straightforward, as we'll see later on in this chapter.
We then proceed to parse the (2 + C1)
expression, which consists of the
terms 2
and C1
. As we've seen in the comments of the code example,
each of these terms consists of one factor, which consists of one primary. In
the end, after parsing the primaries, we identify that 2
is a numeric
literal and C1
is a name.
Note that the usage of parentheses might lead to situations where we have expressions in potentially unsuspected places. For example, consider the following code example:
procedure Show_Name_In_Expression is type Mode is (Off, A, B, C, D); M1 : Mode; begin M1 := A; case M1 is when Off | D => null; when A | B | C => M1 := D; end case; end Show_Name_In_Expression;
Here, the case statement expects a selecting expression. In this case, M1
is identified as a name — after being identified as a relation, a simple
expression, a term, a factor and a primary.
However, if we replace case M1 is
by case (M1) is
, (M1)
is identified as a parenthesized expression, not as a name! This parenthesized
expression is first parsed and evaluated, which might have implications in case
statements, as we'll see
in another chapter.
Let's look at another example, this time with a subprogram call:
procedure Increment_By_One (I : in out Integer);procedure Increment_By_One (I : in out Integer) is begin I := I + 1; end Increment_By_One;with Increment_By_One; procedure Show_Name_In_Expression is V : Integer := 0; begin Increment_By_One ((V)); end Show_Name_In_Expression;
The Increment_By_One
procedure from this example expects a variable as an
actual parameter because the parameter mode is in out
. However, the
(V)
in the call to the procedure is interpreted as an expression, so we
end up providing a value — the result of the expression — as the
actual parameter instead of the V
variable. Naturally, this is a
compilation error. (Of course, writing Increment_By_One (V)
fixes the
error.)
Conditional Expressions
As we've seen before, we can write simple expressions such as I = 0
or
D.Valid
. A conditional expression, as the name implies, is an
expression that contains a condition. This might be an "if-expression" (in the
if ... then ... else
form) or a "case-expression" (in the
case ... is when =>
form).
The Max
function in the following code example is an expression function
implemented with a conditional expression — an if-expression, to be more
precise:
package Expr_Func is function Max (A, B : Integer) return Integer is (if A >= B then A else B); end Expr_Func;
Let's say we have a system with four states Off
, On
,
Waiting
, and Invalid
. For this system, we want to implement a
function named Toggled
that returns the toggled value of a state
S
. If the current value of S
is either Off
or On
,
the function toggles from Off
to On
(or from On
to Off
). For other values, the state remains unchanged — i.e. the
returned value is the same as the input value. This is the implementation using
a conditional expression:
package Expr_Func is type State is (Off, On, Waiting, Invalid); function Toggled (S : State) return State is (if S = Off then On elsif S = On then Off else S); end Expr_Func;
As you can see, if-expressions may contain an elsif
branch (and
therefore be more complicated).
The code above corresponds to this more verbose version:
package Expr_Func is type State is (Off, On, Waiting, Invalid); function Toggled (S : State) return State; end Expr_Func;package body Expr_Func is function Toggled (S : State) return State is begin if S = Off then return On; elsif S = On then return Off; else return S; end if; end Toggled; end Expr_Func;
If we compare the if-block of this code example to the if-expression of the
previous example, we notice that the if-expression is just a simplified version
without the return
keyword and the end if;
. In fact, converting
an if-block to an if-expression is quite straightforward.
We could also replace the if-expression used in the Toggled
function
above with a case-expression. For example:
package Expr_Func is type State is (Off, On, Waiting, Invalid); function Toggled (S : State) return State is (case S is when Off => On, when On => Off, when others => S); end Expr_Func;
Note that we use commas in case-expressions to separate the alternatives (the
when
expressions). The code above corresponds to this more verbose
version:
package Expr_Func is type State is (Off, On, Waiting, Invalid); function Toggled (S : State) return State; end Expr_Func;package body Expr_Func is function Toggled (S : State) return State is begin case S is when Off => return On; when On => return Off; when others => return S; end case; end Toggled; end Expr_Func;
If we compare the case block of this code example to the case-expression of the
previous example, we notice that the case-expression is just a simplified
version of the case block without the return
keyword and the
end case;
, and with alternatives separated by commas instead of
semicolons.
In the Ada Reference Manual
Quantified Expressions
Quantified expressions are for
expressions using a quantifier —
which can be either all
or some
— and a predicate. This
kind of expressions let us formalize statements such as:
"all values of array
A
must be zero" intofor all I in A'Range => A (I) = 0
, and"at least one value of array
A
must be zero" intofor some I in A'Range => A (I) = 0
.
In the quantified expression for all I in A'Range => A (I) = 0
, the
quantifier is all
and the predicate is A (I) = 0
. In the second
expression, the quantifier is some
. The result of a quantified
expression is always a Boolean value.
For example, we could use the quantified expressions above and implement these two functions:
Is_Zero
, which checks whether all components of an arrayA
are zero, andHas_Zero
, which checks whether arrayA
has at least one component of the arrayA
is zero.
This is the complete code:
package Int_Arrays is type Integer_Arr is array (Positive range <>) of Integer; function Is_Zero (A : Integer_Arr) return Boolean is (for all I in A'Range => A (I) = 0); function Has_Zero (A : Integer_Arr) return Boolean is (for some I in A'Range => A (I) = 0); procedure Display_Array (A : Integer_Arr; Name : String); end Int_Arrays;with Ada.Text_IO; use Ada.Text_IO; package body Int_Arrays is procedure Display_Array (A : Integer_Arr; Name : String) is begin Put (Name & ": "); for E of A loop Put (E'Image & " "); end loop; New_Line; end Display_Array; end Int_Arrays;with Ada.Text_IO; use Ada.Text_IO; with Int_Arrays; use Int_Arrays; procedure Test_Int_Arrays is A : Integer_Arr := (0, 0, 1); begin Display_Array (A, "A"); Put_Line ("Is_Zero: " & Boolean'Image (Is_Zero (A))); Put_Line ("Has_Zero: " & Boolean'Image (Has_Zero (A))); A := (0, 0, 0); Display_Array (A, "A"); Put_Line ("Is_Zero: " & Boolean'Image (Is_Zero (A))); Put_Line ("Has_Zero: " & Boolean'Image (Has_Zero (A))); end Test_Int_Arrays;
As you might have expected, we can rewrite a quantified expression as a loop
in the for I in A'Range loop if ... return ...
form. In the code below,
we're implementing Is_Zero
and Has_Zero
using loops and
conditions instead of quantified expressions:
package Int_Arrays is type Integer_Arr is array (Positive range <>) of Integer; function Is_Zero (A : Integer_Arr) return Boolean; function Has_Zero (A : Integer_Arr) return Boolean; procedure Display_Array (A : Integer_Arr; Name : String); end Int_Arrays;with Ada.Text_IO; use Ada.Text_IO; package body Int_Arrays is function Is_Zero (A : Integer_Arr) return Boolean is begin for I in A'Range loop if A (I) /= 0 then return False; end if; end loop; return True; end Is_Zero; function Has_Zero (A : Integer_Arr) return Boolean is begin for I in A'Range loop if A (I) = 0 then return True; end if; end loop; return False; end Has_Zero; procedure Display_Array (A : Integer_Arr; Name : String) is begin Put (Name & ": "); for E of A loop Put (E'Image & " "); end loop; New_Line; end Display_Array; end Int_Arrays;with Ada.Text_IO; use Ada.Text_IO; with Int_Arrays; use Int_Arrays; procedure Test_Int_Arrays is A : Integer_Arr := (0, 0, 1); begin Display_Array (A, "A"); Put_Line ("Is_Zero: " & Boolean'Image (Is_Zero (A))); Put_Line ("Has_Zero: " & Boolean'Image (Has_Zero (A))); A := (0, 0, 0); Display_Array (A, "A"); Put_Line ("Is_Zero: " & Boolean'Image (Is_Zero (A))); Put_Line ("Has_Zero: " & Boolean'Image (Has_Zero (A))); end Test_Int_Arrays;
So far, we've seen quantified expressions using indices — e.g.
for all I in A'Range => ...
. We could avoid indices in quantified
expressions by simply using the E of A
form. In this case, we can just
write for all E of A => ...
. Let's adapt the implementation of
Is_Zero
and Has_Zero
using this form:
package Int_Arrays is type Integer_Arr is array (Positive range <>) of Integer; function Is_Zero (A : Integer_Arr) return Boolean is (for all E of A => E = 0); function Has_Zero (A : Integer_Arr) return Boolean is (for some E of A => E = 0); end Int_Arrays;
Here, we're checking the components E
of the array A
and
comparing them against zero.
In the Ada Reference Manual
Declare Expressions
So far, we've seen expressions that make use of existing objects declared outside of the expression. Sometimes, we might want to declare constant objects inside the expression, so we can use them locally in the expression. Similarly, we might want to rename an object and use the renamed object in an expression. In those cases, we can use a declare expression.
A declare expression allows for declaring or renaming objects within an expression:
pragma Ada_2022; package P is function Max (A, B : Integer) return Integer is (declare Bigger_A : constant Boolean := (A >= B); begin (if Bigger_A then A else B)); end P;
The declare expression starts with the declare
keyword and the usual
object declarations, and it's followed by the begin
keyword and the
body. In this example, the body of the declare expression is a conditional
expression.
Of course, the code above isn't really useful, so let's look at a more complete example:
pragma Ada_2022; package Integer_Arrays is type Integer_Array is array (Positive range <>) of Integer; function Sum (Arr : Integer_Array) return Integer; -- -- Expression function using -- declare expression: -- function Avg (Arr : Integer_Array) return Float is (declare A : Integer_Array renames Arr; S : constant Float := Float (Sum (A)); L : constant Float := Float (A'Length); begin S / L); end Integer_Arrays;package body Integer_Arrays is function Sum (Arr : Integer_Array) return Integer is begin return Acc : Integer := 0 do for V of Arr loop Acc := Acc + V; end loop; end return; end Sum; end Integer_Arrays;pragma Ada_2022; with Ada.Text_IO; use Ada.Text_IO; with Integer_Arrays; use Integer_Arrays; procedure Show_Integer_Arrays is Arr : constant Integer_Array := [1, 2, 3]; begin Put_Line ("Sum: " & Sum (Arr)'Image); Put_Line ("Avg: " & Avg (Arr)'Image); end Show_Integer_Arrays;
In this example, the Avg
function is implemented using a declare
expression. In this expression, A
renames the Arr
array, and
S
is a constant initialized with the value returned by the Sum
function.
In the Ada Reference Manual
Restrictions in the declarative part
The declarative part of a declare expression is more restricted than the declarative part of a subprogram or declare block. In fact, we cannot:
declare variables;
declare constants of limited types;
rename an object of limited type that is constructed within the declarative part;
declare aliased constants;
declare constants that make use of the
Access
orUnchecked_Access
attributes in the initialization;declare constants of anonymous access type.
Let's see some examples of erroneous declarations:
pragma Ada_2022; package Integer_Arrays is type Integer_Array is array (Positive range <>) of Integer; type Integer_Sum is limited private; type Const_Integer_Access is access constant Integer; function Sum (Arr : Integer_Array) return Integer; function Sum (Arr : Integer_Array) return Integer_Sum; -- -- Expression function using -- declare expression: -- function Avg (Arr : Integer_Array) return Float is (declare A : Integer_Array renames Arr; S1 : aliased constant Integer := Sum (A); -- ERROR: aliased constant S : Float := Float (S1); L : Float := Float (A'Length); -- ERROR: declaring variables S2 : constant Integer_Sum := Sum (A); -- ERROR: declaring constant of -- limited type A1 : Const_Integer_Access := S1'Unchecked_Access; -- ERROR: using 'Unchecked_Access -- attribute A2 : access Integer := null; -- ERROR: declaring object of -- anonymous access type begin S / L); private type Integer_Sum is new Integer; end Integer_Arrays;package body Integer_Arrays is function Sum (Arr : Integer_Array) return Integer is begin return Acc : Integer := 0 do for V of Arr loop Acc := Acc + V; end loop; end return; end Sum; function Sum (Arr : Integer_Array) return Integer_Sum is (Integer_Sum (Integer'(Sum (Arr)))); end Integer_Arrays;
In this version of the Avg
function, we see many errors in the
declarative part of the declare expression. If we convert the declare
expression into an actual function implementation, however, those declarations
won't trigger compilation errors. (Feel free to try this out!)
Reduction Expressions
Note
This feature was introduced in Ada 2022.
A reduction expression reduces a list of values into a single value. For
example, we can reduce the list [2, 3, 4]
to a single value:
by adding the values of the list:
2 + 3 + 4 = 9
, orby multiplying the values of the list:
2 * 3 * 4 = 24
.
We write a reduction expression by using the Reduce
attribute and
providing the reducer and its initial value:
the reducer is the operator (e.g.:
+
or*
) that we use to combine the values of the list;the initial value is the value that we use before all other values of the list.
For example, if we use +
as the operator and 0
an the initial
value, we get the reduction expression: 0 + 2 + 3 + 4 = 9
. This can be
implemented using an array:
with Ada.Text_IO; use Ada.Text_IO; procedure Show_Reduction_Expression is A : array (1 .. 3) of Integer; I : Integer; begin A := [2, 3, 4]; I := A'Reduce ("+", 0); Put_Line ("A = " & A'Image); Put_Line ("I = " & I'Image); end Show_Reduction_Expression;---- run info:
Here, we have the array A
with a list of values. The
A'Reduce ("+", 0)
expression reduces the list of values of A
into
a single value — in this case, an integer value that is stored in
I
. This statement is equivalent to:
I := 0;
for E of A loop
I := I + E;
end loop;
Naturally, we can reduce the array using the *
operator:
with Ada.Text_IO; use Ada.Text_IO; procedure Show_Reduction_Expression is A : array (1 .. 3) of Integer; I : Integer; begin A := [2, 3, 4]; I := A'Reduce ("*", 1); Put_Line ("A = " & A'Image); Put_Line ("I = " & I'Image); end Show_Reduction_Expression;---- run info:
In this example, we call A'Reduce ("*", 1)
to reduce the list. (Note
that we use an initial value of one because it is the
identity element of a multiplication, so the
complete operation is: 1 * 2 * 3 * 4 = 24
.)
In the Ada Reference Manual
Value sequences
In addition to arrays, we can apply reduction expression to value sequences,
which consist of an iterated element association — for example,
[for I in 1 .. 3 => I + 1]
. We can simply append the reduction
expression to a value sequence:
with Ada.Text_IO; use Ada.Text_IO; procedure Show_Reduction_Expression is I : Integer; begin I := [for I in 1 .. 3 => I + 1]'Reduce ("+", 0); Put_Line ("I = " & I'Image); I := [for I in 1 .. 3 => I + 1]'Reduce ("*", 1); Put_Line ("I = " & I'Image); end Show_Reduction_Expression;---- run info:
In this example, we create the value sequence [for I in 1 .. 3 => I + 1]
and reduce it using the +
and *
operators. (Note that the
operations in this example have the same results as in the previous examples
using arrays.)
Custom reducers
In the previous examples, we've used standard operators such as +
and
*
as the reducer. We can, however, write our own reducers and pass
them to the Reduce
attribute. For example:
with Ada.Text_IO; use Ada.Text_IO; procedure Show_Reduction_Expression is type Integer_Array is array (Positive range <>) of Integer; A : Integer_Array (1 .. 3); I : Long_Integer; procedure Accumulate (Accumulator : in out Long_Integer; Value : Integer) is begin Accumulator := Accumulator + Long_Integer (Value); end Accumulate; begin A := [2, 3, 4]; I := A'Reduce (Accumulate, 0); Put_Line ("A = " & A'Image); Put_Line ("I = " & I'Image); end Show_Reduction_Expression;---- run info:
In this example, we implement the Accumulate
procedure as our reducer,
which is called to accumulate the individual elements (integer values) of the
list. We pass this procedure to the Reduce
attribute in the
I := A'Reduce (Accumulate, 0)
statement, which is equivalent to:
I := 0;
for E of A loop
Accumulate (I, E);
end loop;
A custom reducer must have the following parameters:
The accumulator parameter, which stores the interim result — and the final result as well, once all elements of the list have been processed.
The value parameter, which is a single element from the list.
Note that the accumulator type doesn't need to match the type of the individual
components. In this example, we're using Integer
as the component type,
while the accumulator type is Long_Integer
. (For this kind of reducers,
using Long_Integer
instead of Integer
for the accumulator type
makes lots of sense due to the risk of triggering overflows while the reducer
is accumulating values — e.g. when accumulating a long list with larger
numbers.)
In the example above, we've implemented the reducer as a procedure. However, we can also implement it as a function. In this case, the accumulated value is returned by the function:
with Ada.Text_IO; use Ada.Text_IO; procedure Show_Reduction_Expression is type Integer_Array is array (Positive range <>) of Integer; A : Integer_Array (1 .. 3); I : Long_Integer; function Accumulate (Accumulator : Long_Integer; Value : Integer) return Long_Integer is begin return Accumulator + Long_Integer (Value); end Accumulate; begin A := [2, 3, 4]; I := A'Reduce (Accumulate, 0); Put_Line ("A = " & A'Image); Put_Line ("I = " & I'Image); end Show_Reduction_Expression;---- run info:
In this example, we converted the Accumulate
procedure into a function
(while the core implementation is essentially the same).
Note that the reduction expression remains the same, independently of whether
we're using a procedure or a function as the reducer. Therefore, the statement
with the reduction expression in this example is the same as in the previous
example: I := A'Reduce (Accumulate, 0);
. Now that we're using a
function, this statement is equivalent to:
I := 0;
for E of A loop
I := Accumulate (I, E);
end loop;
Other accumulator types
The accumulator type isn't restricted to scalars: in fact, we could use record types as well. For example:
with Ada.Text_IO; use Ada.Text_IO; procedure Show_Reduction_Expression is type Integer_Array is array (Positive range <>) of Integer; A : Integer_Array (1 .. 3); type Integer_Accumulator is record Value : Long_Integer; Count : Integer; end record; function Accumulate (Accumulator : Integer_Accumulator; Value : Integer) return Integer_Accumulator is begin return (Value => Accumulator.Value + Long_Integer (Value), Count => Accumulator.Count + 1); end Accumulate; function Zero return Integer_Accumulator is (Value => 0, Count => 0); function Average (Acc : Integer_Accumulator) return Float is (Float (Acc.Value) / Float (Acc.Count)); Acc : Integer_Accumulator; begin A := [2, 3, 4]; Acc := A'Reduce (Accumulate, Zero); Put_Line ("Acc = " & Acc'Image); Put_Line ("Avg = " & Average (Acc)'Image); end Show_Reduction_Expression;---- run info:
In this example, we're using the Integer_Accumulator
record type in our
reducer — the Accumulate
function. In this case, we're not only
accumulating the values, but also counting the number of elements in the
list. (Of course, we could have used A'Length
for that as well.)
Also, we're not limited to numeric types: we can also create a reducer using strings as the accumulator type. In fact, we can display the initial value and the elements of the list by using unbounded strings:
with Ada.Text_IO; use Ada.Text_IO; with Ada.Strings.Unbounded; use Ada.Strings.Unbounded; procedure Show_Reduction_Expression is type Integer_Array is array (Positive range <>) of Integer; A : Integer_Array (1 .. 3); function Unbounded_String_List (Accumulator : Unbounded_String; Value : Integer) return Unbounded_String is begin return Accumulator & ", " & Value'Image; end Unbounded_String_List; begin A := [2, 3, 4]; Put_Line ("A = " & A'Image); Put_Line ("L = " & To_String (A'Reduce (Unbounded_String_List, To_Unbounded_String ("0")))); end Show_Reduction_Expression;---- run info:
In this case, the "accumulator" is concatenating the initial value and individual values of the list into a string.