Version 1.7 of ai22s/ai22-0011-1.txt
!standard 4.5.10(9/5) 22-01-19 AI22-0011-1/05
!standard 4.5.10(10/5)
!standard 4.5.10(12/5)
!standard 4.5.10(13/5)
!standard 4.5.10(15/5)
!standard 4.5.10(16/5)
!standard 4.5.10(17/5)
!standard 4.5.10(18/5)
!standard 4.5.10(19/5)
!standard 4.5.10(20/5)
!standard 4.5.10(24/5)
!standard 4.5.10(25/5)
!standard 4.5.10(27/5)
!standard 4.5.10(29/5)
!standard 4.5.10(34/5)
!class binding interpretation 21-11-11
!status Corrigendum 1-2022 22-01-19
!status ARG Approved 11-0-0 21-11-18
!status work item 21-11-11
!status received 21-04-28
!priority Low
!difficulty Easy
!qualifier Omission
!subject Reduction expression issues
!summary
Various minor issues with reduction expressions.
!question
A few minor issues with reduction expressions.
1) It should be made clear that in the case of an indefinite accumulator
subtype, the tag, bounds, or discriminant values of the accumulator
object may be determined by its initial value in the same way as for
any other object declaration.
2) It should be made clear that neither the accumulator type nor
the value type of a reduction expression may be an anonymous access type.
3) It should be made clear that in the zero-iteration case, the
initial value is still assigned to the accumulator (which involves
a conversion to the accumulator subtype, which may fail). RM 4.5.10(29/5)
incorrectly states otherwise.
4) Accum_Type is a subtype, despite its name. The same applies to Value_Type.
We work around this by saying Accum_Type subtype all over the place.
It would be better to call these Accum_Subtype and Value_Subtype in the
first place.
5) The result of a reduction expression has the nominal subtype of Accum_Type.
However, the initial value may have some other subtype (the only
requirement is that it have the type of Accum_Type). Similarly, the
expression of the iterated_element_association may not belong to
Accum_Type. We need wording to convert these to the appropriate subtype.
(Note that the parameter and/or result of the reducer subprogram does have
the correct subtype and does not need a conversion.)
Should these be fixed? (Yes.)
!recommendation
(See Summary.)
!wording
Modify 4.5.10(9/5):
In the remainder of this subclause, we will refer to nonlimited subtypes
[Value_Type]{Value_Subtype} and [Accum_Type]{Accum_Subtype} 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:
Modify 4.5.10(10/5):
* [*Accum_Type*]{*Accum_Subtype*} is a subtype of the expected type of
the reduction_attribute_reference.
Replace 4.5.10(12/5) with:
function Reducer(Accumulator : Accum_Subtype;
Value : Value_Subtype) return Accum_Subtype;
Replace 4.5.10(13/5) with:
procedure Reducer(Accumulator : in out Accum_Subtype;
Value : in Value_Subtype);
Modify 4.5.10(15/5):
* The expected type of an initial_value_expression of a
reduction_specification is [that of subtype Accum_Type]{the type of
Accum_Subtype}.
Modify 4.5.10(16/5):
* The expected type of the expression of the
iterated_element_association of a value_sequence is [that of subtype
Value_Type]{the type of Value_Subtype}.
Modify 4.5.10(17/5):
If the reduction_attribute_reference has a value_sequence with the reserved
word parallel, [the subtypes Accum_Type and Value_Type]{Accum_Subtype
and Value_Subtype} shall statically match.
Modify 4.5.10(18/5):
If the identifier of a reduction_attribute_designator is Parallel_Reduce,
[the subtypes Accum_Type and Value_Type]{Accum_Subtype and
Value_Subtype} shall statically match.
Modify AARM 4.5.10(18.a/5):
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]{Accum_Subtype and Value_Subtype} can be different because
only one logical thread of control is presumed so there is no need to
combine multiple results.
Add after 4.5.10(18/5) [at the end of the Legality Rules section].
Neither the type of @i<Accum_Subtype> nor @i<Value_Subtype> shall be an
anonymous access type.
Replace 4.5.10(19/5) with:
A reduction_attribute_reference denotes a value, with its nominal subtype
being Accum_Subtype Redundant[(the subtype of the first parameter of the
subprogram denoted by the reducer_name)].
[Editor's note: We didn't need to change this one, but it is odd and confusing
to have a name for something and not use it in the rules. I left the old part
as a parenthesized remark as it can be clarifying.]
Modify 4.5.10(20/5):
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]{Value_Subtype} and is used to define the next value in the
sequence.
Modify 4.5.10(24/5):
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
{converts the the value of the initial_value_expression (the initial value)
to Accum_Subtype and then }initializes the accumulator {with the result}[of
the reduction expression to the value of the initial_value_expression (the
initial value)]. The value_sequence V is then evaluated.
Add an AARM note after 4.5.10(24/5):
AARM Ramification: This initialization follows the object initialization
rules of an explicit object declaration. In particular, if the accumulator
subtype is indefinite, then the tag, array bounds, or discriminants of the
accumulator object are determined by its initial value as needed.
Modify 4.5.10(25/5):
If the value_sequence does not have the reserved word parallel, each value
of the value_sequence is {converted to Value_Subtype and then} 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.
Modify 4.5.10(27/5):
Each logical thread of control creates a local accumulator for processing
its subsequence. The accumulator for a subsequence is initialized to the
{result of converting the} first value of the subsequence {to
Accum_Subtype}, 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.
Modify 4.5.10(29/5):
If the evaluation of the value_sequence yields an empty sequence of values,
the reduction expression yields the initial value{ of the accumulator}.
Modify 4.5.10(34/5):
For a parallel reduction expression, it is a bounded error if the reducer
subprogram is not associative. That is, for any arbitrary values of
{Value_Subtype}[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)); it is a bounded error if R does not. 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.
!discussion
Adding an AARM note for #1 is harmless at worst. It seems worthwhile
to clarify this point.
#2 eliminates useless-but-hard-to-implement corner cases. If the
the type of the accumulator subtype can be an anonymous access type,
then what happens if the intial value expression is an allocator?
When is that allocated object finalized? Is the accumulator object
a standalone object of an anonymous access type? Similar questions arise if
the type of the value subtype can be an anonymous access type and the
value sequence contains allocators.
It might be argued that no new legality rule is needed to disallow
anonymous access types because this is already a (somewhat obscure)
consequence of the existing rules, at least in the case of a
reducer function (as opposed to a reducer procedure).
It seems better to state this explicitly.
The change for #4 clarifies most of the wording. Talking about the
"Accum_Type subtype" is annoying, while the "type of Accum_Subtype" is a
usual thing to talk about. So we make this change.
The change for #3 seems like a clear improvement; otherwise a
reduction expression might yield a result that is not within its
nominal subtype.
But for that to be the case (#5), we need to convert the initial value and
any expressions to the Accum_Subtype, so that any constraints or predicates
are respected. We chose to keep the accumulator(s) always having the
appropriate subtype. This is similar to what would happen if we had a
declaration for the accumulator:
Accumulator : Accum_Subtype;
And in any case we have to check the value before passing it to a reducer,
so there is no real benefit in waiting.
!corrigendum 4.5.10(9/5)
Replace the paragraph:
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:
by:
In the remainder of this subclause, we will refer to nonlimited subtypes
Value_Subtype and Accum_Subtype 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:
!corrigendum 4.5.10(10/5)
Replace the paragraph:
- Accum_Type is a subtype of the expected type of
the reduction_attribute_reference.
by:
- Accum_Subtype is a subtype of the expected type of
the reduction_attribute_reference.
!corrigendum 4.5.10(12/5)
Replace the paragraph:
function Reducer(Accumulator : Accum_Type;
Value : Value_Type) return Accum_Type;
by:
function Reducer(Accumulator : Accum_Subtype;
Value : Value_Subtype) return Accum_Subtype;
!corrigendum 4.5.10(13/5)
Replace the paragraph:
procedure Reducer(Accumulator : in out Accum_Type;
Value : in Value_Type);
by:
procedure Reducer(Accumulator : in out Accum_Subtype;
Value : in Value_Subtype);
!corrigendum 4.5.10(15/5)
Replace the paragraph:
- The expected type of an initial_value_expression of a
reduction_specification is that of subtype Accum_Type.
by:
- The expected type of an initial_value_expression of a
reduction_specification is the type of Accum_Subtype.
!corrigendum 4.5.10(16/5)
Replace the paragraph:
- The expected type of the expression of the
iterated_element_association of a value_sequence
is that of subtype Value_Type.
by:
- The expected type of the expression of the
iterated_element_association of a value_sequence
is the type of Value_Subtype.
!corrigendum 4.5.10(17/5)
Replace the paragraph:
If the reduction_attribute_reference has a value_sequence with the
reserved word parallel, the subtypes Accum_Type and Value_Type
shall statically match.
by:
If the reduction_attribute_reference has a value_sequence with the
reserved word parallel, Accum_Subtype and Value_Subtype
shall statically match.
!corrigendum 4.5.10(18/5)
Replace the paragraph:
If the identifier of a reduction_attribute_designator is
Parallel_Reduce, the subtypes Accum_Type and Value_Type shall
statically match.
by:
If the identifier of a reduction_attribute_designator is
Parallel_Reduce, Accum_Subtype and Value_Subtype shall
statically match.
Neither the type of Accum_Subtype nor Value_Subtype shall be an
anonymous access type.
!corrigendum 4.5.10(19/5)
Replace the paragraph:
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.
by:
A reduction_attribute_reference denotes a value, with its nominal subtype
being Accum_Subtype (the subtype of the first parameter of the subprogram
denoted by the reducer_name).
!corrigendum 4.5.10(20/5)
Replace the paragraph:
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.
by:
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_Subtype and is used to define the next value in the sequence.
!corrigendum 4.5.10(24/5)
Replace the paragraph:
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.
by:
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 converts the the value of the
initial_value_expression (the initial value) to Accum_Subtype
and then initializes the accumulator with the result. The
value_sequence V is then evaluated.
!corrigendum 4.5.10(25/5)
Replace the paragraph:
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.
by:
If the value_sequence does not have the reserved word
parallel, each value of the value_sequence is converted to
Value_Subtype and then 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.
!corrigendum 4.5.10(27/5)
Replace the paragraph:
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.
by:
Each logical thread of control creates a local accumulator for
processing its subsequence. The accumulator for a subsequence is initialized
to the result of converting the first value of the subsequence to
Accum_Subtype, 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.
!corrigendum 4.5.10(29/5)
Replace the paragraph:
If the evaluation of the value_sequence yields an empty
sequence of values, the reduction expression yields the initial value.
by:
If the evaluation of the value_sequence yields an empty
sequence of values, the reduction expression yields the initial value of the
accumulator.
!corrigendum 4.5.10(34/5)
Replace the paragraph:
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)); it is a bounded
error if R does not. 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.
by:
For a parallel reduction expression, it is a bounded error if the reducer
subprogram is not associative. That is, for any arbitrary values of
Value_Subtype 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)); it is a bounded
error if R does not. 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.
!ACATS test
An ACATS B-Test should be written to check that no anonymous access types are
allowed. ACATS C-Test(s) should be written to check that the conversions are
made (and fail when needed).
!appendix
From: Steve Baird
WG 9 Review issue #139 - May 14, 2021
Looking more closely at value_sequences involves looking at
iterated_element_associations. And that leads to wondering about whether
the accessibility level is well defined for the loop parameter of such an
iteration. Consider this expression:
[for Loop_Param of ... => Loop_Param.Aliased_Component'Access]'Reduce
(Function_With_Anonymous_Access_Parameter, Init_Value)
When Function_With_Anonymous_Access_Parameter is called (at runtime), an
accessibility level will need to be passed in. What rules define
that level?
Perhaps the apparently-missing accessibility level definition follows
from some equivalence rule, but I don't see it.
If there is an issue here, then (at least) two solutions come to mind.
One approach is to make sure this case is well-defined in 3.10.2.
Alternatively, we could require that the Value_Type and the Accum_Type
of a reduction expression shall each not be an anonymous access type.
Is there a similar issue with quantified expressions? Perhaps not
(although it seems suspicious that 5.5.2(8.a/5) talks about "the loop
statement", as opposed to other looping constructs).
[Remainder of (off-topic) note is elided here, see AI22-0025-1 for
it - Editor.]
****************************************************************
From: Randy Brukardt
WG 9 Review issue #139 - May 29, 2021
This can't possibly be something that is going to matter to 99.99% of
reduction expressions, so resolving this sort of corner case can wait for a
future Ada. (Preferably about Ada 2222. :-) Marked as deferred.
****************************************************************
!topic Roman numbers reduction expression example
!reference Ada 202X Darft 29 RM 4.2.1(18/5ff), 4.5.10
!from Christoph Grein 2021-05-27
!discussion
I'm, or better GNAT CE 2020 and 2021 are having problems with the example
cited above. GNAT complains about the initial value 0:
[for I in R'Range =>
(if I < R'Last and then R(I) < R(I + 1) then -1 else 1) * R(I)]
'Reduce("+", 0)
roman.adb:40:25: warning: value not in range of type "Roman_Number" defined at line 24 [enabled by default]
roman.adb:40:25: warning: "Constraint_Error" will be raised at run time [enabled by default]
I've reported this in 2020, but since I'm not a supported user, didn't get an answer.
My analysis:
Accum_Type and Value_Type are the same here, the Reducer is
function "+" (Left, Right: Roman_Number'Base) return Roman_Number'Base;
Now according to the RM, Accum_Type is a subtype of the expected type, here
the expected type is obviously the *type of Roman_Number*, let's call it T (in
recursive letter like it's done in the RM when talking about a type). Now
Roman_Number trivially *is* a subtype of T , but the RM does not explicitly
say which subtype. Starting from the reducer subprogram, the subtype to choose
must obviously be Roman_Number'Base or else the initial value 0 and also the
value -1 in the value sequence are not in range.
The RM is rather obscure in this subclause.
So either the example is wrong or GNAT chooses the wrong subtype.
****************************************************************
From: Tucker Taft
Sent: Thursday, May 27, 2021 2:31 PM
I agree that the GNAT warning is incorrect in this case. It is a somewhat
unusual case in that the initial value never becomes the final value of the
reduction. But in general, the warning does seem overly strict, since
there are probably a number of situations where the initial value never
becomes the final value, and it is certainly wrong to say "Constraint_Error
*will* be raised". The nominal subtype of a reduction expression is the
Accum_Type, and that comes from the first parameter to "+", which as you
point out is Roman_Number'Base, and 0 is certainly a legal value of that
subtype.
I suggest you suppress the warnings for now.
****************************************************************
From: Christoph Grein
Sent: Friday, May 28, 2021 2:42 AM
It does raise Constraint_Error.
There is another problem in GNAT:
X: Roman_Number := "III" * "IV" * "XII"; -- 144 (that is, CXLIV)
roman.adb:51:30: error: there is no applicable operator "*" for a string type
Replacing this by
X: Roman_Number := to_Roman_number("III") * "IV" * "XII"; it compiles.
So, Tuck, you say GNAT is wrong? I'll rereport it to report@adacore.com with
reference to you.
****************************************************************
From: Tucker Taft
Sent: Friday, May 28, 2021 7:12 AM
> It does raise Constraint_Error.
Interesting. It sounds like it is checking against the wrong subtype, and the
warning comes from that, so I guess suppressing the warning won't solve the
problem. It is presumably using the first subtype of the type rather than the
subtype of the first parameter of the Reducer subprogram as the subtype for
the accumulator.
>There is another problem in GNAT:
>
> X: Roman_Number := "III" * "IV" * "XII"; -- 144 (that is, CXLIV)
>roman.adb:51:30: error: there is no applicable operator "*" for a string
>type
>
>Replacing this by
> X: Roman_Number := to_Roman_number("III") * "IV" * "XII";
>it compiles.
It looks like overload resolution is not considering fully the possibility of
user-defined string literals.
>So, Tuck, you say GNAT is wrong? I'll rereport it to report@adacore.com
>with reference to you.
OK. Since this is an unsupported account, I presume it might not be fixed
immediately, but thank you for documenting the problems! That will certainly
make it more likely a fix can be found sooner.
****************************************************************
From: Egil Harald Hoevik
Sent: Friday, May 28, 2021 7:40 AM
> It does raise Constraint_Error.
explicitly stating Roman_Number'Base(0) seems to be a workaround
> There is another problem in GNAT:
>
> X: Roman_Number := "III" * "IV" * "XII"; -- 144 (that is, CXLIV)
> roman.adb:51:30: error: there is no applicable operator "*" for a string
> type
>
> Replacing this by
> X: Roman_Number := to_Roman_number("III") * "IV" * "XII";
> it compiles.
That triggered a GNAT bug-box here with the newest GNAT Pro wavefront (22.0w 20210424)
however, both
X: Roman_Number := to_Roman_number("III") * to_Roman_number("IV") * to_Roman_number("XII");
and
X: Roman_Number := Roman_Number' ("III") * Roman_Number' ("IV") * Roman_Number'("XII");
works, though
****************************************************************
From: Steve Baird [Privately]
Sent: Friday, October 22, 2021 5:25 PM
Let's assume we are talking about a reduction expression with neither
"parallel" nor "Parallel_Reduce" anywhere in sight.
The type of the Initializer expression and the type of the first parameter of
the Reducer_Subprogram are required to be the same. And if the reducer
subprogram is a function, then the function result type also has to be the
same type.
The RM uses the name Accum_Type to denote this type.
I suppose it goes without saying that Accum_Type cannot be an anonymous
access type. And it is stated explicitly that it cannot be limited. But it can
be class-wide, right?
[Brief digression: Do we need to state that Value_Type cannot be an anonymous
access type?]
And the aforementioned 2-3 subtypes of Accum_Type can all differ with respect to
subtype-specific properties, such as constraints or predicates. They are not
required to statically match.
Consider, for example, the case where the subtype of the first parameter of the
Reducer_Subprogram is indefinite (e.g., Standard.String or
Some_Discriminated_Type'Class). In that case, presumably the accumulator object
inherits the constraints and/or tag of the initial value; does that need to be
stated explicitly, or does that follow implicitly from 4.5.10(24):
" It then initializes the accumulator of the reduction expression to the value of the
initial_value_expression (the initial value)."
?
This is inconsistent with the 4.5.10(19) rule that the nominal subtype of a
reduction expression is
"the subtype of the first parameter of the subprogram denoted by the
reducer_name".
To see the inconsistency, consider the zero-iteration case. In this case, the
value of the reduction expression is that of the initial value. In that case,
there is no justification for assuming that the value of the reduction
expression will satisfy the constraints/predicates of its nominal subtype.
This can lead to problems, for example with a case statement that cases on
such a zero-iteration reduction expression and only provides choices for the
values of the nominal subtype of that reduction expression.
Suppose we require that the other 1-2 subtypes shall be statically compatible
with the one that was chosen as the nominal subtype? Alternatively, we could
change the dynamic semantics to include an explicit nominal-subtype membership
check in the zero-iteration case. The latter approach is a dynamic semantics
change; those are sometimes considered the worst sort of incompatibility, but
it doesn't seem so bad in this case.
Opinions?
****************************************************************
From: Tucker Taft [Privately]
Sent: Friday, October 22, 2021 5:56 PM
...
>The RM uses the name Accum_Type to denote this type.
No, it uses Accum_Type to designate the *subtype* and several times says
"subtype Accum_Type". I think this might be a fundamental issue in the way
you interpreted this section. You really need to think of Accum_Type as a
particular *subtype*.
> I suppose it goes without saying that Accum_Type cannot be an anonymous
> access type. And it is stated explicitly that it cannot be limited. But it
> can be class-wide, right?
>
> [Brief digression: Do we need to state that Value_Type cannot be an anonymous
> access type?]
Not sure why you say this goes without saying. I haven't thought about all
the implications, but I don't see any rule disallowing Accum_Type from being
an anonymous access type, and if that causes heartburn, we need to say it
explicitly.
> And the aforementioned 2-3 subtypes of Accum_Type can all differ with
> respect to subtype-specific properties, such as constraints or predicates.
> They are not required to statically match.
I don't agree. Subtype conformance is required, so that to me says that we
do expect the subtypes to match. And if that is not clear to you, we should
make it clear. There seems no reason to allow the subtypes to differ, and as
mentioned above, Accum_Type is itself a particular subtype.
> Consider, for example, the case where the subtype of the first parameter of
> the Reducer_Subprogram is indefinite (e.g., Standard.String or
> Some_Discriminated_Type'Class). In that case, presumably the accumulator
> object inherits the constraints and/or tag of the initial value; does that
> need to be stated explicitly, or does that follow implicitly from 4.5.10(24):
> " It then initializes the accumulator of the reduction expression to the
> value of the initial_value_expression (the initial value)."
>?
I agree that that would seem to establish the *runtime* subtype properties of
the accumulator, if Accum_Type is an indefinite subtype. I suppose it
wouldn't hurt to make that explicit, at least in the AARM.
> This is inconsistent with the 4.5.10(19) rule that the nominal subtype of a
> reduction expression is
> "the subtype of the first parameter of the subprogram denoted by the
> reducer_name".
Not sure why you say that. Isn't this the same as a function whose result
subtype is indefinite? The nominal subtype of the result of the function call
is still that of the result subtype. It isn't affected by the possibility
that the body is simply "return value;".
> To see the inconsistency, consider the zero-iteration case. In this case,
> the value of the reduction expression is that of the initial value. In that
> case, there is no justification for assuming that the value of the reduction
> expression will satisfy the constraints/predicates of its nominal subtype.
I don't agree, since the assignment of the initial value to the accumulator
happens even in the case of the zero-iteration case, so if Accum_Type is *not*
an indefinite subtype, then clearly the initial value must satisfy its
constraints.
I think the problem is really the last rule which says:
If the evaluation of the value_sequence yields an empty sequence of values,
the reduction expression yields the initial value.
This probably should be augmented with something like "converted to Accum_Type"
or to say it "yields the initial value assigned to the accumulator."
> This can lead to problems, for example with a case statement that cases on
> such a zero-iteration reduction expression and only provides choices for the
> values of the nominal subtype of that reduction expression.
>
> Suppose we require that the other 1-2 subtypes shall be statically
> compatible with the one that was chosen as the nominal subtype?
I am unclear on what you mean by the other 1-2 subtypes. In my view, there is
only the Accum_Type, which despite its name is a well-defined *subtype* which
is the nominal subtype of the whole expression. Accum_Type must represent a
particular subtype because in the parallel cases we require it to statically
match the Value_Type, in 4.5.10(17/5).
> Alternatively, we could change the dynamic semantics to include an explicit
> nominal-subtype membership check in the zero-iteration case. The latter
> approach is a dynamic semantics change; those are sometimes considered the
> worst sort of incompatibility, but it doesn't seem so bad in this case.
I definitely think the initial value needs to satisfy the constraints of the
Accum_Type, and the Accum_Type is the nominal subtype of the result. The only
confusing part in my view is 4.5.10(15/5) which should probably be clarified
to say we return the initial value of the accumulator, which is of the
Accum_Type.
> Opinions?
See above.
****************************************************************
From: Steve Baird [Privately]
Sent: Friday, October 22, 2021 6:49 PM
Thanks for the quick response.
Ok, I was confused - Accum_Type is a subtype, not a type. So a lot of my
message was noise.
===
I say Accum_Type cannot be an anonymous access type (at least in the function
case) because in a case like
function Reducer(Accumulator : access Integer;
Value : Value_Type) return access Integer;
the two access types are not the same type, so there is no way that it can be
subtype conformant with any profile of the form
function Reducer(Accumulator : Accum_Type;
Value : Value_Type) return Accum_Type;
But I guess this argument doesn't hold up if we are talking about the
procedure case. So maybe this doesn't go without saying.
In general, we don't want to open the door for something that couldn't have
been written explicitly using explicit loops, object declarations, etc. via a
straightforward equivalence. Saying "we have this type/subtype that is taken
from a subprogram specification and we are going to use it in an object
declaration for an accumulator" is fine for a named entity, but not for an
anonymous one.
And do we really want to have to deal with the case where the initializer
expression is an allocator of an anonymous access type whose designated type
has whatever heartburn-inducing properties you care to imagine? Or a call to
a function with an anonymous access result type having a similar designated
type?
Ditto for anonymous value types. Do we really want to spend time on
static/dynamic accessibility issues regarding actual parameters of implicit
calls when the value type for a Reducer subprogram is an anonymous access
type? Perhaps the case is weaker here, since I think the equivalent explicit
expansion doesn't involve generating any names that denote an existing
anonymous type. Nonetheless, a value sequence whose value type is anonymous
seems to me like a new (and therefore undesirable) use of anonymous access
types.
===
I agree that an AARM note would be sufficient to clarify that the accumulator
can inherit tag/constraint stuff from the initial value if the accumulator
subtype is indefinite.
===
Regarding the problem with nominal subtypes and the zero-iteration case, I
agree that your solution
> This probably should be augmented with something like "converted to
> Accum_Type" or to say it "yields the initial value assigned to the
> accumulator."
addresses the problem.
****************************************************************
From: Steve Baird [Privately]
Sent: Friday, October 22, 2021 7:01 PM
> This probably should be augmented with something like "converted to
> Accum_Type" or to say it "yields the initial value assigned to the
> accumulator."
That also would have the advantage of removing any ambiguity about what
happens when the initial assignment to the accumulator involves array
bound sliding.
****************************************************************
From: Tucker Taft [Privately]
Sent: Friday, October 22, 2021 8:58 PM
...
> But I guess this argument doesn't hold up if we are talking about the
> procedure case. So maybe this doesn't go without saying.
An anonymous access type parameter cannot be of mode "in out" so the
procedure case wouldn't allow for that. I certainly don't see any
particular value to allowing "access T" but we should probably explicitly
disallow it. Saying that two anonymous access subtypes are of different
types is not particularly meaningful. What is relevant is whether they are
statically matching subtypes.
> In general, we don't want to open the door for something that couldn't have
> been written explicitly using explicit loops, object declarations, etc. via
> a straightforward equivalence. Saying "we have this type/subtype that is
> taken from a subprogram specification and we are going to use it in an object
> declaration for an accumulator" is fine for a named entity, but not for an
> anonymous one.
I am not sure I am following you here. Are you saying indefinite subtypes
should not be allowed? The accumulator is initialized by the initial value,
so I don't see a problem there.
> And do we really want to have to deal with the case where the initializer
> expression is an allocator of an anonymous access type whose designated type
> has whatever heartburn-inducing properties you care to imagine?
> Or a call to a function with an anonymous access result type having a similar
> designated type?
I am convinced, but I think we just need to explicitly disallow aonymous access
types, not try to argue that they are illegal for some obscure reason.
> Ditto for anonymous value types.
>
> Do we really want to spend time on static/dynamic accessibility
> issues regarding actual parameters of implicit calls when the value type for
> a Reducer subprogram is an anonymous access type?
> Perhaps the case is weaker here, since I think the equivalent explicit
> expansion doesn't involve generating any names that denote an existing
> anonymous type. Nonetheless, a value sequence whose value type is anonymous
> seems to me like a new (and therefore undesirable) use of anonymous access types.
I agree the case is weaker, but simplest is to disallow anonymous access types
for both the Accum_Type and the Value_Type.
===
> Regarding the problem with nominal subtypes and the zero-iteration case, I
> agree that your solution
>> This probably should be augmented with something like "converted to
>> Accum_Type" or to say it "yields the initial value assigned to the
>> accumulator."
> addresses the problem.
OK.
****************************************************************
From: Tucker Taft [Privately]
Sent: Friday, October 22, 2021 8:59 PM
> That also would have the advantage of removing any ambiguity about what
> happens when the initial assignment to the accumulator involves array
> bound sliding.
Good point.
****************************************************************
From: Randy Brukardt [Privately]
Sent: Friday, October 22, 2021 9:06 PM
> Ok, I was confused - Accum_Type is a subtype, not a type. So a lot of my
> message was noise.
I wondered if you were really confused, since resolution is usually about
types. But 4.5.10(11/5) does indeed require *subtype* conformance of the
reducer subprograms - in particular this means that for the function case,
the parameter and result have to be the same subtype.
However, that doesn't apply to the initial value nor the expression of the
iterated_element_association; those use expected type. Some sort of conversion
needs to be applied to both of those, else they won't match the nominal
subtype.
===
...
> But I guess this argument doesn't hold up if we are talking about the
> procedure case. So maybe this doesn't go without saying.
Correct. The expected type resolution for other things will properly handle
anonymous types.
> In general, we don't want to open the door for something that couldn't have
> been written explicitly using explicit loops, object declarations, etc. via
> a straightforward equivalence. Saying "we have this type/subtype that is
> taken from a subprogram specification and we are going to use it in an object
> declaration for an accumulator" is fine for a named entity, but not for an
> anonymous one.
I'm not sure I agree with this, at least if anonymous access is really the
first-class sort of thing that it is supposed to be. You certainly can write
an equivalent loop (we "fixed" iterators to allow anonymous access types
precisely for this reason).
I can believe that the wording for reduction expressions won't handle it
properly, but that's arguably the fault of the wording and not the language
semantics. OTOH, I'd just as soon anonymous access didn't exist at all, so I
certainly don't care that much.
> And do we really want to have to deal with the case where the initializer
> expression is an allocator of an anonymous access type whose designated type
> has whatever heartburn-inducing properties you care to imagine? Or a call to
> a function with an anonymous access result type having a similar designated type?
I don't want to deal with those in ANY context. :-)
> Ditto for anonymous value types. Do we really want to spend time on
> static/dynamic accessibility issues regarding actual parameters of implicit
> calls when the value type for a Reducer subprogram is an anonymous access
> type? Perhaps the case is weaker here, since I think the equivalent explicit
> expansion doesn't involve generating any names that denote an existing
> anonymous type. Nonetheless, a value sequence whose value type is anonymous
> seems to me like a new (and therefore undesirable) use of anonymous access types.
Shrug. Doesn't seem worse than any other loop.
===
> Regarding the problem with nominal subtypes and the zero-iteration case, I
> agree that your solution
>> This probably should be augmented with something like "converted to
>> Accum_Type" or to say it "yields the initial value assigned to the
>> accumulator."
> addresses the problem.
Correct, a conversion is necessary (else the nominal subtype wouldn't be
respected). It seems that might be necessary for the expression of a value
sequence, if that can get returned somehow (one presumes that if it gets
passed to a reducer subprogram, parameter passing will do the appropriate
conversion -- perhaps all that is needed is an AARM note that a check is
needed on passing to the reducer since the value may not belong to the
Value_(sub)Type.
****************************************************************
From: Stephen Baird [Privately]
Sent: Friday, October 22, 2021 10:53 PM
> Are you saying indefinite subtypes should not be allowed?
No, I'm only agreeing that (one way or another) we want to disallow anonymous
access types.
> ... simplest is to disallow anonymous access types for both the Accum_Type
> and the Value_Type.
Agreed.
****************************************************************
From: Randy Brukardt [Privately]
Sent: Friday, November 12, 2021 7:47 PM
A couple of thoughts on some ancient mail (mostly so this appears in the
!appendix of the AI):
...
>I say Accum_Type cannot be an anonymous access type (at least in the
>function case) because in a case like
> function Reducer(Accumulator : access Integer;
> Value : Value_Type) return access Integer; the two
>access types are not the same type, so there is no way that it can be
>subtype conformant with any profile of the form
> function Reducer(Accumulator : Accum_Type;
> Value : Value_Type) return Accum_Type;
Subtype conformance of anonymous access types is structural; if it wasn't,
you could never have a spec for a subprogram with an anonymous access
parameter, because it would never conform to the body (as you say, those are
separate types). Tucker said something about this, but it was garbled and I
failed to understand what he was driving at.
...
> In general, we don't want to open the door for something that couldn't
> have been written explicitly using explicit loops, object
> declarations, etc. via a straightforward equivalence. Saying "we have
> this type/subtype that is taken from a subprogram specification and we
> are going to use it in an object declaration for an accumulator" is
> fine for a named entity, but not for an anonymous one.
I don't see anything in the current wording that truly reflects the model
that we've used the Accum_(sub)Type as the subtype of an object declaration.
(Aside: Accum_Type is a terrible name for a subtype!! One has to stick
"subtype" in front of it every time you care about the subtype properties).
The wording simply says that it "initializes the accumulator", nothing is
said about the subtype of the accumulator object or even if there is such an
object. Moreover, it just says that it is initialized to the initial value
(with more words than that). There's nothing about any subtype in sight, or
check, or anything.
Accum_Type is never tied to the accumulator object at all; it's used in
various other ways, but not there. Admittedly, the accumulator object has
to have that type (but not necessarily subtype) in order for the Dynamic
Semantics to mean anything at all, but we shouldn't be assuming that.
I brought up this point after you guys were mostly done discussing this topic,
and nobody paid any attention to me. :-) There are no subtype checks mentioned
anywhere in the RM text. The proposed AARM note makes sense, but the normative
model needs to be clearer as well.
I took a stab at adding some of that topic to the AI as I updated it, but I
may have missed by quite a bit.
...
> Do we really want to spend time on static/dynamic accessibility issues
> regarding actual parameters of implicit calls when the value type for
> a Reducer subprogram is an anonymous access type?
This reminded me that aliased parameters also bring up such considerations. We
banned them from user-defined literal functions for this reason. Do we need to
do that here as well?
Answering my own question: No, because "subtype conformance" requires "aliased"
to match, and the given profiles do not have aliased parameters. So a
subprogram with an aliased parameter can never be subtype conformant with
those profiles. When we use normal resolution only, we don't get that for free.
One could probably have used a similar argument to note that anonymous access
types aren't allowed here, although its more nebulous. Accum_Type as described
in this profile is clearly a named subtype (as that's all that allowed in a
profile), and one cannot name anonymous "access T". ("subtype S is access T;"
is not a legal declaration). As such, you are comparing a named subtype to an
anonymous access, and that cannot match. Still, it's probably best to just say
that is illegal, and then we don't have to come up with arguments as to why it
is not allowed.
****************************************************************
Editor's note: We have a couple of Editorial Review fixes to the wording as
approved by ARG meeting #62M.
First, the wording of 4.3.5(27/5) adds an additional comma, which makes
parsing the sentence hard (and possibly ambiguous). We rearrange the words
to avoid introducing a comma. From:
The accumulator for a subsequence is initialized to the
first value of the subsequence, { converted to Accum_Subtype,} and
to:
The accumulator for a subsequence is initialized to the {result of
converting the} first value of the subsequence {to Accum_Subtype}, and
Second, 4.3.5(25/5) converts a value to Accum_Subtype and passes it to the
second (value) parameter of the Reducer. The problem here is that second
parameter has Value_Subtype, not Accum_Subtype. For a reducer without the
parallel keyword, these don't even need to be the same type, so the existing
wording makes no sense. Replacing Accum_Subtype with Value_Subtype fixes that.
****************************************************************
Questions? Ask the ACAA Technical Agent