!standard 4.3.3(21) 19-03-18 AI12-0250-1/04 !standard 4.3.3(26) !standard 4.3.3(31) !standard 4.3.5(0) !standard 4.5.10(0) !standard 5.5(4) !standard 5.5(7) !standard 5.5(9/4) !standard 5.5(9.1/4) !standard 5.5(10) !standard 5.5.2(2/3) !standard 5.5.2(10/3) !standard 5.5.2(11/3) !class Amendment 18-01-25 !status Amendment 1-2012 19-03-11 !status ARG Approved 9-0-1 19-03-11 !status work item 18-01-25 !status received 18-01-24 !priority Very_Low !difficulty Easy !subject Iterator Filters !summary The syntax "when " may be used in an iterator to filter out elements which do not satisfy the specified condition. !problem When iterators are used for things like container aggregates and reduction expressions, it is common to want to limit the iteration to a subset of the "raw" container contents, or a subset of the elements of an array, or even a subset of the elements of some numeric sequence. In cases like this, if we were writing a loop, we could just enclose the body of the loop in an "if" statement. However, when the iterator is integrated within an expression like a container aggregate or a reduction expression, an "if" expression doesn't have the right semantics. For example, if you want a set made of all of the odd elements of another container, you would want to write something like the following: S : constant Set := [for E of C when E mod 2 = 1 => E]; That is, you want to *filter* out the elements for which the predicate "E mod 2 = 1" is False. This is useful in other contexts as well, for example in a parallel loop, it clarifies that the elements that don't pass the filter probably won't take any significant amount of computing, so could be ignored for the purposes of determining the timings for the various threads executing the loop. Here are further examples: Odd_Elements_Are_Purple : constant Boolean := (for all E of C when E mod 2 = 1 => Is_Purple(E)); type Pair is record Key : Integer; Val : Integer; end record; A : array (Positive range <>) of Rec := [(Key => 55, Val => 77), (Key => 1, Val => 88)]; M : constant Map := [for E of A when E.Val mod 2 = 1 use E.Key => E.Val]; -- M = [55 => 77]; Note that we already check against the predicates of the discrete_subtype in a loop_parameter_specification or iterated_component_association, so this effectively "and"s the filter condition with the predicates, if any, so we already have a precedent for a discontiguous sequence of elements coming from an iterator. !proposal Allow an iterator to include a filter of the form: "when " which would cause the iterator to bypass the "body" of the loop or the "expression" of the iterated construct when the filter evaluates to False for the given value of the loop parameter. Note that this AI interacts with AI12-0212-1 and other AIs involving iteration. This AI is now written relative to the 202X AARM version published on 20-Feb-2019, known as "Draft 18". Note that when incorporating filters into reduction expressions, it became clear we should revert to the suggested alternative approach of using iterated_element_association rather than iterated_component_association. This had the nice effect of significantly simplifying the wording. We have disallowed the use of "reverse" in one case here, but at this point that is somewhat arbitrary, and could be allowed, since we seem to allow it in container aggregates, and reduction expressions can actually be seen as more like a container aggregate for a sequence of some sort rather than an array aggregate. Originally this AI was written to be relative to rev 1.19 of AI12-0212-1, which in the header is described as "18-11-19 AI12-0212-1/08," so keep that in mind when comparing the wording in this version relative to the original AI. !wording Modify RM 4.3.3(20.3/5-20.b/5): 1. Each iterator_specification is elaborated (in an arbitrary order) and an iteration is performed solely to determine a {maximum} count {for} [of] the number of values produced by the iteration; all of these counts are combined to determine the {maximum} overall length of the array, and ultimately {the limits on} the bounds of the array (defined below); 2. A second iteration is performed for each of the iterator_specifications, in the order given in the aggregate, and for each value produced, the associated expression is evaluated, its value is converted to the component subtype of the array type, and used to define the value of the next component of the array starting at the low bound and proceeding sequentially toward the high bound. A check is made that the second iteration results in [the same} {an} array length {no greater than the maximum determined by the first iteration}; Constraint_Error is raised if this check fails. AARM To Be Honest: Constraint_Error should be raised no later than when the iterations exceed the [expected] {maximum} array length; memory that doesn't belong to the aggregate temporary should not be overwritten. Modify RM 4.3.3(26.2/5): * For a named_array_aggregate containing only iterated_component_associations with an iterator_specification, the lower bound is determined as for a positional_array_aggregate without an others choice, and the upper bound is determined from the lower bound and the total number of values produced by the {second set of iterations} [iteration(s)]; Modify RM 4.3.3(32/5): Implementation Permissions When evaluating iterated_component_associations for an array_aggregate that contains only iterated_component_associations with iterator_specifications, the first step of evaluating a iterated_component_association can be omitted if the implementation can determine the {maximum} number of values by some other means. Modify RM 4.3.5(28/5): For a named_container_aggregate that is an indexed aggregate, all container_element_associations shall contain either a key_choice_list, or a loop_parameter_specification without a /key_/expression {or iterator_filter}. Furthermore, for such an aggregate, either: Replace RM 4.5.10(3/5) with: value_sequence ::= '[' [parallel[(chunk_specification)]] iterated_element_association ']' Replace RM 4.5.10(6/5-6.b/5) with: The iterated_element_association of a value_sequence shall not have a /key_/expression, nor shall it have a loop_parameter_specification that has the reserved word reverse. AARM Reason: The intent is that the syntax matches as closely as possible array or container aggregate notation. Syntax that matches a loop_parameter_specification with the reverse reserved word would not be permitted in an array aggregate, so we disallow that here. [Author's Note: This is somewhat arbitrary, and reverse could be permitted, without any other change to the wording outside this paragraph.] Modify 4.5.10(19/5): * The expected type of the expression {of the iterated_element_assocation} of a value_sequence is that of subtype Value_Type. [Editor's note: A value_sequence doesn't directly have an expression, and a chunk_specification also has an expression, so we better be explicit as to what we're talking about.] Delete RM 4.5.10(20/5). Delete RM 4.5.10(23/5). Modify RM 4.5.10(27/5-28/5): For the evaluation of a value_sequence[: * if the iterated_component_association has an iterator_specification, an iteration is performed for that iterator_specification (as described in 5.5.2)], {the iterated_element_association is elaborated, then an iteration is performed as described in 5.5 or 5.5.2,} and for each value produced by the iterator, the associated expression is evaluated with the loop parameter having this value, to produce a result that is converted to Value_Type, and used to define the next value in the sequence[;]{.} Delete RM 4.5.10(29/5). Replace RM 5.5(4) with: loop_parameter_specification ::= defining_identifier in [reverse] discrete_subtype_definition [iterator_filter] Add after RM 5.5(4): iterator_filter ::= when condition Add before RM 5.5(7/5): An iterator_filter is defined to be /satisfied/ when the condition evaluates to True for a given iteration of a loop_parameter_specification, iterator_specification, or procedural_iterator. The term /conditionally executed/ when referring to the sequence_of_statements of a loop_statement means that the statements are executed only when there is no iterator_filter, or the iterator_filter is satisfied. In contexts where a loop_parameter_specification or iterator_specification is used to produce a sequence of values (see 4.3.3 and 4.3.5), if an iterator_filter is present, the sequence of values will contain only the values for which the iterator_filter is satisfied. Modify RM 5.5(9/5): For the execution of a loop_statement that has an iteration_scheme including a loop_parameter_specification, after elaborating the chunk_specification, if any, the loop_parameter_specification is elaborated. This [elaboration] elaborates the discrete_subtype_definition, which defines the subtype of the loop parameter. If the discrete_subtype_definition defines a subtype with a null range, the execution of the loop_statement is complete. Otherwise, the sequence_of_statements is {conditionally} executed once for each value of the discrete subtype defined by the discrete_subtype_definition that satisfies the predicates of the subtype ... Modify RM 5.5(9.3/5): [Redundant: For details about the execution of a loop_statement with the iteration_scheme including an iterator_specification, see 5.5.2. {For details relating to a procedural_iterator, see 5.5.3.}] Modify RM 5.5(10): 5 A loop parameter {declared by a loop_parameter_specification} is a constant; it cannot be updated within the sequence_of_statements of the loop (see 3.3). Replace RM 5.5.2(2/5) with: iterator_specification ::= defining_identifier [: loop_parameter_subtype_indication] in [reverse] iterator_name [iterator_filter] | defining_identifier [: loop_parameter_subtype_indication] of [reverse] iterable_name [iterator_filter] Modify RM 5.5.2(10/3): ... Otherwise, the sequence_of_statements is {conditionally} executed and then the Next operation of the iterator type is called with the loop iterator and the current value of the loop parameter to produce the next value to be assigned to the loop parameter. ... Modify RM5.5.2(10.2/5): ...Otherwise, the sequence_of_statements is {conditionally} executed and then the Next operation of the iterator type having a Chunk parameter is called, with the loop iterator, the current value of the loop parameter, and the corresponding chunk index, to produce the next value to be assigned to the loop parameter.... Modify RM 5.5.2(11/3): ... Otherwise, the sequence_of_statements is {conditionally} executed with the loop parameter denoting each component of the array for the loop, using a canonical order of components, which is last dimension varying fastest (unless the array has convention Fortran, in which case it is first dimension varying fastest). ... The loop iteration proceeds until the sequence_of_statements has been {conditionally} executed for each component of the array for the loop, or until the loop is left as a consequence of a transfer of control. Replace RM 5.5.3(2/5) with: procedural_iterator ::= iterator_parameter_specification of iterator_procedure_call [iterator_filter] Add to the end of RM 5.5.3(15/5): {The body of P consists of the conditionally executed sequence_of_statements.} AARM Implementation Note: For a procedural_iterator with an iterator_filter, the body of the routine would be something like: procedure P ... is begin if iterator_filter then sequence_of_statements end if; end P; !discussion Some uses of filters are merely "nice to have", but when constructing an aggregate or performing a reduction, it is more important, in that trying to construct a new aggregate (or perform a reduction) from a subset of the elements of an existing container cannot be easily accomplished without something like a filter. For the reduction, it would be possible to use an "if" expression and substitute in the identity of the reduction, but the resulting construct is almost certainly harder to read and understand. But for an aggregate, an "if" expression does not have the right semantics (as mentioned in the !problem above). Note that we do not propose to allow a filter on an array aggregate with a "simple" discrete range such as: [for I in 1..10 => I * 2] This is because the values of the index parameter ("I" in this case) are taken directly as the array indices, and a filter would not make sense -- no holey arrays please! By contrast, when a container iterator is used within an array aggregate, the iterator just produces a sequence of values and the index within the array is determined by position within this sequence of values. For a container aggregate, we have similar restrictions on "indexed" aggregates -- these are effectively user-defined array aggregates and have nearly identical semantics (and restrictions). For non-indexed container aggregates, we always allow filters, since these do not require full coverage over a contiguous key range. Note that we do not define the notion of a "static" filter. There seems less reason to introduce the distinction between "static" and "dynamic" for a filter, analogous to what we have done for predicates, because the filter is explicit syntax in the iterator, and there is no promise that the overall iteration will somehow magically and statically "skip over" the filtered-out elements. The presumption with a filter is that you generate all of the elements of the iteration, and then ignore those filtered out. With a loop over a predicated subtype, it was felt that a loop over a dynamically-predicated subtype might have misleading performance characteristics if there were an assumption we might magically "visit" only the valid elements of the subtype. !corrigendum 4.3.3(21) @drepl The evaluation of an @fa of a given array type proceeds in two steps: @dby For an @fa that contains only @fas that are @fas with @fas, evaluation proceeds in two steps: @xhang<@xterms<1.>Each @fa is elaborated (in an arbitrary order) and an iteration is performed solely to determine a maximum count for the number of values produced by the iteration; all of these counts are combined to determine the overall length of the array, and ultimately the limits on the bounds of the array (defined below);> @xhang<@xterms<2.>A second iteration is performed for each of the @fas, in the order given in the @fa, and for each value produced, the associated @fa is evaluated, its value is converted to the component subtype of the array type, and used to define the value of the next component of the array starting at the low bound and proceeding sequentially toward the high bound. A check is made that the second iteration results an in array length no greater than the maximum determined by the first iteration; Constraint_Error is raised if this check fails.> The evaluation of any other @fa of a given array type proceeds in two steps: !corrigendum 4.3.3(26) @dinsa @xbullet (or equivalent @fa) without an @b choice, the lower bound is that of the corresponding index range in the applicable index constraint, if defined, or that of the corresponding index subtype, if not; in either case, the upper bound is determined from the lower bound and the number of @fas (or the length of the @fa);> @dinss @xbullet, bounds for each dimension are determined as for a @fa without an @b choice with zero expressions for each dimension;> @xbullet containing only @fas with an @fa, the lower bound is determined as for a @fa without an @b choice, and the upper bound is determined from the lower bound and the total number of values produced by the second set of iterations;> !corrigendum 4.3.3(31) @dinsa The exception Constraint_Error is raised if any of the above checks fail. @dinst @s8<@i> When evaluating @fas for an @fa that contains only @fas with @fas, the first step of evaluating a @fa can be omitted if the implementation can determine the maximum number of values by some other means. !corrigendum 4.3.5(0) @dinsc A fake to force a conflict; the actual text is in the conflict file. !corrigendum 4.5.10(0) @dinsc A fake to force a conflict; the actual text is in the conflict file. !corrigendum 5.5(4) @drepl @xindent<@fa@fa<@ ::=@ >@hr @ @ @ @fa @b [@b] @fa> @dby @xindent<@fa@fa<@ ::=@ >@hr @ @ @ @fa @b [@b] @fa@hr @ @ @ @ @ [@fa]> @xindent<@fa@fa<@ ::=@ >@b @fa> !corrigendum 5.5(7) @drepl For the execution of a @fa, the @fa is executed repeatedly, zero or more times, until the @fa is complete. The @fa is complete when a transfer of control occurs that transfers control out of the loop, or, in the case of an @fa, as specified below. @dby An @fa is defined to be @i when the @fa evaluates to True for a given iteration of a @fa, @fa, or @fa. The term @i when referring to the @fa of a @fa means that the statements are executed only when there is no @fa, or the @fa is satisfied. In contexts where a @fa or @fa is used to produce a sequence of values (see 4.3.3 and 4.3.5), if an @fa is present, the sequence of values will contain only the values for which the @fa is satisfied. For the execution of a @fa, the @fa is executed zero or more times, until the @fa is complete. The @fa is complete when a transfer of control occurs that transfers control out of the loop, or, in the case of an @fa, as specified below. !corrigendum 5.5(9/4) @drepl For the execution of a @fa with the @fa being @b @fa, the @fa is first elaborated. This elaboration creates the loop parameter and elaborates the @fa. If the @fa defines a subtype with a null range, the execution of the @fa is complete. Otherwise, the @fa is executed once for each value of the discrete subtype defined by the @fa that satisfies the predicates of the subtype (or until the loop is left as a consequence of a transfer of control). Prior to each such iteration, the corresponding value of the discrete subtype is assigned to the loop parameter. These values are assigned in increasing order unless the reserved word @b is present, in which case the values are assigned in decreasing order. @dby For the execution of a @fa that has an @fa including a @fa, after elaborating the @fa, if any, the @fa is elaborated. This elaborates the @fa, which defines the subtype of the loop parameter. If the @fa defines a subtype with a null range, the execution of the @fa is complete. Otherwise, the @fa is conditionally executed once for each value of the discrete subtype defined by the @fa that satisfies the predicates of the subtype (or until the loop is left as a consequence of a transfer of control). Prior to each such iteration, the corresponding value of the discrete subtype is assigned to the loop parameter associated with the given iteration. If the loop is a parallel loop, each chunk has its own logical thread of control with its own copy of the loop parameter; otherwise (a @i), a single logical thread of control performs the loop, and there is a single copy of the loop parameter. Each logical thread of control handles a distinct subrange of the values of the subtype of the loop parameter such that all values are covered with no overlaps. Within each logical thread of control, the values are assigned to the loop parameter in increasing order unless the reserved word @b is present, in which case the values are assigned in decreasing order. !corrigendum 5.5(9.1/4) @drepl For details about the execution of a @fa with the @fa being @b @fa, see 5.5.2. @dby For details about the execution of a @fa with the @fa including an @fa, see 5.5.2. For details relating to a @fa, see 5.5.3. !corrigendum 5.5(10) @drepl @xindent<@s9<5 A loop parameter is a constant; it cannot be updated within the @fa of the loop (see 3.3).>> @dby @xindent<@s9<5 A loop parameter declared by a @fa is a constant; it cannot be updated within the @fa of the loop (see 3.3).>> !corrigendum 5.5.2(2/3) @drepl @xindent<@fa@fa<@ ::=@ >@hr @ @ @ @ @fa@ @b@ [@b]@ @i@fa@hr @ @ |@ @fa@ [:@ @fa]@ @b@ [@b]@ @i@fa> @dby @xindent<@fa@fa<@ ::=@ >@hr @ @ @ @ @fa@ [:@ @fa]@ @b@ [@b]@ @i@fa@hr @ @ @ @ @ @ [@fa]@hr @ @ |@ @fa@ [:@ @fa]@ @b [@b]@ @i@fa@hr @ @ @ @ @ @ [@fa]> !corrigendum 5.5.2(10/3) @drepl For a generalized iterator, the loop parameter is created, the @i@fa is evaluated, and the denoted iterator object becomes the @i. In a forward generalized iterator, the operation First of the iterator type is called on the loop iterator, to produce the initial value for the loop parameter. If the result of calling Has_Element on the initial value is False, then the execution of the @fa is complete. Otherwise, the @fa is executed and then the Next operation of the iterator type is called with the loop iterator and the current value of the loop parameter to produce the next value to be assigned to the loop parameter. This repeats until the result of calling Has_Element on the loop parameter is False, or the loop is left as a consequence of a transfer of control. For a reverse generalized iterator, the operations Last and Previous are called rather than First and Next. @dby For a sequential generalized iterator, the loop parameter is created, the @i@fa is evaluated, and the denoted iterator object becomes the @i. In a forward generalized iterator, the operation First of the iterator type is called on the loop iterator, to produce the initial value for the loop parameter. If the result of calling Has_Element on the initial value is False, then the execution of the @fa is complete. Otherwise, the @fa is conditionally executed and then the Next operation of the iterator type is called with the loop iterator and the current value of the loop parameter to produce the next value to be assigned to the loop parameter. This repeats until the result of calling Has_Element on the loop parameter is False, or the loop is left as a consequence of a transfer of control. For a reverse generalized iterator, the operations Last and Previous are called rather than First and Next. For a parallel generalized iterator, the @fa, if any, of the associated parallel construct, is first elaborated, to determine the maximum number of chunks (see 5.5), and then the operation Split_Into_Chunks of the iterator type is called, with the determined maximum passed as the Max_Chunks parameter, specifying the upper bound for the number of loop parameter objects (and the number of logical threads of control) to be associated with the iterator. In the absence of a @fa, the maximum number of chunks is determined in an implementation-defined manner. Upon return from Split_Into_Chunks, the actual number of chunks for the loop is determined by calling the Chunk_Count operation of the iterator, at which point one logical thread of control is initiated for each chunk, with an associated chunk index in the range from one to the actual number of chunks. Within each logical thread of control, a loop parameter is created. If a @fa with a @fa is present in the associated parallel construct, then a chunk parameter is created, and initialized with a value from the discrete subtype defined by the @fa, so that the order of the chosen chunk parameter values correspond to the order of the chunk indices associated with the logical threads of control. The operation First of the iterator type having a Chunk parameter is called on the loop iterator, with Chunk initialized from the corresponding chunk index, to produce the initial value for the loop parameter. If the result of calling Has_Element on this initial value is False, then the execution of the logical thread of control is complete. Otherwise, the @fa is conditionally executed and then the Next operation of the iterator type having a Chunk parameter is called, with the loop iterator, the current value of the loop parameter, and the corresponding chunk index, to produce the next value to be assigned to the loop parameter. This repeats until the result of calling Has_Element on the loop parameter is False, or the associated parallel construct is left as a consequence of a transfer of control. In the absence of a transfer of control, the associated parallel construct of a parallel generalized iterator is complete when all of its logical threads of control are complete. !corrigendum 5.5.2(11/3) @drepl For an array component iterator, the @i@fa is evaluated and the denoted array object becomes the @i. If the array for the loop is a null array, then the execution of the @fa is complete. Otherwise, the @fa is executed with the loop parameter denoting each component of the array for the loop, using a @i order of components, which is last dimension varying fastest (unless the array has convention Fortran, in which case it is first dimension varying fastest). For a forward array component iterator, the iteration starts with the component whose index values are each the first in their index range, and continues in the canonical order. For a reverse array component iterator, the iteration starts with the component whose index values are each the last in their index range, and continues in the reverse of the canonical order. The loop iteration proceeds until the @fa has been executed for each component of the array for the loop, or until the loop is left as a consequence of a transfer of control. @dby For an array component iterator, the @fa of the associated parallel construct, if any, is first elaborated to determine the maximum number of chunks (see 5.5), and then the @i@fa is evaluated and the denoted array object becomes the @i. If the array for the loop is a null array, then the execution of the @fa is complete. Otherwise, the @fa is conditionally executed with the loop parameter denoting each component of the array for the loop, using a @i order of components, which is last dimension varying fastest (unless the array has convention Fortran, in which case it is first dimension varying fastest). For a forward array component iterator, the iteration starts with the component whose index values are each the first in their index range, and continues in the canonical order. For a reverse array component iterator, the iteration starts with the component whose index values are each the last in their index range, and continues in the reverse of the canonical order. For a parallel array component iterator, the iteration is broken up into contiguous chunks of the canonical order, such that all components are covered with no overlaps; each chunk has its own logical thread of control with its own loop parameter and iteration within each chunk is in the canonical order. The number of chunks is implementation defined, but is limited in the presence of a @fa to the determined maximum. The loop iteration proceeds until the @fa has been conditionally executed for each component of the array for the loop, or until the loop is left as a consequence of a transfer of control. Modify RM 5.5.2(11/3): ... Otherwise, the sequence_of_statements is {conditionally} executed with the loop parameter denoting each component of the array for the loop, using a canonical order of components, which is last dimension varying fastest (unless the array has convention Fortran, in which case it is first dimension varying fastest). ... The loop iteration proceeds until the sequence_of_statements has been {conditionally} executed for each component of the array for the loop, or until the loop is left as a consequence of a transfer of control. !corrigendum 5.5.3(0) @dinsc A fake to force a conflict; the actual text is in the conflict file. Add to the end of RM 5.5.3(15/5): {The body of the locally declared procedure P follows this structure: procedure P ... is begin if (iterator_filter is absent or satisfied) then sequence_of_statements end if; end P; !ASIS [Not sure. Probably new routines for new syntax are needed. - Editor.] !ACATS test ACATS B- and C-Tests are needed to check that the new capabilities are supported. !appendix From: Tucker Taft Sent: Wednesday, January 24, 2018 3:45 PM Here is a short writeup of the notion of an iterator "filter" using "when " (don't worry, the '<' '>' are not in the syntax -- they are just the usual meta-notation for grammar non-terminals!). [This is version /01 of this AI - ED.] Florian is apparently swamped, so he encouraged me to take over his AIs in the near term. **************************************************************** From: Randy Brukardt Sent: Wednesday, January 24, 2018 4:39 PM > Allow an iterator to include a filter of the form: "when " > which would cause the iterator to bypass the "body" of the loop or the > "expression" of the iterated construct when the filter evaluates to > False for the given value of the loop parameter. I don't see why loop statement iterators (and exits!) should be special in this regard. This seems more sensible as a general feature that applied to all simple_statements (and loops). If loop I in 1 .. 10 when loop and exit when ; are good, why isn't goto when ; or return Foobar when ; good?? Otherwise, this seems like an ugly hack only needed in one specific case, and generalized somewhat just to make it look less like the hack it is rather than for any real need. (We've written Ada loops for nearly 40 years and I've never missed a special conditional feature!) > We would define a "static filter" as one that obeys essentially the > same rules as a static predicate, roughly that it would be static if > the loop parameter were replaced with a static expression. For > filters that accompany loop_parameter_specifications or > iterated_component_associations, non-static filters would be permitted > only in places where discrete subtypes with dynamic predicates are > permitted. The wording of 3.2.4 essentially puts these rules into terms of nonstatic subtypes (Dynamic_Predicates are not usually mentioned explicitly), so this formulation won't work as written. I can't think of an easy rule that would work as a replacement, but I suppose Tucker can. **************************************************************** From: Tucker Taft Sent: Thursday, January 25, 2018 3:57 AM > Otherwise, this seems like an ugly hack only needed in one specific > case, and generalized somewhat just to make it look less like the hack > it is rather than for any real need. (We've written Ada loops for > nearly 40 years and I've never missed a special conditional feature!) The generalization to loop statements is not the main point here. The critical need for a filter is in a container aggregate, and a reduction expression, though as the reduction expression now seems to be heading toward being just the application of an attribute reference to a container aggregate (where a call on Add_Positional is effectively replaced by a call on the reduction operation), the filter in a container aggregate would work for both. I don't want to kill this idea by over-generalizing it. If we want "when" clauses to apply to other things, fine, lets have a separate AI for that, but here we are talking about applying when clauses to *iterators*, not to loop statements per-se. ... > The wording of 3.2.4 essentially puts these rules into terms of > nonstatic subtypes (Dynamic_Predicates are not usually mentioned > explicitly), so this formulation won't work as written. I can't think > of an easy rule that would work as a replacement, but I suppose Tucker can. Yes, I believe we can harmonize the wording here. **************************************************************** From: Randy Brukardt Sent: Thursday, January 25, 2018 5:19 PM ... > The generalization to loop statements is not the main point here. The > critical need for a filter is in a container aggregate, and a > reduction expression, though as the reduction expression now seems to > be heading toward being just the application of an attribute reference > to a container aggregate (where a call on Add_Positional is > effectively replaced by a call on the reduction operation), the filter > in a container aggregate would work for both. I don't want to kill > this idea by over-generalizing it. If we want "when" > clauses to apply to other things, fine, lets have a separate AI for > that, but here we are talking about applying when clauses to > *iterators*, not to loop statements per-se. I've already made my feelings on that clear. I understand the thinking for container aggregates, but it is evil in all other contexts and as such I would rather do without it completely. (One can usually write a predicate with the same effect, and if you can't, you probably shouldn't be using the aggregate form in the first place.) I was thinking that I could be swayed to support a general across-the-board enhancement, as then the language would be at least consistent. As it is, it is just a gee-gaw with a very specific use and something that should be avoided in all other cases (indeed, the rules you proposed would make it illegal in many other cases, including in for loop statements, which probably is going too far in the other direction; you definitely need to make it illegal in array aggregates if nonstatic but I don't see the need for that in a for loop statement). As it stands, I can't support it as having too limited utility for the extra overhead. Essentially, it should be allowed everywhere or nowhere. **************************************************************** From: Tucker Taft Sent: Wednesday, October 17, 2018 7:42 PM I don't see AI12-0250 (iterator filters) on anybody's list. What is its status? **************************************************************** From: Randy Brukardt Sent: Thursday, October 18, 2018 2:40 PM We didn't discuss it last time, so there was no guidance or obvious need for an update. It had a fairly low priority, if I recall correctly (recall our priority votes of last spring). (Checking on this.) Yup, it's behind "Image for all types", "Marshalling streams (split from the previous), "Declare expressions", "Anonymous functions", "User-defined literals", "Access ownership", and "Bignums" (as well as all of the AIs that are directly associated with the instructions, and practically the "simple" AIs as well). I know that we didn't talk about access ownership or bignums in Lisbon, so we couldn't have discussed 250, either. Of these, I would hope to complete all of them (with the possible exception of "Access ownership"), so I'd expect it to get the top eventually, but whether that will happen in time for it to make it into Ada 2020 is TBD. **************************************************************** From: Tucker Taft Sent: Thursday, December 6, 2018 4:10 PM Here is a version of this AI on iterator filters that includes !wording. [This is version /02 of the AI. - Editor.] **************************************************************** From: Randy Brukardt Sent: Thursday, December 6, 2018 10:32 PM > !standard 5.5(4) 18-12-06 AI12-0250-1/03 I'm not sure what happened to version /02; I don't have one, so that's the number this draft was given. I know that will confuse everyone, sorry. I didn't see any issues, other than removing a few double blanks, before I filed this. But I'm too far behind to read this carefully (and I will continue my voting against - I do not want to have to add 24 new kinds of iteration code generation to my compiler -- nor do I want to make any other implementers do that either). **************************************************************** From: Tucker Taft Sent: Friday, March 01, 2019 9:03 PM I reviewed the "filter" AI, and made quite a number of changes to the wording, taking advantage of the fact that Randy has integrated all of the other relevant AIs into the RM wording. So this AI is now written relative to Draft 18 of the Ada 202X RM, which makes it a lot easier to understand (at least from my perspective). Probably the biggest change that resulted from incorporating filters into all relevant syntax was to tip the scale back in favor of "iterated_element_association" for reduction expressions. Brad had suggested this early on, and in retrospect it was the right approach. Trying to use "iterated_component_association" which was invented for array aggregates turns out to be not as good a fit as "iterated_element_association" which was invented for container aggregates. In many ways, a reduction expression is more like a container aggregate for a sequence of some sort (like a linked list), rather than an array or other indexed structure. Switching to iterator_element_association actually allowed a bunch of the wording for reduction expressions to be deleted or simplified. So without further ado, here is the updated Filter AI. [This is version /03 of the AI - Editor.] **************************************************************** From: Randy Brukardt Sent: Tuesday, March 05, 2019 9:59 PM Editorial fixes: A] Found two places with punctuation inside of quotes. John will flog me over those, so please don't. (They probably were in the previous version, too.) B] A bunch of double spaces after periods. C] You changed some of the syntax to put reserved words in CAPS (mostly at the start), but the bulk of it has the reserved words in lower case. I changed them all into lower case, to be consistent. D] In the AARM Rationale (which isn't a type of AARM note, BTW, you meant "Reason"), you have NOTE: This is somewhat arbitrary, and reverse could be permitted, without any other change to the wording outside this paragraph. This looks like a note to the ARG, not something for the AARM. So it needs to be set off, preferably with [Author's Note: ...] E] You have an AARM note following 5.5.3(15/5): AARM Implementation Note: Any transfer of control out of the sequence_of_statements, presuming the callable entity "C" has Allows_Exit True (see below), would be transformed to a raise of some sort of special exception that only the implementation can handle and can use to implement the desired transfer of control. We decided not to describe an implementation technique in the AARM when we discussed AI12-0189-1. I'm not sure why this has changed. AI12-0189-1 has quite a bit on possible implementations of transfer-of-control, and they are quite a bit more general than this note. Note using an exception here requires passing a code along with the exception to describe the type of exit, which may not work well in some implementations (particularly if using some underlying exception mechanism rather than creating one). In any case, "would be transformed" seems way more prescriptive than usually done in such notes. I changed this to "could be transformed" to allow an implementation to do this some other way should it want to. We'll talk during the meeting about whether this note should even exist (I'd say no, but I'm not going to just rip it out on my own). I'm not a fan of the way 5.5.3(15/5) is presented, it seems rather different than the rest of the paragraph. Another thing to discuss. **************************************************************** From: Tucker Taft Sent: Tuesday, March 05, 2019 10:24 PM Thanks for the review. One comment below. >... >I'm not a fan of the way 5.5.3(15/5) is presented, it seems rather different >than the rest of the paragraph. Another thing to discuss. It seemed useful to show what the implicitly constructed procedure would look like. I agree it is not a typical way of doing things in the RM, but it seemed important here. We rarely construct subprograms on the fly. **************************************************************** From: Randy Brukardt Sent: Tuesday, March 05, 2019 10:44 PM I disagree, mainly because we didn't feel it necessary to show the complicated part of the implicit subprogram, so why show the trivial part? It seems incomplete just showing the if expression, since that is pretty obvious. Every Ada programmer knows what a procedure and an if statement look like, we don't need to show them! We could have just said something like: "The sequence_of_statements is not executed if the filter (if any) is not satisfied." -- which is roughly what is said for all of the other iterators. Anyway, something to talk about. **************************************************************** From: Randy Brukardt Sent: Monday, March 19, 2019 10:44 PM Here are some "for the record" changes to AI12-0250-1. Consider them my Editorial Review. (1) The replaced RM 4.5.10(6/5) starts: The iterated_element_association shall not have a /key_/expression, ... but this never identifies the context of the syntax element. We surely don't want this to apply to *all* iterated_element_associations. So I added "of a value_sequence": The iterated_element_association {of a value_sequence} shall not have a /key_/expression, ... (2) [This isn't caused by this AI, but it's related.] RM 4.5.10(19/5) says: The expected type of the expression of a value_sequence is that of subtype Value_Type. But a value_sequence does not contain an expression, and it is composed of two other constructs that both have an expression. So we better be explicit here: The expected type of the expression {of the iterated_element_assocation} of a value_sequence is that of subtype Value_Type. (3) For some reason the syntax of 5.5.2(2/5) is broken: iterator_specification ::= defining_identifier [: loop_parameter_subtype_indication] in [reverse] iterator_name [iterator_filter] | defining_identifier [: loop_parameter_subtype_indication] of [reverse] iterable_name [iterator_filter] I verified that the old syntax fit fine in the RM, so we certainly don't want breaks at "in" and "of". (Syntax is supposed to show the recommended style). It should read (one does not break syntax or program code that fits in the RM, regardless of the usual line length). iterator_specification ::= defining_identifier [: loop_parameter_subtype_indication] in [reverse] iterator_name [iterator_filter] | defining_identifier [: loop_parameter_subtype_indication] of [reverse] iterable_name [iterator_filter] (4) Tucker added the following to the end of 5.5.3(15/5): The body of the locally declared procedure P follows this structure: procedure P ... is begin if (iterator_filter is absent or satisfied) then sequence_of_statements end if; end P; This seems like overkill; everywhere else it is deemed sufficient to simply say the sequence_of_statements is conditionally executed. Steve often has proposed this sort of thing and Tucker is always the first to suggest that it be put into an AARM note or the bit bucket, so I'm not sure what is different here. Moreover, P is already defined earlier in the paragraph, so there's no reason to repeat all of that. We do need some statement about the body (and we should have already had that statement), but it can be a lot simpler than this: The body of P consists of the conditionally executed sequence_of_statements. And then an AARM Implementation Note: For a procedural_iterator with an iterator_filter, the body of the routine would be something like: procedure P ... is begin if iterator_filter then sequence_of_statements end if; end P; [Note: We can just use "iterator_filter" if it is OK to just use "sequence_of_statements", since both are going to refer to the parameters of P. It evaluates to True if satisfied. I find this note to be rather obvious, but I didn't want to remove the text completely as Editorial Review.] (5) He also has at this same place: AARM Implementation Note: Any transfer of control out of the sequence_of_statements, presuming the callable entity "C" has Allows_Exit True (see below), could be transformed to a raise of some sort of special exception that only the implementation can handle and can use to implement the desired transfer of control. When we discussed this original AI, we couldn't agree as a group on any implementation techniques, so we stripped that discussion from the RM and AARM text (and left some in the AI). Tucker doesn't get to ignore the explicit preference of the group and put this implementation discussion back. I've just completely deleted it. Aside: I note that it is not necessary to use an "unhandlable" exception here, since the only handlers that could cause a problem are inside of the explicit loop body - those in an Allows_Exit routine cannot "eat" an exception, otherwise the aspect has been violated. It's certainly possible to generate code to avoid those handlers when raising the (single) special exception, since the implementation can always know that they are there. In that case, the exception will be handled directly by the iterator handler (the one associated with the loop). Some care needs to be used if there are nested procedural iterators (mainly an issue for ACATS tests, I expect); in such a case, if the exception is targeted to an outer loop, one has to be careful to again avoid any nested handlers. (That is, it's the same as any other raise of the special exception.) We use a technique like this to deal with gotos out of blocks that have handlers, and it seems reasonable to use it here as well. Certainly a lot easier than implementing an others handler that handles all but a small list of exceptions! Point is, there are a number of ways to handle this, and there's no reason to favor one over another in the AARM -- and especially not the most likely need new capabilities for an implementation. ****************************************************************