Version 1.2 of ai12s/ai12-0151-1.txt
!standard 5.5.2(2/3) 15-02-23 AI05-0151-1/01
!standard 5.5.2(5/3)
!standard 5.5.2(7/3)
!class binding interpretation 15-02-20
!status work item 15-02-20
!status received 15-02-13
!priority Medium
!difficulty Easy
!qualifier Omission
!subject Meaning of subtype_indication in array component iterators
!summary
The optional subtype specified in an array component iterator or a container
element iterator must statically match the array component subtype or container
element subtype.
An anonymous access declaration can be specified in an array component iterator
if necessary to match the component subtype.
An optional subtype_indication can be provided with a generalized iterator.
!question
(1) Consider an array component iterator with a specified subtype.
5.5.2(5/3) says that the specified subtype has to cover the component type of
the array object. 5.5.2(7/3) says that the nominal subtype of the loop
parameter is that specified. So consider the following:
type Arr is array (1 .. 10) of Integer;
subtype Short is Integer range 1 .. 10;
Obj : Arr := (2,4,6,8,10,12,14,16,18,20);
for P : Short of Obj loop
case P is
when 1 .. 9 => ...
when 10 => ...
end case; --
end loop;
The nominal subtype of P is Short, so the case statement does not allow any
others clause. However, some of the elements have values outside of this
nominal subtype. Essentially, the language is requiring that the loop
parameter has invalid values, as there is no check to prevent this.
What is the intent here? (The subtype must statically match.)
(2) The syntax for array component iterators is:
defining_identifier [: subtype_indication] of [reverse] iterable_name
However, the syntax for the definition of the component subtype of an array
type includes access_definition. Given our zeal for allowing anonymous access
everywhere, it's odd that we're not allowing it here. Should it be allowed
here? (Yes.)
(3) One of the important features of Ada is that in almost all cases, the type
of an object appears at the place where the object is declared. Thus it is not
necessary to follow chains of declaration to find the type and subtype of an
object.
Traditional for loops is the exception to this feature, as the type of I does
not appear in "for I in 1 .. 10 loop". Here, however, the type is assumed, and
Ada programmers quickly get used to that.
The reason for allowing the loop parameter to be specified for array component
iterators and for component element iterators is to preserve this principle (at
least optionally). Traditional for loops did not need such an option because the
subtype is drawn directly from the given subtype_indiciation - the only thing
that is missing is the colon.
However, in a generalized iterator, the name of the cursor subtype does not
appear anywhere. Again, a search through a chain of declarations is needed to
find the name of this subtype (it will be found as the actual subtype in the
instantiation of Ada.Iterator_Interfaces).
It appears that we need to allow specifying the subtype in the case of a
generalized iterator as well as in the other cases. Should this be done?
(Yes.)
!recommendation
(See Summary.)
!wording
Replace 5.5.2(2/3) with: (Note: square and curly brackets as in syntax, not
wording modifications)
iterator_specification ::=
defining_identifier [: loop_parameter_subtype_indication] in [reverse] iterator_name
| defining_identifier [: loop_parameter_subtype_indication] of [reverse] iterable_name
loop_parameter_subtype_indication ::= subtype_indication | access_definition
Modify 5.5.2(5/3):
{The subtype defined by the subtype_indication, if any, of a generalized iterator
component iterator shall statically match the iteration cursor subtype.
The [type of]{subtype defined by} the subtype_indication, if any, of an array
component iterator shall [cover]{statically match} the component
[type]{subtype} of the type of the iterable_name. The [type of]{subtype defined
by} the subtype_indication, if any, of a container element iterator shall
[cover]{statically match} the default element [type]{subtype} for the type of
the iterable_name.
Modify 5.5.2(7/3) by swapping the second and third sentences and making the other
changes indicated:
An iterator_specification declares a loop parameter. In {a generalized
iterator, }an array component iterator{,} or a container element iterator, if
a {loop_parameter_}subtype_indication is present, it determines the nominal
subtype of the loop parameter. In a generalized iterator, {if a
loop_parameter_subtype_indication is not present, }the nominal subtype of the
loop parameter is the iteration cursor subtype. In an array component iterator,
if a {loop_parameter_}subtype_indication is not present, the nominal subtype of
the loop parameter is the component subtype of the type of the iterable_name. In
a container element iterator, if a {loop_parameter_}subtype_indication is not
present, the nominal subtype of the loop parameter is the default element
subtype for the type of the iterable_name.
!discussion
For question 1, we have essentially three choices:
(A) The subtype of the loop parameter works like that of an object renames; it
is essentially ignored and the component subtype used instead.
(B) The loop parameter is a view conversion of the component, meaning that
there are extra checks when it is created and assigned.
(C) The subtype of the loop parameter has to statically match that of the
component subtype.
(A) is misleading to the reader; the subtype doesn't really mean what it says.
[This is a long-time complaint about renames; compatibility concerns have
prevented us from fixing them, but the consensus is that they're a bad design.]
(B) means that one might get exceptions raised by the loop itself if the wrong
subtype is written by accident. Moreover, there has to be some sort of reverse
check when the component is assigned. In addition, such a check would be
problematical of the components are uninitialized. A loop to initialize an
array such as:
for Comp : Natural of Obj loop
Comp := 10;
end loop;
might raise an exception if the uninitialized junk has a negative value.
(C) is incompatible with the Ada 2012 language as defined. The wording in the
Ada 2012 standard clearly allows constructions such as in the example, but
requiring static matching would render it illegal.
Given the potential for confusion, and the early state of Ada 2012 adoption,
we chose to require static matching. The alternatives either are full of
potential landmines for the language definition and the user (B), or are
misleading to the reader (A) and not really helpful (since any constraints
are ignored, why write them in the first place?).
Container element iterators have similar issues with the subtype name
(question 1) as array component iterators. We adopt the same solution as
for arrays for them.
For question 2, we allow anonymous access types for consistency with other
kinds of object declaration (including extended return statements and generic
formal parameters). Whether we should have done that can be argued, but since
we have, there should not be places where anonymous access types cannot be
written. Notice that static matching will always fail for container element
iterators, as the Iterator_Element has to be a "name", and that does not
include anonymous access types.
For question 3, we allow an optional subtype_indication on a generalized
iterator. We don't want to force readers to look through many declarations
to find out the type of a loop parameter. We allow that type to be optional,
however, as it often is obvious from context. (For instance, if one is
iterating a language-defined container, everyone should know that the
cursor type is named Cursor and it's defined in the instance that defines
the container type.) But that's not true for user-defined iterators, which
may have been added to existing types for which the name "Cursor" is not
appropriate.
We don't need to allow an access_definition in a generalized iterator, as
the actual type provided to any generic instance (including an instance of
Ada.Iterator_Interfaces) is a subtype_mark; it cannot be an anonymous
access_definition. We use loop_parameter_subtype_indication anyway, just to
simplify the wording.
We could have allowed an optional subtype_indication in a traditional for
loop as well. But "in" here acts much like ":", so we don't really need
the extra subtype. And the static matching requirement would mean that
the same subtype would have to be written twice in most circumstances:
for I : Short in Short loop
A charter member of the department of redundancy department. In addition,
it would have required additional wording and syntax changes in subclause
5.5. So this does not seem worthwhile.
!corrigendum 5.5.2(5/3)
Replace the paragraph:
by:
!ASIS
No ASIS effect.
!ACATS test
An ACATS B-Test is needed for the static matching check.
!appendix
From: Randy Brukardt
Sent: Friday, February 13, 2015 4:53 PM
Brad and I have been talking about test objectives for "of" iterators (OK,
formally known as array component iterators and container element iterators).
Brad's questions boiled down to "what is the semantics of the loop parameter"
for "of" iterators.
5.5.2(7/3) says that the nominal subtype of the loop parameter is that
specified.
But that doesn't make much sense. Brad has a test case something like:
type Arr is array (1 .. 10) of Integer;
subtype Short is Integer range 1 .. 10;
Obj : Arr := (2,4,6,8,10,12,14,16,18,20);
for P : Short of Obj loop
case P is
when 1 .. 9 => ...
when 10 => ...
end case; -- Legal, no others needed or allowed.
end loop;
The problem here is that some of the components of the array have values outside
of the nominal subtype of the loop parameter P. Thus the case expression will
raise Constraint_Error. The programmer cannot write an Others here, even if they
wanted to.
Brad thought that GNAT was essentially ignoring the specified subtype in favor
of the actual component type. That would be something like the way renames work.
But I think we all agree that the way renames work is nasty behavior; and it
clearly is different than what the wording of 5.5.2 says.
In general, in cases like this, we would require the named subtype to statically
match the component type (at least for elementary types; we might want a weaker
rule for tagged types). That would avoid the problem of different constraints.
Part of the problem I have with this is that I have no idea what we were trying
to do by providing this optional subtype. It's not at all clear to me why just
using the component subtype (or element subtype) isn't good enough.
Here's what the AI (AI12-0139-2) has to say on that topic:
The subtype_indication is optional, since the Iterator_Element aspect of the
container type determines the element type. Similarly for an array, the element
type is simply the array component type. One purpose of the subtype_indication
might be to tighten the requirements on the elements of the container/array,
acting essentially as an assertion about the values stored in the
container/array at the time of the iteration. Another purpose is simply to help
the reader, and provide an extra check that the Iterator_Element type of the
container is as expected.
That suggests that it is intended that the constraints on the component type
could be tightened (or loosened). But that would also imply that there needs to
be a runtime check that the loop parameter actually has the specified subtype.
And there's no such check in the dynamic semantics of 5.5.2. It wouldn't make
sense to *require* the loop parameter to be invalid in some circumstances, but
that's what happens without a mandated check.
So maybe we need to add something like: "If the array component iterator
includes a subtype_indication, each element is view-converted to that subtype."
to 5.5.2(11/3) and 5.5.2(12/3). Note that would have to be checked in both
directions, if you swap Short and Integer in the above example, you could assign
a component value of 12 to an array that should only hold values of 1..10.
So, are we missing a runtime check here? Or should we require static matching?
Or should we treat it like a renames and ignore the constraints completely??
****************************************************************
From: Bob Duff
Sent: Friday, February 13, 2015 5:20 PM
> In general, in cases like this, we would require the named subtype to
> statically match the component type
Static matching seems like the right answer to me.
>... (at least for elementary types; we might want a weaker rule for
>tagged types).
Why should tagged types be different?
> Part of the problem I have with this is that I have no idea what we
> were trying to do by providing this optional subtype. It's not at all
> clear to me why just using the component subtype (or element subtype) isn't
> good enough.
I think it aids readability to put the type there. In fact, I once suggested it
should be required. People (Tucker?) told me that was a dumb idea, and I guess
I now agree, but I still like the option of putting the type. So long as it
doesn't introduce exceptions when I mistakenly use the wrong subtype, that is.
> Here's what the AI (AI12-0139-2) has to say on that topic:
>
> The subtype_indication is optional, since the Iterator_Element aspect
> of the container type determines the element type. Similarly for an
> array, the element type is simply the array component type. One
> purpose of the subtype_indication might be to tighten the requirements
> on the elements of the container/array, acting essentially as an
> assertion about the values stored in the container/array at the time
> of the iteration. Another purpose is simply to help the reader, and
> provide an extra check that the Iterator_Element type of the container is
> as expected.
>
> That suggests that it is intended that the constraints on the
> component type could be tightened (or loosened).
Yes, it does. I'm surprised. It just seems like a tripping hazard.
If you want a run-time check, just put:
pragma Assert (P in Short);
in the loop.
****************************************************************
From: Steve Baird
Sent: Friday, February 13, 2015 5:48 PM
> So, are we missing a runtime check here? Or should we require static
> matching? Or should we treat it like a renames and ignore the
> constraints completely??
Those do seem to be the choices.
I don't think the runtime check works well. For example if you are iterating
over an uninitialized array of Naturals with the goal of initializing each
element, a runtime check wouldn't be very helpful.
And, in the case of a variable, just because an element passes the runtime check
at one point doesn't mean it will stay in the desired subtype for the remainder
of that iteration of the loop.
So the runtime check solution doesn't really work (and it would probably be
error-prone if it did).
As you pointed out, the AI says
> One purpose of the
> subtype_indication might be to tighten the requirements on the
> elements of the container/array, acting essentially as an assertion
> about the values stored in the container/array at the time of the iteration.
It looks like when that was discussed we forgot that we are (or may be) dealing
with variables, not constants.
If we wanted to have different dynamic semantics for iterating over constants
vs. over variables, I suppose the runtime check would work in the constant case.
That seems like a very bad idea.
As you pointed out
> But I think we all agree that the way renames work is nasty behavior
So that leaves us with static matching.
Bob Duff wrote:
> Static matching seems like the right answer to me.
I agree.
****************************************************************
From: Gary Dismukes
Sent: Friday, February 13, 2015 5:51 PM
> > In general, in cases like this, we would require the named subtype
> > to statically match the component type
>
> Static matching seems like the right answer to me.
Seems that way to me as well.
> >... (at least for elementary types; we might want a weaker rule for
> >tagged types).
>
> Why should tagged types be different?
That's not clear to me either. Static matching also seems right for that case.
****************************************************************
From: Randy Brukardt
Sent: Friday, February 13, 2015 6:23 PM
...
> > >... (at least for elementary types; we might want a weaker rule
> > >for tagged types).
> >
> > Why should tagged types be different?
>
> That's not clear to me either. Static matching also seems right for
> that case.
The fact that the type requirement was written in terms of "covers" rather than
just "the same" suggests to me that the intent was to allow classwide types
(essentially a wider constraint) in the case of specific tagged types. I don't
think T'Class statically matches T.
If we're convinced that that whole idea is nonsense (and it does sound a lot
like nonsense, especially after Steve's points about the infeasability of a
runtime check), then static matching is the answer. Probably should dump the
"covers" wording in that case as well, as it just would appear to promise more
than possible.
Maybe just replace 5.5.2(5/3) with:
The type of the subtype_indication, if any, of an array component iterator shall
statically match the component subtype of the type of the iterable_name. The
type of the subtype_indication, if any, of a container element iterator shall
statically match the default element subtype for the type of the iterable_name.
****************************************************************
From: Randy Brukardt
Sent: Friday, February 13, 2015 6:31 PM
...
> > Part of the problem I have with this is that I have no idea what we
> > were trying to do by providing this optional subtype. It's not at
> > all clear to me why just using the component subtype (or element
> > subtype) isn't good enough.
>
> I think it aids readability to put the type there. In fact, I once
> suggested it should be required. People (Tucker?) told me that was a
> dumb idea, and I guess I now agree, but I still like the option of
> putting the type. So long as it doesn't introduce exceptions when I
> mistakenly use the wrong subtype, that is.
Right. But of course then I wonder why we didn't put it on "in" iterators as
well. It would just be noise for the normal discrete iterator in almost all
cases, but for the cursor version, the cursor name isn't in the syntax anywhere.
for I : Short in Short loop -- Pretty silly.
for I : Short in 1 .. 10 loop -- Still silly, should have used the
-- above (if you have a name for something, use it)
for I : Cursor in Container.Iterator loop -- Helps here, but not allowed.
for I : Short of Obj loop -- Helps here, and is allowed.
****************************************************************
From: Brad Moore
Sent: Friday, February 13, 2015 7:10 PM
To me, there is a good enough of a reason for limiting this syntax to "of"
loops.
It reads like a declaration that describes the type of I. Inside the loop, I
refers to an object of that type.
In "in" loops however, the type of I is an index or iterator. Having a subtype
in the syntax for that type of loop would be confusing I would think, since
declarations use the colon to separate the declaration name from its type.
The subtype on the "of" loop also helps to better distinguish these two kinds of
loops. So I can imagine a coding style might want the subtypes to be explicit,
as otherwise the two letter difference between "of" and "in" might be considered
to be subtle.
****************************************************************
From: Brad Moore
Sent: Friday, February 13, 2015 8:58 PM
I missed that you were thinking for "in" loops that the subtype indication could
also have been used to describe the type of I in these examples (rather than the
component of the array or container element). I agree that the benefits of
specifying the subtype indication for the container cursor case would have been
similar to the benefits for "of" loops.
****************************************************************
From: Tucker Taft
Sent: Friday, February 13, 2015 9:27 PM
I agree with static matching. The current wording using "covers" is weird. I
don't know what I was/we were thinking.
> ...
> Maybe just replace 5.5.2(5/3) with:
>
> The type of the subtype_indication, if any, of an array component
> iterator shall statically match the component subtype of the type of
> the iterable_name. The type of the subtype_indication, if any, of a
> container element iterator shall statically match the default element
> subtype for the type of the iterable_name.
The above doesn't really make sense. A "type" can't statically match a subtype.
I think you will have to change this to say "the subtype defined by the
subtype_indication" rather than "the type of the subtype_indication."
****************************************************************
From: Randy Brukardt
Sent: Friday, February 13, 2015 10:00 PM
> The above doesn't really make sense. A "type" can't statically match
> a subtype. I think you will have to change this to say "the subtype
> defined by the subtype_indication"
> rather than "the type of the subtype_indication."
Steve noted that privately (not sure why privately). I thought I'd changed all
of the "type"s to "subtype"s and obviously missed the most important one.
---
In the who cares? category, it strikes me that we have
[: subtype_indication]
for array component iterators, but we don't have
[: access_definition]
as most of the similar places do. I would have expected that we'd allow anything
here that could be an array component subtype, and we do allow anonymous access
there. (Why we thought that was good idea escapes me, but whatever. :-)
Alternatively, we could use "component_definition" there (which is really a
subtype); that would also allow "aliased". Probably overkill, though. (And we'd
need to match "aliased" to the component subtype.)
****************************************************************
From: Bob Duff
Sent: Saturday, February 14, 2015 9:55 AM
> > I think it aids readability to put the type there. In fact, I once
> > suggested it should be required. People (Tucker?) told me that was
> > a dumb idea, and I guess I now agree, but I still like the option of
> > putting the type. So long as it doesn't introduce exceptions when I
> > mistakenly use the wrong subtype, that is.
>
> Right. But of course then I wonder why we didn't put it on "in"
> iterators as well.
I don't think that would be useful.
>... It would just be noise for the normal discrete iterator in almost
>all cases, but for the cursor version, the cursor name isn't in the
>syntax anywhere.
>
> for I : Short in Short loop -- Pretty silly.
Agreed.
> for I : Short in 1 .. 10 loop -- Still silly, should have used the
> above (if you have a name for something, use it)
Not sure why this is silly, but we already have this:
for I in Short range 1 .. 10 loop
(By the way, I never say "for I in 1 .. 10" unless I is unused, so it's just a
"do this 10 times" loop.)
> for I : Cursor in Container.Iterator loop -- Helps here, but not
> allowed.
I don't think it helps here. Of course it's a cursor, tell me something I don't
know. ;-)
> for I : Short of Obj loop -- Helps here, and is allowed.
Here's where it helps. It's a reminder that Obj is a container of Shorts. This
seems entirely different from the previous "Cursor" example -- I don't need a
reminder that iterators have cursors.
****************************************************************
From: Randy Brukardt
Sent: Sunday, February 15, 2015 7:34 PM
...
> > for I : Cursor in Container.Iterator loop -- Helps here, but not allowed.
>
> I don't think it helps here. Of course it's a cursor, tell me
> something I don't know. ;-)
Sure it is a cursor (lower case), but since this is user-defined, we don't know
the name of the type in question. It's "Cursor" for a language-defined
container, but iterators are useful for many other types. And many of those
types are pre-existing, so they're almost certainly *not* named "Cursor". In my
various programs, for instance, I can imagine iterators where the cursor would
be types Symbol_Ptr, Domain_Index, and Menu_Item_Type.
In most other cases in Ada, all you need to do to find the type of something is
to find its declaration, but that doesn't work for I in this case. You need
three lookups to find the name for an array component iterator (find the loop to
find the object, find the object declaration to find the array type, find the
array type declaration to find the component type), and thus it makes sense to
optionally allow it in the iterator (which eliminates two of these steps).
The same is true for a generalized iterator: find the loop to find the iterator
expression, which typically is a function call, so you have to find the function
to find the iterator type, then you have to find the instantiation that defines
that iterator type - which finally will give you the name of the cursor type.
I suppose if you never use a generalized iterator for anything other than a
container, you'd be right, but I hope our language design is more general than
that. After all, when this was first proposed, I recall one B. Duff wanting to
use it for lazy iterations over a lazy list type. I doubt that the elements in
that type would be identified by type Cursor. And surely you don't mean that
anyone writing an iterator has to name their type Cursor, even when adding an
iterator to an existing package.
****************************************************************
From: Jean-Pierre Rosen
Sent: Tuesday, February 17, 2015 3:17 AM
Going over a long backlog of discussions...
Le 13/02/2015 23:51, Randy Brukardt a ‚crit :
> Part of the problem I have with this is that I have no idea what we
> were trying to do by providing this optional subtype. It's not at all
> clear to me why just using the component subtype (or element subtype) isn't
> good enough.
I always claim that a nice feature of Ada is that (except for "for I in 1..10")
the type of an object always appears at the place where the object is created,
avoiding to follow chains of declaration. I think the same idea applies here.
About what to do with the subtype:
My initial feeling was to have the check, because I viewed the parameter as an
assignment, while it is really a renaming of the component. It is therefore
possible to give a value outside the parameter's declared subtype by direct
assignment to the array component. Since static matching is better than lying, I
concur for static matching.
****************************************************************
Questions? Ask the ACAA Technical Agent