!standard 4.1(2) 11-06-09 AI05-0139-2/13 !standard 4.1.5(0) !standard 4.1.6(0) !standard 5.5(3) !standard 5.5(9) !standard 5.5.1(0) !standard 13.3.2(9) !class Amendment 09-02-13 !status Amendment 2012 11-06-09 !status ARG Approved (by Letter Ballot) 9-0-2 11-05-16 !status work item 11-04-26 !status ARG Approved 8-0-0 11-03-17 !status work item 09-02-13 !status received 09-01-19 !priority Medium !difficulty Medium !subject Syntactic sugar for accessors, containers, and iterators !summary (See proposal.) !problem Using an iterator over one of the Ada 2005 containers libraries requires writing a subprogram which gets called repeatedly with each element of the container. That effectively requires writing the loop body out-of-line from the loop, making the code inside-out of its normal flow, requiring the writing of a lot of extra text, and thus making it much harder to read and understand. A better mechanism is required. In addition, using the notion of "accessors" or "references" as defined in AI05-0142 is syntactically awkward. Finally, it is often clearer to iterate over the elements of a container (or array) directly, rather than iterating over a cursor (or index) into the container (or array). Using accessors for user-defined dereference (rather than simple access values as proposed in AI05-141) has the potential to provide a safer mechanism that might be used for persistence, relocating garbage collection, detection of dangling references, etc. However, using accessors would be syntactically painful compared to a simple ".all." In all these cases, a modest amount of "syntactic sugar" might make the use of accessors, containers, and iterators more natural and easier to read. !proposal Provide a generic unit containing two iterator interfaces, and some additional "aspects" related to implicit dereference and indexing, along with some appropriate syntactic sugar for accessors, containers, and iterators. We also propose providing iterators for arrays, including multi-dimensional arrays. As part of that, we use the notion of canonical order for multidimensional arrays established for Stream attributes, but modify it to take into account convention Fortran, where the first, rather than the last, dimension varies fastest. 1) SUPPORT FOR ACCESSORS/REFERENCES We propose to add an aspect "Implicit_Dereference" which specifies an access discrminant of a discriminated type. Any type with an Implicit_Dereference aspect is considered a "reference" type, and provides implicit dereference in all contexts, with the "implicit dereference" of an object Ref of such a type being equivalent to "Ref..all," where "" is the name of the specified access discriminant. If the access discriminant is an access constant, then of course this dereference is a constant view. If not, then it is a variable view, and can be used on the left-hand-side of an assignment, as the actual for an [in] out parameter, etc. These reference types are particularly useful because they can be controlled types or have a controlled part, so that when finalized, a user-defined finalization action will be invoked. This allows, for example, the "tampering" state of a container to be reset, a persistent object to be "unpinned," a reference count to be decremented, etc. Here is an example of use. We imagine type T and a type T_Table which is some kind of container with T elements in it, indexed by a string. with Ada.Finalization; package Whatever is type T is private; Null_T : constant T; -- default initial value for T function New_T(...) return T; type T_Table is tagged limited private; type Ref_T(Data : access T) is new Ada.Finalization.Limited_Controlled with ... with Implicit_Dereference => Data; -- This type is now a "reference" type procedure Enter(Tab : in out T_Table; Key : String; Value : T); -- Add the given T Value to the T_Table Tab. function Lookup_Or_Create(Tab : access T_Table; Key : String) return access T; -- Return a pointer to an element of the table with the given Key. -- If it doesn't exist, add a default-initialized value of type -- T (i.e. Null_T) using the given Key. function Element(Tab : access T_Table; Key : String) return Ref_T; -- Return a "reference" to an element of the table private ... end Whatever; package body Whatever is function Element(Tab : access T_Table; Key : String) return Ref_T is -- Return a "reference" to an element of the table begin -- "Wrap" the pointer in a reference object return Ref_T'(Data => Lookup(Tab, Key)); end Element; ... procedure Do_Something(Tab : access T_Table) is -- Illustrate the implicit dereference -- of the objects of type Ref_T returned -- by function Element. begin Tab.Element("Apple") := New_T(...); if Tab.Element("Banana") = Null_T then Tab.Element("Banana") := New_T(...); end if; ... end Do_Something; My_Tab : aliased T_Table; begin Do_Something(My_Tab'Access); end Whatever; As you can see, references provides a very natural interface for dealing with elements of a container. 2) SUPPORT FOR CONTAINER INDEXING We now take this one step further by allowing the definition of two new aspects, Constant_Indexing and Variable_Indexing, which allow the elimination of the function name in uses like that above, simplifying the notation to simply "Tab("Banana")". This works as follows: package Whatever is ... as above type T_Table is tagged limited private with Variable_Indexing => Element; Constant_Indexing => Element_RO; type Ref_Const_T(Data : access constant T) is new Ada.Finalization.Limited_Controlled with ... with Implicit_Dereference => Data; function Element_RO(Tab : access constant T_Table; Key : String) return Ref_Const_T; ... as above end Whatever; Now the body of Do_Something can be simply: procedure Do_Something(Tab : access T_Table) is -- Illustrate the implicit dereference -- of the objects of type Ref_T returned -- by function Element. begin Tab("Apple") := New_T(...); if Tab("Banana") = Null_T then Tab("Banana") := New_T(...); end if; ... end Do_Something; Essentially we are allowing a container to be "indexed" in the same way as an array is indexed, with the key (or potentially various different key types) being arbitrary. The basic rule with the Variable_Indexing and Constant_Indexing aspects is that all subprograms with the given name declared immediately within the same declaration list as the type, must be suitable for calling using prefix notation, given a variable or constant (respectively) of the associated type, meaning that it is either a primitive operation or a class-wide operation of the tagged type (via its first parameter). It must have at least two parameters. There seems no particular reason it couldn't have more than two parameters. There are no predefined _Indexing aspects, though one could imagine that all array types have such aspects, if it helps in understanding the model. If only the Variable_Indexing aspect is defined, then indexing may only be applied to variables. If only the Constant_Indexing aspect is defined then indexing may be applied to a variable or a constant, but not in a context that requires the result to be a variable. If both aspects are defined, then the Variable_Indexing aspect is used when the indexing is used in a context that requires a variable, or in an object renaming where the prefix is a variable; the Constant_Indexing aspect is used otherwise. With these two additional aspects defined, we can now use the "Tab(Key)" notation on both the left-hand-side of an assignment (on variables) and on the right-hand-side (on variables or constants). Note that we try to use Constant_Indexing wherever it is legal since it presumably has lower overhead, particularly if indexing into a "persistent" constainer. 3) SUPPORT FOR ITERATORS Given these two capabilities, we are now ready to define a more general "iterator" syntax, that is, a variant of the "for" loop statement which works naturally on containers. In fact we propose two distinct iterator syntaxes: a) The first syntax is intended for iterating over the set of cursors or indices that select elements of a container. This is most analogous to what is already available for arrays by writing "for I in Arr'Range loop": for Cursor in Iterate(Container) loop Container(Cursor) := Container(Cursor) + 1; end loop; What the above presumes is that the function Iterate returns an object of a type derived from a "magic" iterator type (see below). The iterator type provides a First and Next operation which return a Cursor type, and a Has_Element function that allows testing for the end of iteration. The above expands into: declare _Iter : Iterator_Type := Iterate(Container); Cursor : Cursor_Type := First(_Iter); begin while Has_Element(Cursor) loop Container(Cursor) := Container(Cursor) + 1; -- This presumes an appropriate _Indexing aspect -- is defined. Cursor := Next(_iter, Cursor); end loop; end; The generic package Iterator_Interfaces defines a pair of "magic" interfaces used to "enable" the above iterator syntax for a given cursor type: generic type Cursor; -- Formal incomplete type (see AI05-0213-1). with function Has_Element (Position : Cursor) return Boolean; package Ada.Iterator_Interfaces is type Forward_Iterator is limited interface; function First (Object : Forward_Iterator) return Cursor is abstract; function Next (Object : Forward_Iterator; Position : Cursor) return Cursor is abstract; type Reversible_Iterator is limited interface and Forward_Iterator; function Last (Object : Reversible_Iterator) return Cursor is abstract; function Previous (Object : Reversible_Iterator; Position : Cursor) return Cursor is abstract; end Ada.Iterator_Interfaces; By instantiating this package with a given cursor type, and then deriving one or more type(s) from the Forward_Iterator and/or Reversible_Iterator interfaces, this first iterator syntax is enabled for the given iterator type(s). See above for an example. Note that the routines defined in this magic generic generally match those used in iteration for the Ada.Containers packages, and their meaning is the same. The only difference is that we've added an iterator object parameter to the Next and Previous functions (which is necessary to make them part of the interface). The interface Forward_Iterator defines a user-defined iterator for the forward direction; the interface Reversible_Iterator defines a user-defined iterator for both the forward and reverse direction. Descendants of Reversible_Iterator can be used in loops with the REVERSE reserved word. The function First is expected to deliver the first cursor value for the iterator iterating in the forward direction, or return a value for which Has_Element will return False if no cursor value is available. The function Next is expected to deliver the next cursor value for the iterator iterating in the forward direction given the previous cursor value returned from a call to First or Next, or return a value for which Has_Element will return False if no additional cursor values are available. The function Last is expected to deliver the last cursor value for the iterator iterating in the reverse direction, or return a value for which Has_Element will return False if no cursor value is available. The function Previous is expected to deliver the next cursor value for the iterator iterating in the reverse direction given the previous cursor value returned from a call to Last or Previous, or return a value for which Has_Element will return False if no additional cursor values are available. b) The second syntax is intended for iterating directly over the elements of a container or an array: for Elem of Container loop Elem := Elem + 1; end loop; or for Elem of Arr loop Elem := Elem + 1; end loop; This second syntax is more convenient, but less flexible, in that there is no way to specify the particular iterator to use, so it requires that there be a "default" iterator. Two new "aspects" for an aspect_specification on a container type are used to enable the second syntax (Default_Iterator and Iterator_Element). If the aspects Default_Iterator and Iterator_Element are specified, then the above loop over Container expands into: declare _Iter : Iterator_Type := Container_Type'Default_Iterator(Container); _Cursor : Cursor := First(_Iter); begin while Has_Element(_Cursor) loop declare Elem : Container_Type'Iterator_Element renames Container(_Cursor); -- This requires an appropriate _Indexing aspect being defined begin Elem := Elem + 1; _Cursor := Next(_Iter, _Cursor); end; end loop; end; Here is how this generic and the iterator-related aspects might be used: type Cursor_Type is ... function Has_Element (Position : Cursor_Type) return Boolean; package Iterators is new Ada.Iterator_Interfaces(Cursor_Type, Has_Element); type Container is ... with Variable_Indexing => Element, Constant_Indexing => Element_RO, Default_Iterator => Iterate, Iterator_Element => Element_Type; type Element_Ref(E : access Element_Type) is tagged limited private with Implicit_Dereference => E; type Element_CRef(E : access constant Element_Type) is tagged limited private with Implicit_Dereference => E; type Iterator_Type is new Iterators.Reversible_Iterator with ... function Iterate(Con : Container) return Iterator_Type; function Element(Con : access Container; Cur : Cursor_Type) return Element_Ref; function Element_RO(Con : access constant Container; Cur : Cursor_Type) return Element_CRef; ... These two new kinds of "for" loops would require two new kinds of "for" loop parameter specifications: defining_identifier IN [REVERSE] iterator_expression defining_identifier [: subtype_indication] OF container_expression The first requires an expression returning an object of a type descended from Forward_Iterator, or Reversible_Iterator if REVERSE appears. The defining_identifier is of the corresponding Cursor type, and can be used to index into the container. The second requires an expression returning an array, or an object of a type which has aspects Default_Iterator and Iterator_Element specified. 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. Here are some additional examples of iterators. An example of the first iterator would be: V : Int_Vectors.Vector; ... for C in V.Iterate loop V(C) := V(C) + 1; -- Note that this presumes appropriate -- _Indexing aspects have been defined. end loop; Some examples of the second iterator would be: Arr : array(1..10) of Natural := ... ... for X : Natural of Arr loop -- The ": Natural" part is optional. -- X renames Arr() where in Arr'Range. -- X is a variable if Arr is a variable. X := X + 1; end loop; -------- Vec : Int_Vectors.Vector; ... for X of Vec loop -- X renames Vec() where in Default_Iterator(Vec). -- If Vec is a variable, Vec() is equiv to -- Vec'Variable_Indexing(Vec, ).Discrim.all -- and if Vec is a constant, is equiv to -- Vec'Constant_Indexing(Vec, ).Discrim.all X := X + 1; end loop; The "of" syntax, suggested by J.P. Rosen, seems to help identify X as an element *of* the array/container rather than as an index or cursor *in* the sequence defined by the discrete range or iterator. !wording [NOTE for editor: I am using /xyzzy/ to mean italicized xyzzy.] Revise 4.1(2/3) as follows: name ::= direct_name | explicit_dereference | indexed_component | slice | selected_component | attribute_reference | type_conversion | function_call | character_literal | qualified_expression {| generalized_reference | generalized_indexing} Add a new clause 4.1.5: 4.1.5 User-Defined References Given a discriminated type T, the following type-related operational aspect may be specified: Implicit_Dereference This aspect is specified by a name that denotes an access discriminant declared for the type T. A (view of a) type with a specified Implicit_Dereference aspect is a /reference type/. A /reference object/ is an object of a reference type. The discriminant named by the Implicit_Dereference aspect is the /reference discriminant/ of the reference type or reference object. [Redundant: A generalized_reference is a name that identifies a reference object, and denotes the object or subprogram designated by the reference discriminant of the reference object.] Syntax generalized_reference ::= /reference_object_/name Name Resolution The expected type for the /reference_object_/name in a generalized_reference is expected to be of any reference type. Static Semantics A generalized_reference denotes a view equivalent to that of a dereference of the reference discriminant of the reference object. Given a reference type T, the Implicit_Dereference aspect is inherited by descendants of type T if not overridden. If a descendant type constrains the value of the reference discriminant of T by a new discriminant, that new discriminant is the reference discriminant of the descendant. [Redundant: If the descendant type constrains the value of the reference discriminant of T by an expression other than the name of a new discriminant, a generalized_reference that identifies an object of the descendant type denotes the object or subprogram designated by the value of this constraining expression.] Dynamic Semantics The evaluation of a generalized_reference consists of the evaluation of the /reference_object_/name and a determination of the object or subprogram designated by the reference discriminant of the named reference object. A check is made that the value of the reference discriminant is not the null access value. Constraint_Error is raised if this check fails. The generalized_reference denotes the object or subprogram designated by the value of the reference discriminant of the named reference object. Example type Ref_Element(Data : access Element) is new Ada.Finalization.Limited_Controlled with private with Implicit_Dereference => Data; -- This Ref_Element type is a "reference" type. -- "Data" is its reference discriminant. function Find(C : access Container; Key : String) return Ref_Element; -- Return a reference to an element of a container ... Find(C, "abc") := Element'(...); -- Assign through a reference -- This is equivalent to: -- Find(C, "abc").Data.all := Element'(...); Add a new clause 4.1.6: 4.1.6 User-Defined Indexing Given a tagged type T, the following type-related, operational aspects may be specified: Constant_Indexing This aspect shall be specified by a name that denotes one or more functions declared immediately within the same declaration list in which T is declared. All of such functions shall have at least two parameters, the first of which is of type T or T'Class, or is an access-to-constant parameter with designated type T or T'Class. Variable_Indexing This aspect shall be specified by a name that denotes one or more functions declared immediately within the same declaration list in which T is declared. All of such functions shall have at least two parameters, the first of which is of type T or T'Class, or is an access parameter with designated type T or T'Class. All of such functions shall have a return type that is a reference type (see 4.1.5), whose access discriminant is of an access-to-variable type. These aspects are inherited by descendants of T (including the class-wide type T'Class). The aspects may not be overridden, but the functions they denote may be. An /indexable type/ is (a view of) a tagged type with at least one of the aspects Constant_Indexing or Variable_Indexing specified. An /indexable object/ is an object of an indexable type. [Redundant: A generalized_indexing is a name that denotes the result of calling a function named by a Constant_Indexing or Variable_Indexing aspect.] Syntax generalized_indexing ::= /indexable_object_/prefix actual_parameter_part Name Resolution The expected type for the /indexable_object_/prefix of a generalized_indexing is any indexable type. If the Constant_Indexing aspect is specified for the type of the /indexable_object_/prefix of a generalized_indexing, then the generalized_indexing is interpreted as a /constant indexing/ under the following circumstances: * when the Variable_Indexing aspect is not specified for the type of the /indexable_object_/prefix; * when the /indexable_object_/prefix denotes a constant; * when the generalized_indexing is used within a primary where a name denoting a constant is permitted. AARM NOTE: This means it is not interpreted as a constant_indexing for the /variable_/name in the LHS of an assignment (not inside a primary), nor for the name used for an OUT or IN OUT parameter (not allowed to be a constant), nor for the name in an object renaming (not inside a primary), unless there is no Variable_Indexing aspect defined. Otherwise, the generalized_indexing is interpreted as a /variable indexing/. When a generalized_indexing is interpreted as a constant (or variable) indexing, it is equivalent to a call on a prefixed view of one of the functions named by the Constant_Indexing (or Variable_Indexing) aspect of the type of the /indexable_object_/prefix with the given actual_parameter_part, and with the /indexable_object_/prefix as the prefix of the prefixed view. AARM NOTE: In other words, the generalized_indexing is equivalent to: /indexable_object_/prefix.Indexing actual_parameter_part where Indexing is the name specified for the Constant_Indexing or Variable_Indexing aspect. Revise paragraph 5.5(3): iteration_scheme ::= while condition | for loop_parameter_specification {| for iterator_specification} Revise the first sentence of paragraph 5.5(9): For the execution of a loop_statement with [a FOR iteration_scheme] {the iteration_scheme being FOR loop_parameter_specification}, the loop_parameter_specification is first elaborated. ... Add two new new clauses 5.5.1 and 5.5.2: 5.5.1 User-Defined Iterator Types The following language-defined generic library package exists: generic type Cursor; with function Has_Element (Position : Cursor) return Boolean; package Ada.Iterator_Interfaces is type Forward_Iterator is limited interface; function First (Object : Forward_Iterator) return Cursor is abstract; function Next (Object : Forward_Iterator; Position : Cursor) return Cursor is abstract; type Reversible_Iterator is limited interface and Forward_Iterator; function Last (Object : Reversible_Iterator) return Cursor is abstract; function Previous (Object : Reversible_Iterator; Position : Cursor) return Cursor is abstract; end Ada.Iterator_Interfaces; Static Semantics An /iterator type/ is a type descended from the Forward_Iterator interface from some instance of Ada.Iterator_Interfaces. A /reversible iterator type/ is a type descended from the Reversible_Iterator interface from some instance of Ada.Iterator_Interfaces. An /iterator object/ is an object of an iterator type. A /reversible iterator object/ is an object of a reversible iterator type. The formal subtype Cursor from the associated instance of Ada.Iterator_Interfaces is the /iteration cursor subtype/ for the iterator type. The following type-related operational aspects may be specified for an indexable type T (see 4.1.6): Default_Iterator This aspect is specified by a name that denotes exactly one function declared immediately within the same declaration list in which T is declared, whose first parameter is of type T or T'Class or an access parameter whose designated type is type T or T'Class, whose other parameters, if any, have default expressions, and whose result type is an iterator type. This function is the /default iterator function/ for T. Its result subtype is the /default iterator subtype/ for T. The iteration cursor subtype for the default iterator subtype is the /default cursor subtype/ for T. Iterator_Element This aspect is specified by a name that denotes a subtype. This is the /default element subtype/ for T. These aspects are inherited by descendants of type T (including T'Class). An /iterable type/ is an indexable type with specified Default_iterator and Iterator_Element aspects. A /reversible iterable type/ is an iterable type with the default iterator type being a reversible iterator type. An /iterable object/ is an object of an iterable type. A /reversible iterable object/ is an object of a reversible iterable type. Legality Rules The Constant_Indexing aspect (if any) of an iterable type T shall denote exactly one function with the following properties: * the result type of the function is covered by the default element type of T or is a reference type (see 4.1.5) with an access discriminant designating a type covered by the default element type of T; * the type of the second parameter of the function covers the default cursor type for T; * if there are more than two parameters, the additional parameters all have default expressions. This function (if any) is the /default constant indexing function/ for T. The Variable_Indexing aspect (if any) of an iterable type T shall denote exactly one function with the following properties: * the result type of the function is a reference type (see 4.1.5) with an access discriminant designating a type covered by the default element type of T; * the type of the second parameter of the function covers the default cursor type for T; * if there are more than two parameters, the additional parameters all have default expressions. This function (if any) is the /default variable indexing function/ for T. 5.5.2 Generalized Loop Iteration Generalized forms of loop iteration are provided by an iterator_specification. Syntax iterator_specification ::= defining_identifier IN [REVERSE] /iterator_/name | defining_identifier [: subtype_indication] OF [REVERSE] /array_/name | defining_identifier [: subtype_indication] OF [REVERSE] /iterable_/name Name Resolution The first form of iterator_specification is called a /generalized iterator/; the expected type for the /iterator_/name is any iterator type. The second form of iterator_specification is called an /array component iterator/; the expected type for the /array_/name is any array type. The third form of iterator_specification is called a /container element iterator/; the expected type for the /iterable_/name is any iterable type. Legality Rules If the reserved word REVERSE appears, the iterator_specification is a /reverse iterator/; otherwise it is a /forward iterator/. In a reverse generalized iterator, the /iterator_/name shall be of a reversible iterator type. In a reverse container element iterator, the default iterator type for the type of the /iterable_/name shall be a reversible iterator type. The type of the subtype_indication, if any, of an array component iterator shall cover the component type of the type of the /array_/name. The type of the subtype_indication, if any, of a container element iterator shall cover the default element type for the type of the /iterable_/name. In a container element iterator whose /iterable_/name has type T, if the /iterable_/name denotes a constant or the Variable_Indexing aspect is not specified for T, then the Constant_Indexing aspect shall be specified for T. Static Semantics An iterator_specification declares a /loop parameter/. In a generalized iterator, the nominal subtype of the loop parameter is the iterator cursor subtype. In an array component iterator or a container element iterator, if a subtype_indication is present, it determines the nominal subtype of the loop parameter. In an array component iterator if a subtype_indication is not present, the nominal subtype of the loop parameter is the component subtype of the type of the /array_/name. In a container element iterator if a subtype_indication is not present, the nominal subtype of the loop parameter is the default element subtype for the type of the /iterable_/name. In a generalized iterator, the loop parameter denotes a constant. In an array component iterator, the loop parameter denotes a constant if the /array_/name denotes a constant; otherwise it denotes a variable. In a container element iterator, the loop parameter denotes a constant if the /iterable_/name denotes a constant, or if the Variable_Indexing aspect is not specified for the type of the /iterable_/name; otherwise it denotes a variable. Dynamic Semantics For the execution of a loop_statement with an iterator_specification, the iterator_specification is first elaborated. This elaboration elaborates the subtype_indication, if any. For a generalized iterator, the loop parameter is created, and the /iterator_/name is evaluated and the denoted iterator object becomes the /loop iterator/. 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 loop_statement is complete. Otherwise, the sequence_of_statements 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. For an array component iterator, the /array_/name is evaluated and the array object denoted by the name becomes the /array for the loop/. If the array for the loop is a null array, then the execution of the loop_statement is complete. Otherwise, the sequence_of_statements is 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). 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 sequence_of_statements 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. For a container element iterator, the /iterable_/name is evaluated and the iterable object denoted by the name becomes the /iterable object for the loop/. The default iterator function for the type of the iterable object for the loop is called on the iterable object and the result is the /loop iterator/. An object of the default cursor subtype is created (the /loop cursor/). For a forward container element iterator, the operation First of the iterator type is called on the loop iterator, to produce the initial value for the loop cursor. If the result of calling Has_Element on the initial value is False, then the execution of the loop_statement is complete. Otherwise, the sequence_of_statements is executed with the loop parameter denoting an indexing (see 4.1.6) into the iterable object for the loop, with the only parameter to the indexing being the current value of the loop cursor; then the Next operation of the iterator type is called with the loop iterator and the loop cursor to produce the next value to be assigned to the loop cursor. This repeats until the result of calling Has_Element on the loop cursor is False, or until the loop is left as a consequence of a transfer of control. For a reverse container element iterator, the operations Last and Previous are called rather than First and Next. If the loop parameter is a constant (see above), then the indexing uses the default constant indexing function for the type of the iterable object for the loop; otherwise it uses the default variable indexing function. Modify second sentence of 13.13.2(9/2): ... For composite types, the Write or Read attribute for each component is called in canonical order, which is last dimension varying fastest for an array {(unless the convention of the array is Fortran, in which case it is first dimension varying fastest)}, and positional aggregate order for a record. !discussion (See proposal.) !examples See AI05-0212-1 for examples of these features being used in the predefined containers packages. !corrigendum 4.1(2) @drepl @xcode<@fa ::= @fa | @fa | @fa | @fa | @fa | @fa | @fa | @fa | @fa> @dby @xcode<@fa ::= @fa | @fa | @fa | @fa | @fa | @fa | @fa | @fa | @fa | @fa | @fa> !corrigendum 4.1.5(0) @dinsc @s8<@i> Given a discriminated type @i, the following type-related operational aspect may be specified: @xhang<@xtermThis aspect is specified by a name that denotes an access discriminant declared for the type @i.> A (view of a) type with a specified Implicit_Dereference aspect is a @i. A @i is an object of a reference type. The discriminant named by the Implicit_Dereference aspect is the @i of the reference type or reference object. A @fa is a @fa that identifies a reference object, and denotes the object or subprogram designated by the reference discriminant of the reference object. @s8<@i> @xindent<@fa ::= @i@fa> @s8<@i> The expected type for the @i@fa in a @fa is expected to be of any reference type. @s8<@i> A @fa denotes a view equivalent to that of a dereference of the reference discriminant of the reference object. Given a reference type @i, the Implicit_Dereference aspect is inherited by descendants of type @i if not overridden. If a descendant type constrains the value of the reference discriminant of @i by a new discriminant, that new discriminant is the reference discriminant of the descendant. If the descendant type constrains the value of the reference discriminant of @i by an @fa other than the @fa of a new discriminant, a @fa that identifies an object of the descendant type denotes the object or subprogram designated by the value of this constraining expression. @s8<@i> The evaluation of a @fa consists of the evaluation of the @i@fa and a determination of the object or subprogram designated by the reference discriminant of the named reference object. A check is made that the value of the reference discriminant is not the null access value. Constraint_Error is raised if this check fails. The @fa denotes the object or subprogram designated by the value of the reference discriminant of the named reference object. @s8<@i> @xcode<@b Ref_Element(Data : @b Element) @b @b Ada.Finalization.Limited_Controlled @b @b Implicit_Dereference =@> Data; -- @ft<@i> -- @ft<@i<"Data" is its reference discriminant.>>> @xcode<@b Find(C : @b Container; Key : String) @b Ref_Element; -- @ft<@i>> @xcode<...> @xcode>> @xcode<-- @ft<@i> -- Find(C, "abc").Data.@b := Element'(...);> !corrigendum 4.1.6(0) @dinsc @s8<@i> Given a tagged type @i, the following type-related, operational aspects may be specified: @xhang<@xtermThis aspect shall be specified by a @fa that denotes one or more functions declared immediately within the same declaration list in which @i is declared. All of such functions shall have at least two parameters, the first of which is of type @i or @i'Class, or is an access-to-constant parameter with designated type @i or @i'Class.> @xhang<@xtermThis aspect shall be specified by a @fa that denotes one or more functions declared immediately within the same declaration list in which @i is declared. All of such functions shall have at least two parameters, the first of which is of type @i or @i'Class, or is an access parameter with designated type @i or @i'Class. All of such functions shall have a return type that is a reference type (see 4.1.5), whose access discriminant is of an access-to-variable type.> These aspects are inherited by descendants of @i (including the class-wide type @i'Class). The aspects may not be overridden, but the functions they denote may be. An @i is (a view of) a tagged type with at least one of the aspects Constant_Indexing or Variable_Indexing specified. An @i is an object of an indexable type. A @fa is a @fa that denotes the result of calling a function named by a Constant_Indexing or Variable_Indexing aspect. @s8<@i> @xindent<@fa ::= @i@fa @fa> @s8<@i> The expected type for the @i@fa of a @fa is any indexable type. If the Constant_Indexing aspect is specified for the type of the @i@fa of a @fa, then the @fa is interpreted as a @i under the following circumstances: @xbullet@fa;> @xbullet@fa denotes a constant;> @xbullet is used within a @fa where a @fa denoting a constant is permitted.> Otherwise, the @fa is interpreted as a @i. When a generalized_indexing is interpreted as a constant (or variable) indexing, it is equivalent to a call on a prefixed view of one of the functions named by the Constant_Indexing (or Variable_Indexing) aspect of the type of the @i@fa with the given @fa, and with the @i@fa as the @fa of the prefixed view. !corrigendum 5.5(3) @drepl @xcode<@fa ::= @ft<@b> @fa | @ft<@b> @fa> @dby @xcode<@fa ::= @ft<@b> @fa | @ft<@b> @fa | @ft<@b> @fa> !corrigendum 5.5(9) @drepl For the execution of a @fa with a @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 (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 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 (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. !corrigendum 5.5.1(0) @dinsc @s8<@i> The following language-defined generic library package exists: @xcode<@b @b Cursor; @b Has_Element (Position : Cursor) @b Boolean; @b Ada.Iterator_Interfaces @b @b Pure (Iterator_Interfaces);> @xcode< @b Forward_Iterator @b; @b First (Object : Forward_Iterator) @b Cursor @b; @b Next (Object : Forward_Iterator; Position : Cursor) @b Cursor @b;> @xcode< type Reversible_Iterator @b Forward_Iterator; @b Last (Object : Reversible_Iterator) @b Cursor @b; @b Previous (Object : Reversible_Iterator; Position : Cursor) @b Cursor @b;> @xcode<@b Ada.Iterator_Interfaces;> An @i is a type descended from the Forward_Iterator interface from some instance of Ada.Iterator_Interfaces. A @i is a type descended from the Reversible_Iterator interface from some instance of Ada.Iterator_Interfaces. An @i is an object of an iterator type. A @i is an object of a reversible iterator type. The formal subtype Cursor from the associated instance of Ada.Iterator_Interfaces is the @i for the iterator type. The following type-related operational aspects may be specified for an indexable type @i (see 4.1.6): @xhang<@xtermThis aspect is specified by a @fa that denotes exactly one function declared immediately within the same declaration list in which @i is declared, whose first parameter is of type @i or @i'Class or an access parameter whose designated type is type @i or @i'Class, whose other parameters, if any, have default expressions, and whose result type is an iterator type. This function is the @i for @i. Its result subtype is the @i for T. The iteration cursor subtype for the default iterator subtype is the @i for @i.> @xhang<@xtermThis aspect is specified by a @fa that denotes a subtype. This is the @i for @i.> These aspects are inherited by descendants of type @i (including @i'Class). An @i is an indexable type with specified Default_iterator and Iterator_Element aspects. A @i is an iterable type with the default iterator type being a reversible iterator type. An @i is an object of an iterable type. A @i is an object of a reversible iterable type. @s8<@i> The Constant_Indexing aspect (if any) of an iterable type @i shall denote exactly one function with the following properties: @xbullet or is a reference type (see 4.1.5) with an access discriminant designating a type covered by the default element type of @i;> @xbullet;> @xbullet This function (if any) is the @i for @i. The Variable_Indexing aspect (if any) of an iterable type @i shall denote exactly one function with the following properties: @xbullet;> @xbullet;> @xbullet This function (if any) is the @i for @i. !corrigendum 5.5.2(0) @dinsc Generalized forms of loop iteration are provided by an @fa. @s8<@i> @xcode<@fa @fa @ft<@b> [@ft<@b>] @ft<@i>@fa | @fa @ft<@b> [@ft<@b>] @ft<@i>@fa | @fa @ft<@b> [@ft<@b>] @ft<@i>@fa> @s8<@i> The first form of @fa is called a @i; the expected type for the @i@fa is any iterator type. The second form of @fa is called an @i; the expected type for the @i@fa is any array type. The third form of @fa is called a @i; the expected type for the @i@fa is any iterable type. @s8<@i> If the reserved word @b appears, the @fa is a @i; otherwise it is a @i. In a reverse generalized iterator, the @i@fa shall be of a reversible iterator type. In a reverse container element iterator, the default iterator type for the type of the @i@fa shall be a reversible iterator type. The type of the @fa, if any, of an array component iterator shall cover the component type of the type of the @i@fa. The type of the @fa, if any, of a container element iterator shall cover the default element type for the type of the @i@fa. In a container element iterator whose @i@fa has type @i, if the @i@fa denotes a constant or the Variable_Indexing aspect is not specified for @i, then the Constant_Indexing aspect shall be specified for @i. @s8<@i> An @fa declares a @i. In a generalized iterator, the nominal subtype of the loop parameter is the iterator cursor subtype. In an array component iterator or a container element iterator, if a @fa is present, it determines the nominal subtype of the loop parameter. In an array component iterator if a @fa is not present, the nominal subtype of the loop parameter is the component subtype of the type of the @i@fa. In a container element iterator if a @fa is not present, the nominal subtype of the loop parameter is the default element subtype for the type of the @i@fa. In a generalized iterator, the loop parameter denotes a constant. In an array component iterator, the loop parameter denotes a constant if the @i@fa denotes a constant; otherwise it denotes a variable. In a container element iterator, the loop parameter denotes a constant if the @i@fa denotes a constant, or if the Variable_Indexing aspect is not specified for the type of the @i@fa; otherwise it denotes a variable. @s8<@i> For the execution of a @fa with an @fa, the @fa is first elaborated. This elaboration elaborates the @fa, if any. For a generalized iterator, the loop parameter is created, and 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. For an array component iterator, the @i@fa is evaluated and the array object denoted by the name 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. For a container element iterator, the @i@fa is evaluated and the iterable object denoted by the name becomes the @i. The default iterator function for the type of the iterable object for the loop is called on the iterable object and the result is the @i. An object of the default cursor subtype is created (the @i). For a forward container element iterator, the operation First of the iterator type is called on the loop iterator, to produce the initial value for the loop cursor. 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 with the loop parameter denoting an indexing (see 4.1.6) into the iterable object for the loop, with the only parameter to the indexing being the current value of the loop cursor; then the Next operation of the iterator type is called with the loop iterator and the loop cursor to produce the next value to be assigned to the loop cursor. This repeats until the result of calling Has_Element on the loop cursor is False, or until the loop is left as a consequence of a transfer of control. For a reverse container element iterator, the operations Last and Previous are called rather than First and Next. If the loop parameter is a constant (see above), then the indexing uses the default constant indexing function for the type of the iterable object for the loop; otherwise it uses the default variable indexing function. !corrigendum 13.13.2(9/2) @drepl For elementary types, Read reads (and Write writes) the number of stream elements implied by the Stream_Size for the type @i; the representation of those stream elements is implementation defined. For composite types, the Write or Read attribute for each component is called in canonical order, which is last dimension varying fastest for an array, and positional aggregate order for a record. Bounds are not included in the stream if @i is an array type. If @i is a discriminated type, discriminants are included only if they have defaults. If @i is a tagged type, the tag is not included. For type extensions, the Write or Read attribute for the parent type is called, followed by the Write or Read attribute of each component of the extension part, in canonical order. For a limited type extension, if the attribute of the parent type or any progenitor type of @i is available anywhere within the immediate scope of @i, and the attribute of the parent type or the type of any of the extension components is not available at the freezing point of @i, then the attribute of @i shall be directly specified. @dby For elementary types, Read reads (and Write writes) the number of stream elements implied by the Stream_Size for the type @i; the representation of those stream elements is implementation defined. For composite types, the Write or Read attribute for each component is called in canonical order, which is last dimension varying fastest for an array (unless the convention of the array is Fortran, in which case it is first dimension varying fastest), and positional aggregate order for a record. Bounds are not included in the stream if @i is an array type. If @i is a discriminated type, discriminants are included only if they have defaults. If @i is a tagged type, the tag is not included. For type extensions, the Write or Read attribute for the parent type is called, followed by the Write or Read attribute of each component of the extension part, in canonical order. For a limited type extension, if the attribute of the parent type or any progenitor type of @i is available anywhere within the immediate scope of @i, and the attribute of the parent type or the type of any of the extension components is not available at the freezing point of @i, then the attribute of @i shall be directly specified. !ACATS test ACATS tests are needed for all of these features. !appendix From: Tucker Taft Thoughts on Iterators, 8-Nov-2009, for ARG meeting in St. Pete Relevant players in the game: Container, Cursor, Iterator, Element. ------------- for Element in [reverse] Container loop ... -- body of loop end loop; is equiv to: declare Iterator : Iterator_Type := Iterate(Container); Cursor : Cursor_Type := First(Iterator); begin while Has_Element(Cursor) loop declare Element : Element_Type renames Container(Cursor); begin ... -- body of loop Cursor := Next(Iterator, Cursor); end; end loop; end; or... declare Iterator : Iterator_Type := Iterate(Container); begin while More(Iterator) loop declare Cursor : Cursor_Type := Get_Next(Iterator'Access); Element : Element_Type renames Container(Cursor); begin ... -- body of loop end; end loop; end; --------- Container(Cursor) is equiv to: Element_{Ref,CRef}(Container, Cursor).Data.all for Cursor in Special_Iterator(Container) loop Put(Container(Cursor)); end loop; --------- for Element in Container loop Put(Element); end loop; is equiv to: for Cursor in Default_Iterator(Container) loop declare Element : Element_Type renames Container(Cursor); begin Put(Element); end; end loop; similarly: for Element in Array_Obj loop Put(Element); end loop; is equiv to: for I in Array_Obj'Range(1) loop for J in Array_Obj'Range(2) loop ... declare Element : Array_Obj_Component_Type renames Array_Obj(I, J, ...); begin Put(Element); end; ... end loop; end loop; --------- package Iterators is new Ada.Iterator_Interfaces(Cursor_Type, No_Element); type Iterator_Type is new Iterators.Reversible_Iterator with ... package Containers is new Ada.Container_Interfaces(Iterators, Iterator_Type, Element_Type, Element_Reference); type Container_Type is new Containers.Container with ... generic with package Iterators is new Ada.Iterator_Interfaces(<>); type Iterator_Type is new Iterators.Reversible_Iterator with private; type Element_Type(<>) is limited private; type Element_Reference(Data : access Element_Type) is limited private; type Element_Constant_Reference(Data : access constant Element_Type) is limited private; package Ada.Container_Interfaces is type Container is limited interface; function Default_Iterator(C : Container) return Iterator_Type is abstract; function Element_Ref(C : aliased in out Container; Cursor : Iterators.Cursor) return Element_Reference is abstract; function Element_CRef(C : aliased in Container; Cursor : Iterators.Cursor) return Element_Constant_Reference is abstract; end Ada.Container_Interfaces; ---------- generic type Cursor is private; No_Element : in Cursor; package Ada.Iterator_Interfaces is type Basic_Iterator is limited interface; function First (Object : Basic_Iterator) return Cursor; function Next (Object : Basic_Iterator; Position : Cursor) return Cursor; type Reversible_Iterator is limited interface and Basic_Iterator; function Last (Object : Reversible_Iterator) return Cursor; function Previous (Object : Reversible_Iterator; Position : Cursor) return Cursor; end Ada.Iterator_Interfaces; **************************************************************** From: Robert Dewar Sent: Sunday, November 8, 2009 10:26 AM Equivalent always makes me nervous. Are we sure we can make this strong a statement? **************************************************************** From: Tucker Taft Sent: Sunday, November 8, 2009 11:40 AM This is more like the definition of "new T" when T has a user-defined storage pool. It is equivalent to a call on Allocate because its semantics are defined as calling Allocate. For the proposed syntax for iterators and indexing, their semantics will be defined in terms of calls on certain subprograms, etc. **************************************************************** From: Robert Dewar Sent: Sunday, December 8, 2009 1:14 PM > This is more like the definition of "new T" when T has a user-defined > storage pool. It is equivalent to a call on Allocate because its > semantics are defined as calling Allocate. I think it is fine to say "the behavior is as though xyz was called bla bla". I just think we often get ourselves in trouble if the formal definition uses the word equivalent, it's such a strong word from a formal point of view. **************************************************************** From: Tucker Taft Sent: Tuesday, December 8, 2009 5:01 PM Here is a new variant for AI05-139 on user-defined iterators, etc. [This is version /01 of this AI - ED.] It is based on all the earlier proposals, and incorporates the notes on iterators I passed around at the last ARG meeting. I removed the wording, and didn't update the discussion, so you will just have to focus on the !proposal section, which includes some simple examples. **************************************************************** From: Tucker Taft Sent: Tuesday, February 2, 2010 10:09 PM The "iterator" AI (AI-139-2) which has morphed into a more general "syntactic sugar" AI has some very complex "magic" generics that need to be instantiated "just so" to get the desired effect. I was wondering whether we might simplify things by simply having just one magic generic (essentially Randy's original one for iterators) plus a set of "magic" interfaces, i.e.: generic type Cursor is private; No_Element : in Cursor; package Ada.Iterator_Interfaces is type Basic_Iterator is limited interface; function First (Object : Basic_Iterator) return Cursor; function Next (Object : Basic_Iterator; Position : Cursor) return Cursor; type Reversible_Iterator is limited interface and Basic_Iterator; function Last (Object : Reversible_Iterator) return Cursor; function Previous (Object : Reversible_Iterator; Position : Cursor) return Cursor; end Ada.Iterator_Interfaces; package Ada.Magic is -- (Hopefully we could come up with a better package name!) type Reference is limited interface; type Container is limited interface; end Ada.Magic; There would be no operations defined on the magic interfaces. Defining a type descended from one of them would simply signal the intention of using the corresponding syntactic sugar. The one I am most enamored of these days is Reference, which given a type that is a descendant of Reference, and which has one access discriminant would implicitly dereference that access discriminant when the type is the result type of a function call. This shorthand has come up several times, not just in this AI, but also in our thinking about user-defined dereference, and I am also seeing it might be useful in a reworking of the "subpool" AI. There seem to be several places where you want to return a short-lived reference to an object, but then get control again when the reference goes away, and you don't want to have to write "Func(...).Data.all" to use the result, but merely write "Func(...)", with the ".Data.all" being implicit. Since this is syntactic sugar, I'm not uncomfortable with it being "activated" somewhat specially. But it should be simple. If I know I can easily take any type that has one access discriminant and give it this nice implicit dereference capability by just adding Reference as one of its progenitors, that is very light weight. I envision something similar for the Container interface. Here we want to be able to index into the container and end up with a Reference to an element using the notation "My_Container_Obj(I)". Again, I would just like to add Container as a progenitor to some container-ish type, and then use a specially-named function (e.g. "Index" or "Element") that returns a result of a type descended from Reference, and have "My_Container_Obj(I)" expand into, approximately, "Index(My_Container_Obj, I).Data.all". In both of these cases, actually instantiating generics, etc., to get this syntactic sugar is complex, while just adding "Reference" or "Container" as a progenitor and following some naming conventions might be very straightforward. Comments? **************************************************************** From: Edmond Schonberg Sent: Tuesday, February 2, 2010 10:50 PM This is certainly more appealing that the levels of generics in the current AI. Is the access discriminant part of the "magic" or is it a user-visible record definition somewhere? I take it that it is the actual for type Cursor? Should Cursor then be declared as an extension of this magic Reference interface, or am I misunderstanding this altogether? **************************************************************** From: Tucker Taft Sent: Tuesday, February 2, 2010 11:01 PM Cursor would be an index type for some object of a Container type, not the result type. For example, the Element function would take a Cursor, and return a Reference. I didn't include a discussion of the iterator stuff per-se. I was just focusing on the accessors/references and the container syntactic sugar, and wasn't worrying about the iterator "for ... in/of ... loop" stuff which is where cursors mostly come into the game. **************************************************************** From: Tucker Taft Sent: Tuesday, February 2, 2010 11:11 PM I forgot to answer your question about the access discriminant. The idea is that any type derived from Reference which has exactly one access discriminant would get the implicit dereference syntactic sugar. **************************************************************** From: Bob Duff Sent: Wednesday, February 3, 2010 8:09 AM > package Ada.Magic is ;-) > Comments? I think it's a good approach. If we're going to have magic, we might as well go all the way to Harry Potter style. Programmers will need a "cook book" style explanation. **************************************************************** From: Tucker Taft Sent: Wednesday, February 3, 2010 8:31 AM Ada.Magic, what a coincidence! ;-) By the way, the more I have thought about it, the more it seems handy to be able easily to define a "wrapper" for an access value (i.e. a descendant of Reference) which has that access value as an access discriminant designating the object of interest, plus other hidden stuff that might need finalization. We have talked about persistent containers of some sort. Returning a Reference to an element of a persistent container makes it easy to "pin" the page containing the element in memory as long as the Reference exists. The AI-111 distinction between subpools and handles on subpools also becomes more straightforward, where what I was calling a "handle" is just a Reference, that keeps the designated subpool from disappearing while it exists. A Reference has the properties I wanted for a handle, namely it is immutable (since you can't change the value of an access discriminant), can require finalization (e.g. to decrement a reference count), and would be implicitly dereferenced when used in a subpool_specification in an allocator, eliminating the need to include "implicit_dereference" in the subpool_specification syntax, since the automatic dereference associated with References would be part of the syntax for "name" itself. Similarly the other things I discussed in AI-111 also fit nicely with this idea of a Reference. In particular, what I called a "strong reference" needed to be "split" apart into a static handle and an access value to be useful, but it is easier to simply provide a function that takes a "strong reference" and returns a Reference designating the particular object, with the "static handle" on the associated subpool buried inside that Reference, ensuring that the subpool persists as long as the access value to the object in the subpool is floating around, while also providing implicit dereference to avoid the annoying need to write "ref.Data.all" everywhere. I also have a feeling the reachability checks can be removed from the compiler's concern, by burying them all in operations related to References. Of course, as Randy will remind me, the devil is in the details... **************************************************************** From: Bob Duff Sent: Wednesday, February 3, 2010 8:43 AM Or, "The devil is in the wording". ;-) But I do think you're on the right track, here. **************************************************************** From: Randy Brukardt Sent: Wednesday, February 3, 2010 1:00 PM I wish I did, because the idea is appealing. But the idea of some magic connecting two visibly unrelated types to a third ought to cause Mr. Private to come out of retirement to complain. :-) To be more specific: In the containers packages as currently proposed for AI-142-4, Containers, Cursors, and Reference_Type are all unrelated private types. Without magic, a call: Container_Object.Reference(My_Cursor).Element.all := ... creates a reference. The specs of the interesting things are: type Cursor is private; type Vector is tagged private; type Reference_Type (Element : not null access Element_Type) is tagged limited private; function Reference (Container : aliased in out Vector; Position : in Cursor) return Reference_Type; We want the "magic" to allow this call to be written as: Container_Object(My_Cursor) := ... Doing so requires the relationship between the three types to be exposed in the interface. There is no way to do that with a single magic interface unless we are willing to destroy privacy. One could imagine requiring a magic interface on two or all three of the types. (Requiring an interface on Cursors would require making Cursors a tagged type. And that would require defining Cursors in a nested package so all of the container operations are not primitive on cursors [if they were, they would be illegal]. That's probably too much change. The constant case could be worse: function Constant_Reference (Position : in Cursor) return Reference_Type; Here, there is no visible connection between the types at all. Luckily, we aren't suggesting that for Ada.Containers, but it seems reasonable for user-defined containers. This is, after all, the culmination of the "implicit container" model that Matt likes so much. Here, you are either going to deny the use of nice syntax or require privacy breaking. So I think we need the magic generic for the accessors (and honestly, I don't see what the concern is, the users will almost never instantiate these things, and when they do, they'll copy the containers implementation). The incantation for enabling magic can be as complex as necessary in order for it to work. The only case where this idea might work is the "Reference" one. In that case, the interface is directly placed on the Reference_Type implementation, and that probably is enough to describe some semantics of omitting the .all. IMHO, that's the least interesting of the three, given that Ada allows you to omit the .all in most contexts anyway. (I do see how you could use that to implement most of AI-111, which has been my point all along vis-a-vis that AI: we don't need the complex mechanisms so long as we have a way to get finalization right). It's surely the one I could live without (and I have serious doubts that it can be resolved without ambiguity). **************************************************************** From: Randy Brukardt Sent: Wednesday, February 3, 2010 1:05 PM ... > package Ada.Magic is > -- (Hopefully we could come up with a better package name!) > type Reference is limited interface; > type Container is limited interface; > end Ada.Magic; > > There would be no operations defined on the magic interfaces. > Defining a type descended from one of them would simply signal the > intention of using the corresponding syntactic sugar. Why would we want to use an interface for that purpose? The reason I used an interface was to tie operations on unrelated types together. If you are going to do that some other way, just say so. Interfaces require the type they are applied to be a tagged type, and I don't see any semantic reason that this magic needs to be restricted to tagged types. The only advantage I can see is that we probably wouldn't want to use a pragma to mark this (a pragma that changes the acceptable syntax seems to go far beyond "good taste", however you define that), and other options seems pretty heavy. But then again, this is a "heavy" feature, given the impact it has on reading code -- it needs to be clearly marked. **************************************************************** From: Tucker Taft Sent: Wednesday, February 3, 2010 2:41 PM ... > We want the "magic" to allow this call to be written as: > > Container_Object(My_Cursor) := ... > > Doing so requires the relationship between the three types to be > exposed in the interface. There is no way to do that with a single > magic interface unless we are willing to destroy privacy. I don't follow your logic. Can you drag me through it? > One could imagine requiring a magic interface on two or all three of > the types. (Requiring an interface on Cursors would require making > Cursors a tagged type. And that would require defining Cursors in a > nested package so all of the container operations are not primitive on > cursors [if they were, they would be illegal]. That's probably too much > change. I'm not recommending that. > The constant case could be worse: > > function Constant_Reference (Position : in Cursor) return > Reference_Type; > > Here, there is no visible connection between the types at all. > Luckily, we aren't suggesting that for Ada.Containers, but it seems > reasonable for user-defined containers. This is, after all, the > culmination of the "implicit container" model that Matt likes so much. > Here, you are either going to deny the use of nice syntax or require privacy > breaking. I don't understand where the privacy breaking comes into play. I was suggesting that by declaring a container as a descendant of the Magic.Container interface, you "activate" some of the syntactic sugar. I was further suggesting that by declaring the Reference_Type as a descendant of Magic.Reference, you "activate" the "..all" syntactic sugar. I don't see any privacy breaking here, so you should explain where you see it. This idea of using an interface type as effectively a "marker" is used successfully in other languages. You could use a pragma or aspect clause/specification, but having the presence of a pragma or aspect clause affect syntax seems in particularly bad taste. We already give some special capabilities to types having Controlled or Storage_Pool as an ancestor, so this feels like a modest step up from that. > So I think we need the magic generic for the accessors (and honestly, > I don't see what the concern is, the users will almost never > instantiate these things, and when they do, they'll copy the > containers implementation). The incantation for enabling magic can be > as complex as necessary in order for it to work. > > The only case where this idea might work is the "Reference" one. In > that case, the interface is directly placed on the Reference_Type > implementation, and that probably is enough to describe some semantics of omitting the .all. It's not just the ".all", it is omitting the ".Element.all", which is certainly not currently allowed by Ada. > IMHO, that's the least interesting of the three, given that Ada allows > you to omit the .all in most contexts anyway. (I do see how you could > use that to implement most of AI-111, which has been my point all > along vis-a-vis that AI: we don't need the complex mechanisms so long > as we have a way to get finalization right). It's surely the one I > could live without (and I have serious doubts that it can be resolved without ambiguity). Not surprisingly, I don't agree. Eliminating the need to write ".Element.all" all over the place changes the dynamic significantly, and makes these References actually usable and powerful. **************************************************************** From: Randy Brukardt Sent: Wednesday, February 3, 2010 3:59 PM > > Doing so requires the relationship between the three types to be > > exposed in the interface. There is no way to do that with a single > > magic interface unless we are willing to destroy privacy. > > I don't follow your logic. Can you drag me through it? I don't see how the fact that you've used the shorthand for a container reference would lead the compiler to select the correct function to implement that syntax. The only way I can think of is for the compiler to look inside the representation of Reference_Type and see that it in fact references a Vector. And even that is tenuous. ... > I don't understand where the privacy breaking comes into play. > > I was suggesting that by declaring a container as a descendant of the > Magic.Container interface, you "activate" > some of the syntactic sugar. This is the problem. > I was further suggesting that > by declaring the Reference_Type as a descendant of Magic.Reference, > you "activate" the "..all" > syntactic sugar. This is OK. > I don't see any privacy breaking here, so you should explain where you > see it. I was thinking backwards, but so are you. I presume that the effect of the Magic.Container interface would be to allow writing the call: Container_Object.(params) as Container_Object(params) (I'm presuming that your model is that the reference part is separate magic, so these are separable magic items. That's good, IMHO). But the problem here is that the function name is now gone. How can the compiler decide what function is being called? The original interface idea made it clear: it was named in the interface. But now you have left that out. So you end up having to look at *every* primitive and class-wide function whose first parameter has the right type (the latter because this is prefix notation, and it ought to work like it). You can't do that by name (as you do for everything else in Ada resolution), you have to pick up everything. That means a wide symboltable search, through all of the appropriate packages. And when does this apply? I have to think it has to apply to any function, because you have nothing else to say. That means that you can replace: Container_Object.Capacity and Container_Object.Length by Container_Object That would appear to make Container_Object itself ambiguous. OK, so we could insist on two parameters. We can then replace: Container_Object.To_Cursor (1) by Container_Object(1) But that's going to be ambiguous with the intended use (result resolution should bail us out most of the time, but not always). And then the killer: Container_Object.Element (1) and Container_Object.Reference (1).Element.all (assuming the use of Magic.Reference on the latter) would both be: Container_Object(1) And both have the same result type. So our new syntax is always ambiguous. What's the point of that?? I suppose you could fix this by allowing the special "omission" syntax to apply only to function returning a type with a Reference interface. But that seems very limiting; the new Magic.Container interface becomes a "gazebo" (as Dmitry put it on in a c.l.a. discussion); it only works in one particular case specifically tied to the implementation of Ada.Containers. It would not be usable in a different containers design. For instance, if you willing to directly return an anonymous access, you couldn't use this syntax. That seems insane. So I think it is critical that the marker (currently a container interface) declare the result type that is intended. It would be best if it could also declare the function whose name can be omitted. The only sane ways to do that is to use a magic generic interface, or mark using a pragma or new syntax (the latter being too heavy). And, assuming we have one for iterators, I don't see what the problem of having more magic generic interfaces. That is, you are trying to solve a non-problem. Note that using an interface to select the function is also less than ideal, because it forces the subprogram to have a particular name, and only allows one such function per container. Both of those are quite limiting as well. (This is the real problem with interfaces: a single type can either have or not have a particular interface. This is the same problem we have with stream attributes: it makes perfect sense to have multiple hierarchies of attributes for different purposes [say XML and binary streaming], but only one set can exist, and the other has to be created completely manually without any composition. Sometimes this is appropriate [finalization comes to mind], but it usually isn't [shouldn't fence in your users]. At least if the interface is in a generic, you can make multiple interfaces to cover different needs, and you can use renames to get the intended names of your functions -- but it's all harder than necessary.) > This idea of using an interface type as effectively a "marker" is used > successfully in other languages. You could use a pragma or aspect > clause/specification, but having the presence of a pragma or aspect > clause affect syntax seems in particularly bad taste. > We already give some special capabilities to types having Controlled > or Storage_Pool as an ancestor, so this feels like a modest step up > from that. If it was "modest", I'd agree. But the need to search for all primitive functions that match some profile (rather than searching by name as I suspect all Ada symboltables are currently set up) makes this a very expensive idea. Plus it is very limited in utility *or* Truly, the good thing that I see here is that you've proven that the interfaces for the Reference part and the Accessor part are completely separate logically. You had the pieces confused in the past, and that added to the complexity. But the Accessor interface needs to have nothing at all to do with the Reference interface, and indeed it is better if they are unrelated. (Presuming you believe in the need for the Reference at all, I'm still dubious.) ... > > The only case where this idea might work is the "Reference" one. In > > that case, the interface is directly placed on the Reference_Type > > implementation, and that probably is enough to describe some > > semantics of omitting the .all. > > It's not just the ".all", it is omitting the ".Element.all", which is > certainly not currently allowed by Ada. That's true, but .Element is an access discriminant that is being used only because the lack of some better way to get the right accessibility. I never wanted the .Element part in the first place; the whole mechanism is about getting rid of the .all and still having the right lifetime for the access value. The .Element got introduced for the lifetime reasons only, and only to avoid adding some other mechanism to get the right lifetime. Anyone who uses access discriminants on purpose is either the most brilliant Ada programmer possible or is delusional. You might in fact be the former, but I don't think we need to invent language features just for you. :-) For me, this is all about getting rid of the .all and having the right lifetime if someone tries to abuse the reference. The existence of the access discriminant is a (goofy) means to that end, and I'd prefer to never even think it exists. And I can't imagine anyone doing this on purpose outside a container library. > > IMHO, that's the least interesting of the three, given that Ada > > allows you to omit the .all in most contexts anyway. (I do see how > > you could use that to implement most of AI-111, which has been my > > point all along vis-a-vis that AI: we don't need the complex > > mechanisms so long as we have a way to get finalization right). It's > > surely the one I could live without (and I have serious doubts that > > it can be resolved without ambiguity). > > Not surprisingly, I don't agree. Eliminating the need to write > ".Element.all" all over the place changes the dynamic significantly, > and makes these References actually usable and powerful. Let's agree to disagree on this one. It doesn't matter anyway, I don't think there is a problem with this particular idea, because no guessing/wide search is required to figure out what is meant. I just don't think it works sanely for the container part. **************************************************************** From: Tucker Taft Sent: Wednesday, February 3, 2010 4:26 PM > But the problem here is that the function name is now gone. How can > the compiler decide what function is being called? The original > interface idea made it clear: it was named in the interface. But now > you have left that out. ... Ahh, I see your confusion. We (language designers) would still have to specify the implicit function name that is used in the expansion of the syntactic sugar, in the same way we choose a particular function name in any language-defined package. I had suggested something like "Element" or "Index", but conceivably we could define a new operator designator such as "()" for this purpose. Suppose we choose "Gazebo" ;-). Then for a type descended from Magic.Container, the syntax: Container_Object(params) would expand into Container_Object.Gazebo(params) And then if the result type were a Reference, it would expand further into Container_Object.Gazebo(params)..all One possibility would be to use something quite explicit and unlikely to collide with some existing function, such as "Container_Component" or "Container_Element." **************************************************************** From: Randy Brukardt Sent: Wednesday, February 3, 2010 4:50 PM > One possibility would be to use something quite explicit and unlikely > to collide with some existing function, such as "Container_Component" > or "Container_Element." Humm, if we decided on the operator designator "()" as you put it, note that we no longer need the interface at all. The use of "()" provides the syntactic marker needed. That is, if we have: function "()" (Container : aliased in out Vector; Position : in Cursor) return Reference_Type renames Reference; we know we are allowing Container_Object (My_Cursor) to stand for Container_Object."()" (My_Cursor) or, if you prefer Container_Object.Reference (My_Cursor) And even better, this isn't tied to particular return types or names; you can have as many as you want so long as the results are unambiguous. One would probably have to require that the first parameter be one that is allowed in a prefix notation call (otherwise, you couldn't use the special syntax, so what would be the point). This avoid having to come up with a magic name, as well. (OK, "()" is the magic name, but it is completely magic, just as all other operator symbols are. Seems right to me.) **************************************************************** From: Tucker Taft Sent: Wednesday, February 10, 2010 4:39 PM Here is an update to AI-139-2, [This is version /03 - Editor] which proposes syntactic "sugar" for using iterators, accessors, and containers. This new revision grew out of a recent phone discussion. The major changes involve providing a single "magic" Reference interface instead of a generic package, and an "()" operator to support indexing into a Container as though it were an array. I'm not personally convinced the "default iterator" stuff is worth the added complexity. It works easily for arrays, but it may be overkill to try to get it to work for containers as well. As usual, any and all comments welcome. **************************************************************** From: Tucker Taft Sent: Thursday, February 11, 2010 7:19 AM I didn't like the "default iterator" solution in version 3, so here is version 4 which uses the specification of two aspects, Default_Iterator and Iterator_Element, rather than the signature generic. This makes the solution more palatable to me. I recommend you ignore version 3. **************************************************************** From: Randy Brukardt Sent: Thursday, February 11, 2010 10:17 PM [The first of several free association thoughts on this proposal.] It would be appealing to define: function "()" (S : Unbounded_String; I : Positive) renames Element; but unfortunately Unbounded_String is not tagged, so this doesn't work. **************************************************************** From: Randy Brukardt Sent: Thursday, February 11, 2010 10:23 PM > One relatively important rule for "()" is that one can define two > operators, one that requires the controlling operand to be a variable, > and one that allows it to be a constant, and overload resolution will > resolve to the one that requires a variable if the actual is a > variable, and otherwise it will resolve to the one that allows a > constant. This rule is important so that the same notation, such as > "Container(Key)", can be used when Container is a constant as when it > is a variable, and the variableness of the result can be determined by > the variableness of the Container. Unfortunately, this is purely fantasy. Resolution rules do not take anything about constant vs. variable into account -- those are legality rules. Even if it wasn't fantasy, it still wouldn't make sense, since a variable can always be passed where a constant is allowed. It would seem odd to eliminate that. This seems like a downside of any proposal to eliminate the function names from calls. (Unless you are proposing a new kind of resolution to make this work; it would in fact make sense in this case, but I have to wonder about a preference rule since they're usually problematic.) **************************************************************** From: Randy Brukardt Sent: Thursday, February 11, 2010 10:38 PM In your example, you have the following: > function "()"(Tab : access T_Table; Key : String) return Ref_T; This works OK, but it is important to note that it isn't safe, in that the access value returned as the discriminant of Ref_T can outlive the Ref_T object (at least if I understand AI-51's model, which perhaps I don't in some subtle case). The "aliased" parameters of AI05-0142-4 were designed to eliminate that problem (and the need for dynamic accessibility checking). In that case, the specification would look like: function "()"(Tab : aliased in out T_Table; Key : String) return Ref_T; I couldn't tell whether you were proposing that the original formulation was enough in all cases (which didn't seem to be the case last year, and I doubt that's changed), or whether you were just avoiding the AI-142-4 and AI-143 features necessary to make the intended formulation work in the interest of avoiding mixing of a bunch of new features. **************************************************************** From: Tucker Taft Sent: Friday, February 12, 2010 6:34 AM I agree that "aliased in out" is preferable to "access" but I didn't want to tie the two AIs too closely at this point. I don't believe there is a safety issue. If there is, then AI-51 needs to be fixed. I am certainly hoping we are going to tackle AI-51 at this meeting! **************************************************************** From: Bob Duff Sent: Friday, February 12, 2010 12:57 PM This syntactic sugar is really great! I've been wanting things like this for a long time. Can we add user-defined literals to this AI, so unbounded strings can be tolerable? I'm disappointed that the "of" iterators require the cursor stuff. It would be much cleaner to define them in terms of things like: procedure Iterate (Container : in Vector; Process : not null access procedure (Position : in Cursor)); But I don't see how that can work, because then you can't "exit". Sigh. ... > Using an iterator over one of the Ada 2005 containers libraries > requires writing a subprogram which gets called repeatedly with each element of the container. > That effectively requires writing the loop body out-of-line from the > loop, making the code inside-out of its normal flow, requiring the > writing of a lot of extra text, and thus making it much harder to read and understand. Not to mention having to give it a meaningless name. ... > type Ref_T(Data : access T) is > new Ada.Finalization.Limited_Controlled > and Ada.References.Reference with ... Why not "null record"? ... > package body Whatever is > function Element(Tab : access T_Table; Key : String) return Ref_T is > -- Return a "reference" to an element of the table > begin > -- "Wrap" the pointer in a reference object > return Ref_T'(Data => Lookup(Tab, Key)); Is Lookup_Or_Create meant? ... > package Whatever is > ... as above > > function "()"(Tab : access T_Table; Key : String) return Ref_T; Here and below, id the "access" significant? Could it have been "in out"? ... > The basic rule with "()" is that it must be suitable for prefix > notation, meaning that it is either a primitive operation or a > class-wide operation of some tagged type (via its first parameter), > and if class-wide, is declared immediately within a package where some > tagged type covered by the class-wide type is immediately declared. > It must have at least two parameters. There seems no particular > reason it couldn't have more than two parameters. There are no predefined "()" > operators, though one could imagine that all array types have such > operators, if it helps in understanding the model. It seems annoying to restrict it to tagged types. Is that really necessary? I see below that cursor types will have to be tagged to take advantage of this notation. That's even more annoying, because cursors can often be quite lightweight. > One relatively important rule for "()" is that one can define two > operators, one that requires the controlling operand to be a variable, > and one that allows it to be a constant, and overload resolution will > resolve to the one that requires a variable if the actual is a > variable, and otherwise it will resolve to the one that allows a > constant. This rule is important so that the same notation, such as > "Container(Key)", can be used when Container is a constant as when it > is a variable, and the variableness of the result can be determined by > the variableness of the Container. Well, that's a radical notion! I see that it's necessary. Do you mean "allows a constant" or "requires a constant"? If the former, I think it introduces a Beaujolias effect. Would it make any sense to instead resolve on the basis of whether the context requires a variable? So for "X(C) := X(C) + 1;" the first would resolve to the variable one, and the second to the constant one. ... > What the above presumes is that the function Iterate returns an object > of a type derived from a "magic" iterator type (see below). The > iterator type provides a First and Next operation which return a > Cursor type, and a No_Element cursor value to indicate end of > iteration. The above expands into: > > declare > _Iter : Iterator_Type := Iterate(Container); > Cursor : Cursor_Type := First(_Iter); > begin > while Cursor /= No_Element loop Are you sure you want to require every cursor type to have a special No_Element value? ... > Note that the routines defined in this magic generic generally match > those used in iteration for the Ada.Containers packages, and their > meaning is the same. The only difference is that we've added an > iterator object parameter to the Next and Previous functions (which is > necessary to make them part of the interface). Are we going to add all this stuff to the existing container packages? ... > This second syntax is more convenient, but less flexible, in that > there is no way to specify the particular iterator to use, so it > requires that there be a "default" iterator. Only for containers -- not arrays. ... > The second requires an expression returning an array, or an object of > a type which has aspects Default_Iterator and Iterator_Element > specified. 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. Hmm. I'm inclined to always use a subtype_indication, for readability. Should we require that? If so, we can change "OF" to "IN" in the syntax, which seems a slight improvement. ... > Vec : Int_Vectors.Vector; > ... > for X of Vec loop > -- X renames Vec() where in Default_Iterator(Vec). > -- and Vec() is equiv to "()"(Vec, ).Discrim.all. > -- X is a variable if Vec is a variable. > > X := X + 1; > end loop; > > The "of" syntax, suggested by J.P. Rosen, seems to help identify X as > an element *of* the array/container rather than as an index or cursor > *in* the sequence defined by the discrete range or iterator. For some reason, "of" rubs me the wrong way. The elements are "in" the container, just as much as indices are "in" a range. I didn't read further, since you said you hadn't updated the rest. **************************************************************** From: Tucker Taft Sent: Friday, February 12, 2010 2:32 PM ... > Can we add user-defined literals to this AI, so unbounded strings can > be tolerable? You don't like the "+" operator for this? I suppose we could define another new operator for something like this, or an aspect/attribute. If you have something in mind, go ahead and suggest it. I'm pretty content with using "+". > I'm disappointed that the "of" iterators require the cursor stuff. It > would be much cleaner to define them in terms of things like: > > procedure Iterate > (Container : in Vector; > Process : not null access procedure (Position : in Cursor)); > > But I don't see how that can work, because then you can't "exit". > Sigh. You could have it take a function rather than a procedure, and return True or False to select "exit" or "continue". But you can also exit a loop prematurely using a "goto", a "return", a "requeue", an "exit" to an outer loop, etc. You put that all together and it creates a need to return a "continuation," and I think that is a bit too radical, even for Ada 2012. ;-) > ... >> with Ada.References; >> with Ada.Finalization; >> package Whatever is >> type T is private; >> Null_T : constant T; -- default initial value for T >> function New_T(...) return T; >> >> type T_Table is tagged limited private; >> >> type Ref_T(Data : access T) is >> new Ada.Finalization.Limited_Controlled >> and Ada.References.Reference with ... > > Why not "null record"? That would probably work in many cases. It depends on what sort of cleanup action is required when the reference goes away. ... >> package body Whatever is >> function Element(Tab : access T_Table; Key : String) return Ref_T is >> -- Return a "reference" to an element of the table >> begin >> -- "Wrap" the pointer in a reference object >> return Ref_T'(Data => Lookup(Tab, Key)); > > Is Lookup_Or_Create meant? Oops, yes I meant Lookup_Or_Create. >> package Whatever is >> ... as above >> >> function "()"(Tab : access T_Table; Key : String) return >> Ref_T; > > Here and below, id the "access" significant? Could it have been "in > out"? I think the param might need to also be "aliased", and I didn't want to tie this too closely to the "aliased" parameter mode AI. >> The basic rule with "()" is that it must be suitable for prefix >> notation, meaning that it is either a primitive operation or a >> class-wide operation of some tagged type (via its first parameter), >> and if class-wide, is declared immediately within a package where >> some tagged type covered by the class-wide type is immediately >> declared. It must have at least two parameters. There seems no >> particular reason it couldn't have more than two parameters. There are no predefined "()" >> operators, though one could imagine that all array types have such >> operators, if it helps in understanding the model. > > It seems annoying to restrict it to tagged types. > Is that really necessary? Probably not absolutely necessary. Since we only have prefix notation for tagged types, it seemed natural to piggy-back on that. But these are really pretty different, I suppose, so we could consider defining these for untagged types. > I see below that cursor types will have to be tagged to take advantage > of this notation. That's even more annoying, because cursors can > often be quite lightweight. I didn't mean for cursors to have to be tagged. Where does that requirement come in? It was not intentional. >> One relatively important rule for "()" is that one can define two >> operators, one that requires the controlling operand to be a >> variable, and one that allows it to be a constant, and overload >> resolution will resolve to the one that requires a variable if the >> actual is a variable, and otherwise it will resolve to the one that >> allows a constant. This rule is important so that the same notation, >> such as "Container(Key)", can be used when Container is a constant as >> when it is a variable, and the variableness of the result can be >> determined by the variableness of the Container. > > Well, that's a radical notion! I see that it's necessary. > > Do you mean "allows a constant" or "requires a constant"? > If the former, I think it introduces a Beaujolias effect. > > Would it make any sense to instead resolve on the basis of whether the > context requires a variable? > So for "X(C) := X(C) + 1;" the first would resolve to the variable > one, and the second to the constant one. That might be better. I know this concerned Randy as well. I doubt if there is a Beaujolais effect, because this preference would presumably only apply to operators declared in the same scope. If the preference might affect resolution across packages, that would definitely be an issue. I guess one important issue is whether this is a visibility issue at all. This is more like the prefix-notation than like a visibility lookup. So I see it more as "selecting" the right "()" operator rather than using normal overload resolution. Clearly the devil will be in the wording for this one, if we decide we want this functionality. > ... >> for Element of Arr loop >> Element := Element + 1; >> end loop; >> >> This second syntax is more convenient, but less flexible, in that >> there is no way to specify the particular iterator to use, so it >> requires that there be a "default" iterator. > > Only for containers -- not arrays. Correct. > ... 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. > > Hmm. I'm inclined to always use a subtype_indication, for > readability. Should we require that? > If so, we can change "OF" to "IN" in the syntax, which seems a slight > improvement. I don't agree. Making the presence of a subtype_indication completely change the meaning seems weird to me. I think we want the syntax distinction as well. ... >> The "of" syntax, suggested by J.P. Rosen, seems to help identify X as >> an element *of* the array/container rather than as an index or cursor >> *in* the sequence defined by the discrete range or iterator. > > For some reason, "of" rubs me the wrong way. The elements are "in" > the container, just as much as indices are "in" a range. I think you naturally talk about the elements "of" a container, at least as often as you talk about the elements "in" a container. For arrays, we talk about an array "of" integers, not an array "containing" integers. I realize that is using "of" in the opposite sense, but "of" is a more symmetrical preposition than "in" in any case. Anne of Green Gables. Green Gables of Anne. Both make sense. ;-) > I didn't read further, since you said you hadn't updated the rest. Right-O. I actually tried to delete what wasn't applicable anymore, so the discussion and example sections are still worth reading, but they aren't complete at all. In any case, thanks for your careful reading of the new proposal section. **************************************************************** From: Bob Duff Sent: Friday, February 12, 2010 3:28 PM > > Can we add user-defined literals to this AI, so unbounded strings > > can be tolerable? > > You don't like the "+" operator for this? I think it's an ugly hack. But I do tolerate it. It's kind of annoying that this is a syntax error: X : Unbounded_String := +"Hello"; Y : Unbounded_String := X & +", world."; > I suppose we could define another new operator for something like > this, or an aspect/attribute. > If you have something in mind, go ahead and suggest it. I'm pretty > content with using "+". I don't like the "new operator" idea. That's just a different-looking but equally-ugly hack. My idea would be to have an aspect like Literal_Value that's called implicitly, so: X : T := "Hello"; Y : T2 := 123; is equivalent to X : T := T'Literal_Value("Hello"); Y : T2 := T'Literal_Value("123"); This AI is all about syntactic sugar, so I thought maybe I could sneak this in. ;-) > > I'm disappointed that the "of" iterators require the cursor stuff. > > It would be much cleaner to define them in terms of things like: > > > > procedure Iterate > > (Container : in Vector; > > Process : not null access procedure (Position : in Cursor)); > > > > But I don't see how that can work, because then you can't "exit". > > Sigh. > > You could have it take a function rather than a procedure, and return > True or False to select "exit" or "continue". So an "exit" in the loop would turn into "return True"? Hmm. Maybe. Doesn't work for nested loops. > But you can also exit a loop prematurely using a "goto", a "return", a > "requeue", an "exit" to an outer loop, etc. > You put that all together and it creates a need to return a > "continuation," and I think that is a bit too radical, even for Ada > 2012. ;-) Right, that's what I meant by "But I don't see how that can work". You just need coroutines, not the full mind-bending power of continuations, I think. It's disappointing, though, because it's often easier (and never harder) to write an iterator that way. Think about an iterator that's a recursive tree walk. To implement the get-next-item style, you have to horse around with an explicit stack that is guaranteed to be both too small and too big (or too inefficient). > > It seems annoying to restrict it to tagged types. > > Is that really necessary? > > Probably not absolutely necessary. Since we only have prefix notation > for tagged types, it seemed natural to piggy-back on that. But these > are really pretty different, I suppose, so we could consider defining > these for untagged types. OK, good. Let's try to do that. > > > > I see below that cursor types will have to be tagged to take > > advantage of this notation. That's even more annoying, because > > cursors can often be quite lightweight. > > I didn't mean for cursors to have to be tagged. > Where does that requirement come in? It was not intentional. They have to be tagged if you want to use "()", which I think is common, isn't it? > > Do you mean "allows a constant" or "requires a constant"? > > If the former, I think it introduces a Beaujolias effect. > > > > Would it make any sense to instead resolve on the basis of whether > > the context requires a variable? > > So for "X(C) := X(C) + 1;" the first would resolve to the variable > > one, and the second to the constant one. > > That might be better. But it doesn't work if you rename X(C), right? >...I know this concerned Randy as well. > I doubt if there is a Beaujolais effect, because this preference >would presumably only apply to operators declared in the same scope. >If the preference might affect resolution across packages, that would >definitely be an issue. It's still a Beaujolais effect if it's within the same package. You could argue that such Beaujolais effects are not so bad. I'm thinking that you have just the constant one. X is a variable, and you say "Put(X(C));". Then you add the variable version of "()", and now it silently switches to that one (under one interpretation of your words). > I guess one important issue is whether this is a visibility issue at > all. This is more like the prefix-notation than like a visibility > lookup. So I see it more as "selecting" the right "()" operator > rather than using normal overload resolution. Clearly the devil will > be in the wording for this one, if we decide we want this > functionality. Seems like we need this functionality, or the whole feature is rather crippled. > > Hmm. I'm inclined to always use a subtype_indication, for > > readability. Should we require that? > > If so, we can change "OF" to "IN" in the syntax, which seems a > > slight improvement. > > I don't agree. Making the presence of a subtype_indication completely > change the meaning seems weird to me. > I think we want the syntax distinction as well. A matter of taste, I suppose. (But ": subtype_indication" is syntax!) We could still consider requiring the subtype_indication and keep "of". What do you think? I'm not sure. Maybe there are cases where the type is so obvious you want to leave it out. > I think you naturally talk about the elements "of" a container, at > least as often as you talk about the elements "in" a container. > For arrays, we talk about an array "of" integers, not an array > "containing" integers. I realize that is using "of" in the opposite > sense, but "of" is a more symmetrical preposition than "in" in any > case. Anne of Green Gables. Green Gables of Anne. > Both make sense. ;-) Well, if nobody agrees with me, I'll give in. It's not important. (Note that I still sometimes type "case X of^H^His" N years after leaving the Pascal world for Ada. ;-)) **************************************************************** From: Bob Duff Sent: Friday, February 12, 2010 3:38 PM > But you can also exit a loop prematurely using a "goto", a "return", a > "requeue", an "exit" to an outer loop, etc. > You put that all together and it creates a need to return a > "continuation," and I think that is a bit too radical, even for Ada > 2012. ;-) Actually, all you really need is an implementation of all those goto-like things in terms of exceptions. By the way, the most elegant iterators I've ever seen are in Sather, which is a little-known follow-on to Eiffel. (I think Sather is the name of a tower near Stanford U. Get it?) A little too elegant, in fact. **************************************************************** From: Tucker Taft Sent: Friday, February 12, 2010 3:54 PM >> You don't like the "+" operator for this? > > I think it's an ugly hack. But I do tolerate it. > > It's kind of annoying that this is a syntax error: > > X : Unbounded_String := +"Hello"; > Y : Unbounded_String := X & +", world."; You presumably don't need to use "+" for an operand of "&" since you can overload that directly on String. > >> I suppose we could define another new operator for something like >> this, or an aspect/attribute. >> If you have something in mind, go ahead and suggest it. I'm pretty >> content with using "+". > > I don't like the "new operator" idea. That's just a different-looking > but equally-ugly hack. > > My idea would be to have an aspect like Literal_Value that's called > implicitly, so: > > X : T := "Hello"; > Y : T2 := 123; > > is equivalent to > > X : T := T'Literal_Value("Hello"); > Y : T2 := T'Literal_Value("123"); > > This AI is all about syntactic sugar, so I thought maybe I could sneak > this in. ;-) With a single Literal_Value aspect/attribute, it seems hard to know which of the following should be legal for a given type: Y : T2 := "123"; or Y : T2 := 123; You might find my ideas about this in my pie-in-the-sky language "ParaSail" interesting: http://parasail-programming-language.blogspot.com/2009/12/parasail-character-string-and-numeric.html and http://parasail-programming-language.blogspot.com/2009/12/parasail-universal-types-in-annotations.html These rely on being able to specify universal types explicitly in the declarations of certain operators. But perhaps something analogous could be done in Ada. > ... >>> It seems annoying to restrict it to tagged types. >>> Is that really necessary? >> Probably not absolutely necessary. Since we only have prefix >> notation for tagged types, it seemed natural to piggy-back on that. >> But these are really pretty different, I suppose, so we could >> consider defining these for untagged types. > > OK, good. Let's try to do that. > >>> I see below that cursor types will have to be tagged to take >>> advantage of this notation. That's even more annoying, because >>> cursors can often be quite lightweight. >> I didn't mean for cursors to have to be tagged. >> Where does that requirement come in? It was not intentional. > > They have to be tagged if you want to use "()", which I think is > common, isn't it? Only the container needed to be tagged, not the "index" type (which is where the Cursor fits in). If I stated otherwise, that wasn't my intent. >>> Do you mean "allows a constant" or "requires a constant"? >>> If the former, I think it introduces a Beaujolias effect. >>> >>> Would it make any sense to instead resolve on the basis of whether >>> the context requires a variable? >>> So for "X(C) := X(C) + 1;" the first would resolve to the variable >>> one, and the second to the constant one. >> That might be better. > > But it doesn't work if you rename X(C), right? Yes, in a rename you would clearly want to make it a variable if C were a variable, so you are back to looking at the variableness of C, so you might as well always use that as the determining factor. >> ...I know this concerned Randy as well. >> I doubt if there is a Beaujolais effect, because this preference >> would presumably only apply to operators declared in the same scope. >> If the preference might affect resolution across packages, that would >> definitely be an issue. > > It's still a Beaujolais effect if it's within the same package. You > could argue that such Beaujolais effects are not so bad. I don't follow that. You'll have to show me an example. > I'm thinking that you have just the constant one. > X is a variable, and you say "Put(X(C));". > Then you add the variable version of "()", and now it silently > switches to that one (under one interpretation of your words). Oh, I see. That hardly seems like a maintenance issue. That almost seems more like a feature. ... >>> Hmm. I'm inclined to always use a subtype_indication, for >>> readability. Should we require that? >>> If so, we can change "OF" to "IN" in the syntax, which seems a >>> slight improvement. >> I don't agree. Making the presence of a subtype_indication >> completely change the meaning seems weird to me. >> I think we want the syntax distinction as well. > > A matter of taste, I suppose. (But ": subtype_indication" > is syntax!) > > We could still consider requiring the subtype_indication and keep > "of". What do you think? I'm not sure. > Maybe there are cases where the type is so obvious you want to leave > it out. For an array it would be annoying I would think to have to specify the component subtype in an iterator over its components, particularly if it is a long-winded name. It feels like an annoying "test" that doesn't (im)prove anything. **************************************************************** From: Randy Brukardt Sent: Friday, February 12, 2010 3:44 PM ... > My idea would be to have an aspect like Literal_Value that's called > implicitly, so: > > X : T := "Hello"; > Y : T2 := 123; > > is equivalent to > > X : T := T'Literal_Value("Hello"); > Y : T2 := T'Literal_Value("123"); > > This AI is all about syntactic sugar, so I thought maybe I could sneak > this in. ;-) I'm in favor of this idea. But we looked at it last time, and concluded that it would make most Unbounded_String operations ambiguous to call with literals. So the #1 use for it wouldn't work. Consider Append, for instance. It is defined as: procedure Append (Source : in out Unbounded_String; New_Item : in Unbounded_String); -- (1) procedure Append (Source : in out Unbounded_String; New_Item : in String); --(2) procedure Append (Source : in out Unbounded_String; New_Item : in Character); So a call Append (UBS, "Whatever"); works today. If you add an automatic conversion possibility, this call becomes ambiguous, as both (1) and (2) would match. The only way out would be a preference rule, and those are typically trouble. P.S. I'd rather discuss the various unrelated subjects separately, so that people can comment on the one thing that interests them and not a litany of other things. Thus several separate answers. **************************************************************** From: Bob Duff Sent: Friday, February 12, 2010 4:03 PM > I'm in favor of this idea. But we looked at it last time, and > concluded that it would make most Unbounded_String operations > ambiguous to call with literals. So the #1 use for it wouldn't work. Good point. And there is probably lots of existing code that has the same problem -- people add lots of overloadings precisely to get around the lack of user-defined literals. On the other hand, it would be useful for new packages. > If you add an automatic conversion possibility, this call becomes > ambiguous, as both (1) and (2) would match. The only way out would be > a preference rule, and those are typically trouble. Well, Tucker has argued recently that Beaujolais effects aren't so bad (or shouldn't be considered Beaujolais effects) if they're within a single package. Hmm... Or we could remove the offending overloadings. That would be incompatible, but not very much so... Or we could add Ada.Strings.New_Improved_Unbounded_Strings. Only half kidding here... **************************************************************** From: Randy Brukardt Sent: Friday, February 12, 2010 3:54 PM > > Probably not absolutely necessary. Since we only have prefix > > notation for tagged types, it seemed natural to piggy-back on that. > > But these are really pretty different, I suppose, so we could > > consider defining these for untagged types. > > OK, good. Let's try to do that. I think it would be a good idea, but I don't see any way to do that without losing all of the existing magic that Tucker is leaning on. Currently, he has: Container(...) equivalent to Container."()"(...) But of course the latter form isn't allowed for untagged types (for various good reasons). We could of course say the equivalence is instead to: "()"(Container, ...) But that would prevent the use of the magic of prefix notation: (1) No use-clause needed for visibility; (2) Automatic lookup for class-wide operations; (3) Automatic addition of .all and 'Access as necessary. (2) of course doesn't matter for untagged types, but surely the other two do. Tucker's example in fact requires the automatic addition of 'Access. You'd never want to allow that on untagged types (unless the objects are aliased). We could duplicate all of that magic, but that seems like a mess. Or we could try to allow prefix notation on untagged types, but we already *know* that's a mess. > > > I see below that cursor types will have to be tagged to take > > > advantage of this notation. That's even more annoying, because > > > cursors can often be quite lightweight. > > > > I didn't mean for cursors to have to be tagged. > > Where does that requirement come in? It was not intentional. > > They have to be tagged if you want to use "()", which I think is > common, isn't it? You don't want to use "()" on a cursor, the cursor is the parameter. The parameter has no restrictions. And in any case, if you allow untagged "()" (which is appealing), then it doesn't matter. **************************************************************** From: Randy Brukardt Sent: Friday, February 12, 2010 4:02 PM ... > > > Do you mean "allows a constant" or "requires a constant"? > > > If the former, I think it introduces a Beaujolias effect. > > > > > > Would it make any sense to instead resolve on the basis of whether > > > the context requires a variable? > > > So for "X(C) := X(C) + 1;" the first would resolve to the variable > > > one, and the second to the constant one. > > > > That might be better. > > But it doesn't work if you rename X(C), right? I read this whole section as meaning that resolution already did this, not that you were proposing to change resolution. > >...I know this concerned Randy as well. > > I doubt if there is a Beaujolais effect, because this preference > >would presumably only apply to operators declared in the same scope. > >If the preference might affect resolution across packages, that > >would definitely be an issue. > > It's still a Beaujolais effect if it's within the same package. You > could argue that such Beaujolais effects are not so bad. But the "()" doesn't have to be in the same package, it just has to be in an ancestor (think class-wide lookup). And if you have two forms, they could be in different packages. Note that the alternative of *requiring* const/var matching would be pain if all you want is to define read access (which I think will be fairly common). In that case you'd still need to define two routines with the same result type. We could "fix" both problems by getting rid of the classwide lookup and requiring both routines to always be declared together (only as primitive operations of a type, and both are required). That seems like overkill, however. > I'm thinking that you have just the constant one. > X is a variable, and you say "Put(X(C));". > Then you add the variable version of "()", and now it silently > switches to that one (under one interpretation of your words). Right. The rule I suggest above would fix that, by not allowing there not to be a variable one. But it would clearly be a pain. > > I guess one important issue is whether this is a visibility issue at > > all. This is more like the prefix-notation than like a visibility > > lookup. So I see it more as "selecting" the right "()" operator > > rather than using normal overload resolution. Clearly the devil > > will be in the wording for this one, if we decide we want this > > functionality. > > Seems like we need this functionality, or the whole feature is rather > crippled. That's true, it's more like prefix-notation of an operator. One could simply duplicate the whole set of rules for prefix calls, and change the ones that we don't like or need. Sounds like a bigger mess, though. **************************************************************** From: Tucker Taft Sent: Friday, February 12, 2010 4:08 PM Can you remind me of some of the "good reasons" why prefix notation is not permitted for untagged types? I can't remember the trouble they caused... **************************************************************** From: Randy Brukardt Sent: Friday, February 12, 2010 4:31 PM > Can you remind me of some of the "good reasons" > why prefix notation is not permitted for untagged types? I can't > remember the trouble they caused... Gee, I don't remember either. I just remember we had problems and eventually gave up and just restricted to tagged. Better go look in the old AIs. Nice that we index them from the text changes: the numbers in question are AI95-0252 and AI95-0407 (based on 4.1.3(9.2/2) - there's a more recent fix to that, too, but it's not relevant). AI-0252 just has a paragraph at the end of the discussion: We originally generalized this to support non-tagged types, but the added complexity this brought to handling access types seemed more than the anticipated benefit, since we would have to consider primitives of the access type itself as well as those of its designated type. I think the problem comes about because of the automatic dereference. And I recall some pretty hairy examples of what that would cause. Probably have to go look up the minutes and/or read the old e-mail to recall the full extent. I'll leave that to you. (It strikes me that limiting "()" to composite types might effectively fix that problem.) **************************************************************** From: Tucker Taft Sent: Friday, February 12, 2010 5:31 PM I also remember it being related to access types. If we stick to composite types for "()" that would seem to avoid the problem. Of course a private type might turn out to be an access type. Not sure what mischief that would cause... **************************************************************** From: Randy Brukardt Sent: Monday, February 15, 2010 10:17 PM > I also remember it being related to access types. > If we stick to composite types for "()" that would seem to avoid the > problem. Of course a private type might turn out to be an access > type. Not sure what mischief that would cause... I thought of that, too. But I don't think that has to be a problem; "()" has to be primitive as you currently have it, and thus we could make it illegal to define it on an elementary type (even if we don't find out the type is elementary until the full definition -- it still has to be in the same package). I don't think that there is a problem if you *never* know that the type is elementary: package P is type Priv is private; private type Priv is access ... end P; with P; package Q is type My_Type is new P.Priv; function "()" (O : aliased in out My_Type; ...)... end Q; My_Type will never appear to be an access type to any user, even in the body of P, so you'll never have to consider ".all" of it. But this is sort of a last resort; if we can find a way to make these work without such a rule, that would be better still. **************************************************************** From: Bob Duff Sent: Friday, February 12, 2010 12:57 PM > > Y : Unbounded_String := X & +", world."; > > You presumably don't need to use "+" for an operand of "&" since you > can overload that directly on String. True, but the whole point of having user-defined literals is to avoid having so many overloadings that just do "convert some args from String to T and then do the real work". > With a single Literal_Value aspect/attribute, it seems hard to know > which of the following should be legal for a given type: > > Y : T2 := "123"; > or > Y : T2 := 123; Maybe both? > You might find my ideas about this in my pie-in-the-sky language > "ParaSail" interesting: I'm sure I would. I keep intending to pay attention to that, but I never seem to find the time. By the way, a blog seems like a rather unsuitable way to communicate a language design. How about giving me a Parasail Reference Manual? ;-) > Only the container needed to be tagged, not the "index" type (which is > where the Cursor fits in). > If I stated otherwise, that wasn't my intent. I guess I misunderstood. > > I'm thinking that you have just the constant one. > > X is a variable, and you say "Put(X(C));". > > Then you add the variable version of "()", and now it silently > > switches to that one (under one interpretation of your words). > > Oh, I see. That hardly seems like a maintenance issue. That almost > seems more like a feature. Perhaps it is. By the way, I guess you're normally going to have two "()" ops, and their bodies are going to be identical. That's a little bit annoying. > For an array it would be annoying I would think to have to specify the > component subtype in an iterator over its components, particularly if > it is a long-winded name. > It feels like an annoying "test" that doesn't (im)prove anything. Well, I suppose can buy that, but I'm not sure why you say "For an array". If it's annoying, isn't it equally annoying for any container-of-T, to have to specify T? Anyway, I'm happy to be _allowed_ to specify T. I think "for C: Character of S loop" is nicely readable. When I do "Iterate(X, Action => Blah'Access);" I am annoyed that Blah is in the wrong place, and that it's not anonymous, and that I have to say 'Access. But I'm not particularly annoyed that Blah's parameter's type is explicit. **************************************************************** From: Randy Brukardt Sent: Friday, February 12, 2010 7:48 PM > True, but the whole point of having user-defined literals is to avoid > having so many overloadings that just do "convert some args from > String to T and then do the real work". OK, but you started this discussion with add this "so unbounded strings can be tolerable". But unbounded string *have* all of those overloadings, and we surely aren't going to remove them (the compatibility cost would be horrendous). Now, it would make sense to create a new package by taking Unbounded_Strings, giving the package a new name, replacing all occurrences of "String" by "Unbounded_String", and removing all of the duplicate routines. (We have to do this replacement because there are a number of routines that *only* take String parameters, and those have to be converted.) Then adding the new "()" and literals routines. But I tried such a suggestion last time and it got no traction (I was told "Unbounded_Strings" is good enough). And we tried a proposal much like this and abandoned it because it wouldn't help Unbounded_Strings. Thus I can see why this would be a good idea, but since we wouldn't be able to use it for anything (predefined), it's hard to imagine that it would be worth the effort. **************************************************************** From: Tucker Taft Sent: Friday, February 12, 2010 10:49 PM > By the way, I guess you're normally going to have two "()" ops, and > their bodies are going to be identical. That's a little bit > annoying... I don't see them as identical in some cases. A read-only reference can be cheaper to manage than a read-write reference. There may be less or different locking, and if you are talking about a persistent data structure, there is no need to mark the page with the given element as "dirty" if the reference is read-only. **************************************************************** From: Randy Brukardt Sent: Monday, February 15, 2010 10:18 PM > I also remember it being related to access types. > If we stick to composite types for "()" that would seem to avoid the > problem. Of course a private type might turn out to be an access > type. Not sure what mischief that would cause... I thought of that, too. But I don't think that has to be a problem; "()" has to be primitive as you currently have it, and thus we could make it illegal to define it on an elementary type (even if we don't find out the type is elementary until the full definition -- it still has to be in the same package). I don't think that there is a problem if you *never* know that the type is elementary: package P is type Priv is private; private type Priv is access ... end P; with P; package Q is type My_Type is new P.Priv; function "()" (O : aliased in out My_Type; ...)... end Q; My_Type will never appear to be an access type to any user, even in the body of P, so you'll never have to consider ".all" of it. But this is sort of a last resort; if we can find a way to make these work without such a rule, that would be better still. **************************************************************** From: Brad Moore Sent: Wednesday, February 17, 2010 11:33 PM > > As usual, any and all comments welcome. I am very much in favor of the intent behind this proposal. I have have some suggestions however, that might further improve things. I admit I haven't fully thought these ideas through, but I wanted to see if they had any appeal. In a nutshell, if possible, I would like to see; 1) Syntax even more harmonious with existing array/iterator syntax. 2) An optional extension to the existing loop syntax to allow more than one type of "magic" expansion. Essentially, allowing the programmer to provide/override the implementation of the loop. For point 1, I am wondering if it would make sense to have syntax like; for I in Container'Range loop Container (I) := Container (I) + 1; end loop; instead of; for Cursor in Iterate(Container) loop Container(Cursor) := Container(Cursor) + 1; end loop; This would be more in line with existing syntax for iterating through arrays. In this idea, the 'Range attribute would be user definable for a container. Something like; package Ada.Containers.Doubly_Linked_Lists is type List is tagged private; type Cursor is private; function First (Container : List) return Cursor; function Last (Container : List) return Cursor; package List_Range is new Ada.Range_Attribute (Index_Type => Cursor, Start => First, Finish => Last); for List'Range use List_Range.Magical_Thingy If such a construct is even possible, then perhaps one could also write Position1 : Cursor := ...; Position2 : Cursor := ...; for I in Position1 .. Position2 loop Container (I) := Container (I) + 1; end loop; Which seems useful. > a) The first syntax is intended for iterating over the set of cursors > or indices that select elements of a container. This is most > analogous to what is already available for arrays by writing "for I in > Arr'Range loop": > > for Cursor in Iterate(Container) loop > Container(Cursor) := Container(Cursor) + 1; > end loop; > > What the above presumes is that the function Iterate returns an object > of a type derived from a "magic" iterator type (see below). The > iterator type provides a First and Next operation which return a > Cursor type, and a No_Element cursor value to indicate end of > iteration. The above expands into: > > declare > _Iter : Iterator_Type := Iterate(Container); > Cursor : Cursor_Type := First(_Iter); > begin > while Cursor /= No_Element loop > Container(Cursor) := Container(Cursor) + 1; > -- This presumes an appropriate "()" operator > -- is defined. > > Cursor := Next(_iter, Cursor); > end loop; > end; > Could not this also expand to; declare procedure Iterate (Position : Lists.Cursor) is begin Container (Cursor) := Container (Cursor) + 1; end Iterate; begin Container.Iterate (Iterate'Access); end; Then there is less "magic" needed it seems to me. There is no need to involve an iterator interface with a function returning the First cursor, and other function returning the Next. This way, the expansion also works for containers where it doesn't make as much sense to start at the first element. For example, a recursive binary tree container would probably want to start the iteration at the root node of the tree, rather than start at the left most leaf node. If we can assume such an Iterate function has been defined, then the container implementation can decide how best to iterate through the container. As a further example, I have implemented a parallel recursive binary tree container which makes even less sense to think of starting the iteration at First, and iterating through the elements using Next. I do provide an Iterate procedure that manages the parallelism. > b) The second syntax is intended for iterating directly over the > elements of a container or an array: > > for Element of Container loop > Element := Element + 1; > end loop; > > or > > for Element of Arr loop > Element := Element + 1; > end loop; > These forms could also expand to the same expansion I suggested above, could they not? ---------------- Now the the second main suggestion. I would ideally like to see flexibility in the syntax so that the compiler could generate different expansions depending on the magical interface utilized by the programmer. For example, the expansions so far discussed in this AI center around making it easier to write iterators for containers, using a sequential approach. I would ideally like to be able to see expansions that would allow parallel executions through loops, including containers, arrays, and simple loops. The idea is that loop syntax would allow the programmer to optionally specify the name of a procedure/function used to implement the loop. Perhaps this procedure or function would be associated with a magical interface from a set of several available/defined interfaces so that the interface associated with the procedure would indicate to the compiler how the code should be expanded. For example, For the case of sequentially iterating through a container, one could optionally write something like; for I in Container'Range with Container.Iterate loop Container (I) := Container (I) + 1; end loop; The with clause allows the programmer to specify the name of the procedure (implementation) to use for executing the iteration. This could default to name a primitive of the container such as "Iterate", which would be used if this was not specified by the programmer. i.e. for I in Container'Range loop ... implies "with Container.Iterate" since that was not specified. Perhaps a different expansion would be like the one Tucker suggests for when you have a container where First and Next make sense, and where we would not want to expose an Iterate procedure, since the new syntax makes it easier to iterate through the container. Now consider a simple loop that we would like to execute in parallel. declare Sum : Integer := 0; begin for I in 1 .. 100_000_000 loop Sum := Sum + I; end loop end; We could use similar syntactic sugar to allow the programmer to write this as something like; with Integer_Addition_Reducer; -- an instantiation of a generic procedure declare Sum : Integer := 0; begin for I in 1 .. 100_000_000 with Integer_Addition_Reducer loop Sum := Sum + I; end loop end; In this fantasy syntax, the variables enclosed by angle brackets (if any) represent variables that are modified by the loop but are declared in scope outside the loop. The generic needs to treat these specially to avoid race conditions when executing the loop in parallel. This might expand to; declare Sum : Integer := 0; begin declare procedure Iteration (Start, Finish : Positive; Sum : in out Integer) is begin for I in Start .. Finish loop Sum := Sum + I; end loop; end Iteration; begin Integer_Addition_Reducer (From => 1, To => 100_000_000, Process => Iteration'Access, Item => Sum); end; I am currently working on a paper that utilizes such generics to assist in writing parallel loop iterations and parallel recursion. I am planning to present this at Ada Europe in Valencia, if anyone is interested. While I see these generics being useful without the benefit of syntactic sugar, some added sweetener would be nice, particularly if it is compatible with whatever syntax is used for regular container iterators. **************************************************************** From: Randy Brukardt Sent: Thursday, February 18, 2010 12:47 AM ... > > > As usual, any and all comments welcome. > > I am very much in favor of the intent behind this proposal. Good. > I have have some suggestions however, that might further improve > things. I admit I haven't fully thought these ideas through, but I > wanted to see if they had any appeal. ... > For point 1, I am wondering if it would make sense to have syntax > like; > > for I in Container'Range loop > Container (I) := Container (I) + 1; > end loop; > > instead of; > > for Cursor in Iterate(Container) loop > Container(Cursor) := Container(Cursor) + 1; > end loop; Not really. The first form would only allow one iterator per container, and that would be a mistake. For instance, in the multiway tree containers, you might want to iterate on a particular set of children (starting from a particular node) or on the entire tree. That implies that we need multiple iterators and we need the capability of additional parameters. You also want the possibility of local storage for the iterator (such storage can't be in the container or you are restricting yourself to a single iteration at a time). Also recall that we have prefix notation, so the proposed form could easily be written: for I in Container.Iterate loop Container(I) := Container(I) + 1; end loop; which is hardly different. ... > If such a construct is even possible, then perhaps one could also > write > > Position1 : Cursor := ...; > Position2 : Cursor := ...; > > for I in Position1 .. Position2 loop > Container (I) := Container (I) + 1; > end loop; > > Which seems useful. You can write such an iterator easily. Indeed, I proposed one for the list container: for I in Container.Partial_Iterate (Position1, Position2) loop Container (I) := Container (I) + 1; end loop; Here I'm using the possibility for parameters to the iterator creation function to control the iteration. ... > Could not this also expand to; > > declare > procedure Iterate (Position : Lists.Cursor) is > begin > Container (Cursor) := Container (Cursor) + 1; > end Iterate; > begin > Container.Iterate (Iterate'Access); > end; No, as Bob and Tucker were talking about the other day, the loop has to support Exit (including multi-level exits) and Goto and all of the other transfers of control. A goto can't leave a subprogram body! ... > This way, the expansion also works for containers where it doesn't > make as much sense to start at the first element. For example, a > recursive binary tree container would probably want to start the > iteration at the root node of the tree, rather than start at the left > most leaf node. I don't see this, as you can define First however you need for the iterator object. An iterator is *not* a container, and you don't have to use the same definition of First for the iterator as for the container. As Tucker notes, there is a single default iterator that allows you to use special syntax, but that is far too limiting for a general concept. > If we can > assume such an Iterate function has been defined, then the container > implementation can decide how best to iterate through the container. That's already true; the "Next" function can do whatever is appropriate for a particular iterator. It doesn't have to iterate in a particular order (although I would expect that most iterators would indeed have such an order). > As a further example, I have > implemented a parallel recursive binary tree container which makes > even less sense to think of starting the iteration at First, and > iterating through the elements using Next. I do provide an Iterate > procedure that manages the parallelism. A procedure seems appropriate for that, as it must not have an dependency between iterators nor any transfers of control. With a procedure body, dependencies are tough to write and exceptions are the only transfer of control. The body of a loop is a very different animal; it's easily nested in a declare block, and exits and returns are commonly used. > These forms could also expand to the same expansion I suggested above, > could they not? No, for the same reasons as noted above. > ---------------- > > Now the the second main suggestion. > > I would ideally like to see flexibility in the syntax so that the > compiler could generate different expansions depending on the magical > interface utilized by the programmer. For example, the expansions so > far discussed in this AI center around making it easier to write > iterators for containers, using a sequential approach. > I would ideally like to be able to see expansions that would allow > parallel executions through loops, including containers, arrays, and > simple loops. The idea is that loop syntax would allow the programmer > to optionally specify the name of a procedure/function used to > implement the loop. > Perhaps this procedure or function would be associated with a magical > interface from a set of several available/defined interfaces so that > the interface associated with the procedure would indicate to the > compiler how the code should be expanded. I think that parallel loops would need to be syntactically marked, since the loop bodies would have to be written with a restricted set of features: no dependence between iterations, use of atomic variables/protected objects for sums and the like, and limited or no transfers of control. In an ideal world, those restrictions would be enforced by the compiler. > For example, > For the case of sequentially iterating through a container, one could > optionally write something like; > > for I in Container'Range with Container.Iterate loop > Container (I) := Container (I) + 1; > end loop; Huh? Now you're proposing something more complex to write and use than the current proposal, which is: for I in Container.Iterate loop Container (I) := Container (I) + 1; end loop; And if you want to use another iterator for the same container: for I in Container.Child_Iterate (Some_Cursor) loop Container (I) := Container (I) + 1; end loop; You're setting up a straw man and then knocking it down and proposing something more complex. A parallel loop construct isn't a bad idea, but it doesn't make much sense by itself. You'd want to define other constructs that also could be executed in parallel (parallel blocks and the like). Remember that Ada doesn't allow anything to be executed in parallel other than tasks; you'd surely want to be able to make function calls in parallel. And for all of those things, you'd want to define various restrictions on the statements in the loop/block/subprogram body. That requires dedicated syntax so that the compiler and/or reader can know about those restrictions. Please don't kill this nice sequential proposal by loading up all kinds of parallel cruft on it. **************************************************************** From: Tucker Taft Sent: Thursday, February 18, 2010 8:09 AM I agree with Randy's comments on Brad's suggestions. **************************************************************** From: Randy Brukardt Sent: Thursday, February 18, 2010 9:30 PM > > By the way, I guess you're normally going to have two "()" ops, and > > their bodies are going to be identical. That's a little bit > > annoying... > > I don't see them as identical in some cases. > A read-only reference can be cheaper to manage than a read-write > reference. There may be less or different locking, and if you are > talking about a persistent data structure, there is no need to mark > the page with the given element as "dirty" if the reference is > read-only. I've been thinking about this problem some more. I had almost convinced myself that this would work with some tweaks when I realized that there is an additional problem with this idea of having two function "()"s differing only by the constantness of the first parameter: we would have to allow homographs to be declared. For instance: function "()" (Container : access constant Integer_Container; Index : in Natural) return Integer; and function "()" (Container : access Integer_Container; Index : in Natural) return Integer; are homographs. In order for this resolution trick to work, we'd have to allow homographs (preferably only in this particular case!). That doesn't have much impact on the intended use, presuming that we give the prefix form Obj(Ind) special powers. However, it does have an impact on other uses of the functions. Any uses of the name "()" or Pack."()" are going to be ambiguous, because there will be two matching homographs (resolution uses type conformance to tell differences, which these surely are). For instance, normal calls like Pack."()"(Obj, Ind); won't resolve. We could solve that by making the resolution rules depend on the name of the subprogram (which seems like a nasty wart - renames would work differently than the original subprograms), or by making a more general resolution change (which surely would have Beuajolias effects and possibly incompatibilities). We would also have similar problems with renames and uses as generic formal parameters. (The last isn't particularly important because a formal parameter couldn't be named "()" -- it is never primitive. But that too is weird.) I'm wondering if the proposal has gone off the rails a bit here by trying to have this new kind of subprogram with special resolution. It would be better if such magic was completely confined to the index-like call (which is new and isn't going to be a compatibility problem). As soon as we have an equivalent "normal" call, things start getting messy. So I've been thinking about alternatives. The obvious one would be to make these things a pair of aspects (I'm thinking "Variable_Indexing" and "Constant_Indexing"). Using aspects has several advantages: * The routines are clearly tied to the type, so there isn't any big problem supporting untagged types (or even scalar types); * For normal calls, users use the "real" name of the functions, so there isn't any resolution changes beyond the new magic notation; * We don't need any funny rules about where the routines are declared -- the need to specify the aspects would provide the appropriate result; * It's not a problem to give the same function (name) to both aspects, so there is no need to worry about having to duplicate the body; * Legality of calls would depend only on whether an appropriate profile exists for the appropriate aspect (and of course the normal rules for parameters); * Changing the aspects of a type declaration surely has the possibility of causing errors/changes down the line -- there is no doubt that it isn't Beaujolais or anything like it. OTOH, these would be pretty weird aspects: * The profile isn't fixed; it only needs the first parameter to be of the type; * There shouldn't be any limit to the number of routines specified; the only requirement beyond the first bullet is that the routines are not homographs. If we were to go this way, the "magic" *indexed call* would have at most two possible places to look for an operation: at the aspects of the prefix type, and if that is an access type, at the aspects of the designated type of the prefix type. The aspect to use would depend on whether or not the view of the prefix is a constant view (note that the object itself and the designated type could differ on that: dereferencing an access-to-constant variable would give a constant view.) Neither of these seem to have any particular difficulty. That would give one a set of profiles to resolve in the typical way: and no significant changes to resolution. One could imagine that arrays have a predefined version of these index aspects, with the obvious semantics; that would unify the syntax/semantics and avoid problems if someone tries to define these on an array type (otherwise we'd potentially have an ambiguity). Those would name a pair of anonymous functions like: function (Arr : in Array_Type; Index : in Index_Subtype) return Component_Type; function (Arr : access Array_Type; Index : in Index_Subtype) return access Ref_Component_Type; (The latter is a reference type.) If we go this way, I wonder if we would want a way to specify this aspect in generic contracts (it might make sense to be able to using indexing calls in a generic body). Anyway, food for thought. **************************************************************** From: Tucker Taft Sent: Thursday, February 18, 2010 10:23 PM I agree the "()" idea seems to be creating several resolution issues. Using an aspect specification may be cleaner. If we are using an aspect specification for the Default_Iterator, it might be preferable to stick with that approach for other syntactic sugaring. The aspect specification might just be specifying the name of the Variable_Indexing and Constant_Indexing functions, which could then be overloaded as desired to provide multiple indexing functions. **************************************************************** From: Brad Moore Sent: Friday, February 19, 2010 10:38 PM > > This way, the expansion also works for containers where it doesn't > > make as much sense to start at the first element. For example, a > > recursive binary tree container would probably want to start the > > iteration at the root node of the tree, rather than start at the > > left most leaf node. > > I don't see this, as you can define First however you need for the > iterator object. An iterator is *not* a container, and you don't have > to use the same definition of First for the iterator as for the > container. As Tucker notes, there is a single default iterator that > allows you to use special syntax, but that is far too limiting for a > general concept. I was thinking of a *recursive* binary tree container. I am having a difficult time seeing how one could use First and Next recursively to iterate through a tree. It is fairly easy to iterate through a tree non-recursively, and return a cursor to where the iteration last left off, and then picking up from where the iteration left off, with a call to Next. But I think this would be hard to do using a recursive stack-based approach. I suppose though, that for such a container, one would just have to use the Iterate procedure and not bother with this iterator syntax. If someone really wants to support the iterator syntax for such a container, they could provide a non-recursive First and Next as you suggest. > > As a further example, I have > > implemented a parallel recursive binary tree container which makes > > even less sense to think of starting the iteration at First, and > > iterating through the elements using Next. I do provide an Iterate > > procedure that manages the parallelism. > > A procedure seems appropriate for that, as it must not have an > dependency between iterators nor any transfers of control. With a > procedure body, dependencies are tough to write and exceptions are the > only transfer of control. Perhaps the proposed Preconditions, Postconditions would make it easier to specify the dependency rules. > I think that parallel loops would need to be syntactically marked, > since the loop bodies would have to be written with a restricted set > of features: no dependence between iterations, use of atomic > variables/protected objects for sums and the like, and limited or no > transfers of control. In an ideal world, those restrictions would be > enforced by the compiler. > A parallel loop construct isn't a bad idea, but it doesn't make much > sense by itself. You'd want to define other constructs that also could > be executed in parallel (parallel blocks and the like). I also have implemented generics that similarly make it easier to do parallel recursion. This allows me to write parallel functions and parallel procedures. Syntactic sugar might be able to provide a similar expansion to the parallel looping generics. The expansion is somewhat similar, but involves different generics. You have convinced me that it is probably not a good approach to try to unify the syntax between sequential iterators and parallel iterators. I am now thinking that instead, it would be better to try to unify syntax between parallel loops and parallel recursion. I think that a pragma based approach might work better. eg. Parallel looping: Sum : Integer := 0; for I in 1 .. 100_000_000 Sum := Sum + I; end loop; pragma Parallel_Loop (Work_Seeking_Integer_Addition_Reducer, Sum); In this case, Work_Seeking_Integer_Addition_Reducer is the name of a instantiated generic procedure, and Sum is the name of a global variable referenced within the loop. could expand to; with Work_Seeking_Integer_Addition_Reducer; Sum : Integer := 0; declare procedure Iteration (Start : Integer; Finish : in out Integer; Others_Seeking_Work : not null access Parallel.Work_Seeking_State; Sum : in out Integer) is begin for I in Start .. Finish loop Sum := Sum + I; if Others_Seeking_Work.all then Others_Seeking_Work.all := False; Finish := I; exit; end if; end loop; end Iteration; begin Work_Seeking_Integer_Addition_Reducer (From => 1, To => Integer'Value (Command_Line.Argument (2)), Process => Iteration'Access, Item => Sum); end; eg. Parallel Recursion: function Fibonacci (Number : Natural) return Natural is begin if Number < 2 then return Number; else return Fibonacci (Number - 2) + Fibonacci (Number - 1); end if; end Fibonacci; pragma Parallel_Recursion (Fibonacci, Two_Way_Recursive_Integer_Addition.Parallel_Recursion); In this case, Fibonacci is the name of the function to be executed in parallel, and Two_Way_Recursive_Integer_Addition.Parallel_Recursion is the name of a instantiated generic package. This could expand to the following; Note that this seems like a fair amount of code, it is mostly a template that follows a repeatable pattern. I used this same pattern to create recursive parallel iteration through a binary tree. The expansion essentially creates two versions of the code. One is the sequential version, which you want to execute most of the time, and the second version is the parallel version which gets executed when worker tasks become available to do more work. The parallel version looks very similar to the sequential version, but instead of calling the function recursively, it calls an instantiated function which ultimately causes a split in execution between available processors. with Two_Way_Recursive_Integer_Addition; use Two_Way_Recursive_Integer_Addition; function Fibonacci (Value : Natural) return Natural is function Parallel_Fibonacci (Number : Natural) return Natural; Supervisor : aliased Parallel_Recursion.Overall_Work_Supervisor (Process => Parallel_Fibonacci'Access); Others_Seeking_Work : aliased Parallel_Recursion.Work_Seeking_State; use type Parallel_Recursion.Work_Seeking_State; function Parallel_Fibonacci (Number : Natural) return Natural is begin if Number < 2 then return Number; elsif not Others_Seeking_Work then return Parallel_Fibonacci (Number - 2) + Parallel_Fibonacci (Number - 1); else declare Reduction : aliased Parallel_Recursion.Work_Team_Manager; Sequential_Result : Natural; begin Sequential_Result := Parallel_Recursion.Recurse (Number - 2, Left, Reduction'Unchecked_Access, Supervisor) + Parallel_Recursion.Recurse (Number - 1, Right, Reduction'Unchecked_Access, Supervisor); -- Use the parallel result if work was split parallally, -- otherwise use the sequential result return Parallel_Recursion.Result (Reduction, Sequential_Result); end; end if; end Parallel_Fibonacci; Result : Integer := 0; begin Parallel_Recursion.Initialize (Supervisor'Unchecked_Access, Others_Seeking_Work'Unchecked_Access); Result := Parallel_Fibonacci (Value); Parallel_Recursion.Finalize (Supervisor); return Result; end Fibonacci; The pragmas would cause needed restrictions to be enforced within the parallel bodies. If a compiler vendor didn't want to support the parallel expansion, then they could ignore the pragma and the code would execute as it does today, sequentially. > Please don't kill this nice sequential proposal by loading up all > kinds of parallel cruft on it. It was definitely not my intent to kill this AI. I was looking to see if it could be expanded to cover both parallel and sequential iterators, since it seems on the surface that would be a fair bit of common ground between these two types of iterators. I see now that these are two vastly different beasts. I also wanted to minimize the chance that we might paint ourselves into a corner with new syntax that might preclude parallel support in the language in the future. I think you have convinced me that this is not a concern here. The question might be, is there anything that could be done will all this parallel cruft I have been rambling on about. Does it seem like a pragma based approach like I have suggested would be worth developing into an AI? Obviously, this is too late for the current amendment. We have a lot of AI's to deal with right now and so probably if these ideas have enough merit to develop into an AI, they should be more or less shelved until we get through the pile of other AI' waiting to be processed. I would certainly be willing to work on this in the background though, or at least I hope to create a white paper to better explain how these generics work. **************************************************************** From: Tucker Taft Sent: Tuesday, May 18, 2010 8:53 PM Here is an update to AI05-0139-2. [This is version /05 of the AI - Editor.] This does *not* have wording yet. It just fixes the !proposal part to match the changes specified in the minutes. I will work on the wording for the actual ARG meeting. The !proposal section might be worth reviewing in the upcoming ARG subgroup phone call. **************************************************************** From: Tucker Taft Sent: Thursday, June 10, 2010 11:58 PM OK folks, this is a doozy. I have produced some wording for the user-defined references, indexing, and iterators. The references and the indexing were pretty straightforward. Things got pretty hairy when I got to the iterators, because there were three different forms we were introducing, each with its own peculiarities. I think I have the rules right, but the presentation might benefit from some restructuring. [This is version /06 of the AI - Editor.] So hopefully this is enough for us to look into the correctness and completeness of the wording at the ARG meeting. We can then think about how better to present it. **************************************************************** From: Randy Brukardt Sent: Saturday, June 12, 2010 7:57 PM > OK folks, this is a doozy. You are right about that! A few comments noticed while posting this AI: In 4.1.6, you say "The aspects may not be overridden, but the functions they denote may be." I assume this is intended to be a Legality Rule (it's in introductory text, so I'm not sure how it is enforced). I think the reason you have this rule is so that indexing works on objects of T'Class: that requires dispatching and that means that the tag slot can't change for the indexing functions. However, this appears to be a generic contract model problem. We don't know whether or not these aspects were used when deriving from a generic formal, so it's not clear how the following works: generic type T is tagged private; package Gen is type NT is new T with private with Variable_Indexing => Vfoo, Constant_Indexing => Cfoo; -- Assume definitions of Vfoo and Cfoo here. end Gen; Is this specification legal? If Gen is instantiated with a type that has indexing already (such as Ada.Containers.Vectors), is the instance illegal? Etc. --- You've defined an indexable type as a user-defined tagged type with the aspects specified. Are we allowing these to be specified on abstract types and interfaces? Those are user-defined tagged types. I can see reasons for allowing that, but we then have to be careful that we don't call abstract routines. (That also adds additional cases to the generic above.) --- And why do you say "user-defined" in the indexable type definition? That seems to imply that the containers cannot be indexable, because they are defined by us, not the user. Clearly that is not what you meant, but why do we care who defined the type?? --- 5.5.1: I'm not a fan of "A /(reversible) iterator object/ is an object of a (reversible) iterator type." I was initially confused as to why you didn't define a "forward iterator object". But I eventually realized that you were being cute and are actually defining two terms with one sentence. I suppose the index will help a bit, but I suspect that many readers looking up "iterator object" will fail to see the definition here. --- The legality rules for an iteratable type seem to make the intended definition for Maps conflict with the rules for a default iterator. Specifically, I was expecting that we would define two pairs of indexing functions: one for cursors and the other for the key type, so that AI_Author_Map ("Taft") := 139; would be legal. But these rules only allow one index function. Shouldn't the rules be more flexible than that? Probably they should require the existence of an indexable function with the right profile, but allow the existence of others. **************************************************************** From: Tucker Taft Sent: Sunday, June 13, 2010 11:53 AM >> OK folks, this is a doozy. > > You are right about that! A few comments noticed while posting this AI: > > In 4.1.6, you say "The aspects may not be overridden, but the > functions they denote may be." I assume this is intended to be a > Legality Rule (it's in introductory text, so I'm not sure how it is enforced). Probably unnecessary. Your point about generic contract model seems to pretty well sink it anyway. > I think the reason you have this rule is so that indexing works on > objects of T'Class: that requires dispatching and that means that the > tag slot can't change for the indexing functions. That isn't really necessary, since even if the derived type happens to name a different function for indexing, the ancestor's ones will still exist. > However, this appears to be a generic contract model problem. We don't > know whether or not these aspects were used when deriving from a > generic formal, so it's not clear how the following works: > > generic > type T is tagged private; > package Gen is > type NT is new T with private with > Variable_Indexing => Vfoo, > Constant_Indexing => Cfoo; > -- Assume definitions of Vfoo and Cfoo here. > end Gen; > > Is this specification legal? If Gen is instantiated with a type that > has indexing already (such as Ada.Containers.Vectors), is the instance illegal? > Etc. Let's drop the rule. > > --- > > You've defined an indexable type as a user-defined tagged type with > the aspects specified. Are we allowing these to be specified on > abstract types and interfaces? Those are user-defined tagged types. I > can see reasons for allowing that, but we then have to be careful that > we don't call abstract routines. (That also adds additional cases to > the generic above.) I would presume you want to allow them on abstract types, so yes, we need to be careful not to allow calls on an abstract subprogram. Of course, the "equivalence" rules should take care of it... ;-) > > --- > > And why do you say "user-defined" in the indexable type definition? > That seems to imply that the containers cannot be indexable, because > they are defined by us, not the user. Clearly that is not what you > meant, but why do we care who defined the type?? We talk about user-defined assignment and finalization, even though we use those features in language-defined packages. It is "user-defined" as opposed to "predefined" operations. "User- defined" elsewhere in the manual means "not predefined" (see 3.2.3, for example) so I think it is OK to use that here as well. > --- > > 5.5.1: > > I'm not a fan of "A /(reversible) iterator object/ is an object of a > (reversible) iterator type." I was initially confused as to why you > didn't define a "forward iterator object". But I eventually realized > that you were being cute and are actually defining two terms with one > sentence. I suppose the index will help a bit, but I suspect that many > readers looking up "iterator object" will fail to see the definition here. I think some of the definitions can be eliminated. I put in more definitions than I actually used. > > --- > > The legality rules for an iteratable type seem to make the intended > definition for Maps conflict with the rules for a default iterator. > Specifically, I was expecting that we would define two pairs of > indexing functions: one for cursors and the other for the key type, so > that AI_Author_Map ("Taft") := 139; would be legal. But these rules only allow one index function. > Shouldn't the rules be more flexible than that? Probably they should > require the existence of an indexable function with the right profile, > but allow the existence of others. You can define two pairs of indexing functions, but only one of the pairs can be the "default" indexing functions. I can see that the wording is confusing. Constant_Indexing can denote multiple functions, but only one of them can have the additional requirements specified for an iteratable type's default constant indexing function. Perhaps the Legality Rules should be worded more as follows: Of the functions denoted by the Constant_Indexing aspect (if any) of an iteratable type, exactly one shall have the following properties: **************************************************************** From: Tucker Taft Sent: Monday, June 14, 2010 7:33 AM > ... >>> I think the reason you have this rule is so that indexing works on >>> objects of T'Class: that requires dispatching and that means that >>> the tag slot can't change for the indexing functions. >> That isn't really necessary, since even if the derived type happens >> to name a different function for indexing, the ancestor's ones will >> still exist. > > True, but it means that for > > type T is tagged ... with Variable_Indexing => F1; > type NT is new T with private with Variable_Indexing => F2. > > S : NT; > C : T'Class := T'Class(S); > > C(1) would call F1(C, 1) while S(1) would call F2(S, 1). That seems > confusing at best, since the objects have the same tag and value. > We've tried hard to avoid this sort of effect for regular dispatching, > I would think we'd want to do that here, too. We seem to have switched sides on this one! You had convinced me that if the user chooses to do this, it seems well-defined and not something we should necessarily try to prevent. You pointed out that there are contract model issues with trying to enforce a rule like this, so it just didn't seem important enough. Making a type indexable is a pretty big deal, and it seems unlikely that you would do something like the above by "mistake." So there seems no reason to try to prevent it just to be "friendly." Compilers could provide warnings, I suppose, though I doubt it is worth their effort. **************************************************************** From: Tucker Taft Sent: Monday, October 25, 2010 10:17 AM Here is the update to AI05-0139, based on the minutes. [This is version /07 of the AI - Editor.] Main changes are to use an Implicit_Dereference aspect rather than the "magic" interface Reference, and to try to make the wording more intelligible. Also, we now allow multi-dimensional arrays in an array component iterator, using the canonical ordering used for streams (last dimension varying fastest). **************************************************************** From: Tucker Taft Sent: Wednesday, March 16, 2011 4:48 PM My homework assignment included: AI05-0139-2: Add wording to define what happens when a private type with indexing is completed with an array type (which has default indexing). This is a non-issue, because only tagged types may have indexing aspects specified. **************************************************************** From: Tucker Taft Sent: Wednesday, March 16, 2011 10:07 PM Here is a very minor update to the AI on syntactic sugar for indexing, accessors, etc. Mainly we just made it clear that the associated aspects were type-related operational aspects. Most of the work was in AI-183. [This is version /09 of the AI - Editor.] The concern about indexing aspects applied to a private type that turned out to be an array type was a red herring -- these are only defined for tagged types. **************************************************************** From: Edmond Schonberg Sent: Tuesday, April 5, 2011 10:26 AM All the functions in Ada.Iterator_Interfaces should be abstract. **************************************************************** From: Edmond Schonberg Sent: Tuesday, April 5, 2011 10:36 AM In fact they are in the wording section, but not in the original presentation. i guess that part does not get edited further. **************************************************************** From: Edmond Schonberg Sent: Thursday, April 14, 2011 2:13 AM **************************************************************** From: Christoph Grein Sent: Thursday, April 14, 2011 2:13 AM The new chapter 5.5.1 uses the new term iteratable_name. I checked Webster's online to no avail, also my printed (heavy) version at home, also iterable isn't there. So it's a weak statement that I'm making - but: I think from the Latin origin, iterare, to walk, the correct derivation would be iterable (from iterabilis (don't know if it exists, but conforms to word construction in Latin); like curare, curabilis - curable). This term is also used in Java; and there is a reference to Webster's Revised Unabridged Dictionary in http://www.thefreedictionary.com/Iterable. **************************************************************** From: Martin Dowie Sent: Thursday, April 14, 2011 3:08 AM Some 'weak' evidence in favour of Christoph's suggestion... Google hits: Iterable: about 262,000 Iteratable: about 14,000 (and Google offers results for Iterable instead) Loads of Java, Scala, Android, Boost references to Iterable, so seems to be the de facto standard English word for this context. **************************************************************** From: Bob Duff Sent: Thursday, April 14, 2011 6:34 AM > The new chapter 5.5.1 uses the new term iteratable_name. I haven't looked at a dictionary, and I don't know Latin, but to my ears "iterable" sounds right, "iteratable" sounds wrong. Consider "irritable". **************************************************************** From: David C. Hoos, Sr. Sent: Thursday, April 14, 2011 6:45 AM http://hyperdictionary.com/search.aspx?define=iterable yields this (from Webster's 1913 dictionary) \It"er*a*ble\, a. [L. iterabilis. See {Iterate}.] Capable of being iterated or repeated. [Obs.] **************************************************************** From: Georg Bauhaus Sent: Thursday, April 14, 2011 10:22 AM > The new chapter 5.5.1 uses the new term iteratable_name. > > > > I think from the Latin origin, iterare, to walk, (that would be ire. ;-) William Whitaker's Latin WORDS to the rescue. First, "iterare" exists and means "do a second time; repeat; renew, revise;" For iterabilis, the program prints "Two words May be 2 words combined (itera+bilis) If not obvious, probably incorrect bilis N 3 3 NOM S F bil.is N 3 3 GEN S F bil.is N 3 3 ACC P F Early bilis, bilis N F [XXXBO] gall, bile; wrath, anger, indignation; madness, melancholy, folly; iter.a V 1 1 PRES ACTIVE IMP 2 S itero, iterare, iteravi, iteratus V [XXXCX] do a second time; repeat; renew, revise; *" Admirable. I guess, then, that iterable_item is a correct neologism. **************************************************************** From: Tucker Taft Sent: Thursday, April 14, 2011 11:24 PM I agree iterable is probably better. Enumerate becomes enumerable, so it would make sense for iterate to become iterable. **************************************************************** From: John Barnes Sent: Monday, April 18, 2011 4:29 AM I did see the Ada-Comment stuff. Yes. It should be iterable. Misuse of English makes me irritatable. **************************************************************** From: Florian Weimer Sent: Monday, April 18, 2011 11:32 AM > Loads of Java, Scala, Android, Boost references to Iterable, so seems > to be the de facto standard English word for this context. It's also easier to type because it's fairly obvious there is no letter "e" somewhere in the middle, so please go with "iterable", especially if it is exposed in some way to the programmer. **************************************************************** !topic Implicit_Dereference !reference AI05-0139-2 !from Christoph Grein 2011-04-14 !discussion If I understand the AI correctly, it will be possible to write type Refcounted is abstract tagged private; type Refcounted_Access is access Refcounted'Class; type Accessor (Data: access Refcounted'Class) is limited private with Implicit_Dereference => Data; -- <-- new syntax type Ref is tagged private; -- must be tagged to use AI05-0139-2 procedure Set (Self: in out Ref; Data: Refcounted'Class); function Get (Self: Ref) return Accessor; My_Refcount (P.Get).I := 42; -- instead of My_Refcount (Get (P).Data.all).I := 42; -- or My_Refcount ( P.Get.Data.all).I := 42; Is this correct? **************************************************************** From: Randy Brukardt Sent: Thursday, April 14, 2011 5:35 PM That's the idea. (Presuming "My_Refcount" takes a Data index or parameter.) I'm now waiting for the other shoe to drop. :-) (Questions like this always seem to be a setup for "But, then the following horrible thing could happen!") **************************************************************** From: Tucker Taft Sent: Monday, May 2, 2011 9:44 PM [Part of this message that pertains to this AI.] AI05-0139-2/12 Syntactic sugar for accessors, containers, and iterators [The only change is to the form of the Iterator_Interfaces generic and the associated wording and examples. This generic now uses a formal incomplete type.] Approve __X____ Disapprove ______ Abstain _______ Comments: There are several places where "general_indexing" should be "generalized_indexing." **************************************************************** From: Randy Brukardt Sent: Thursday, June 9, 2011 10:04 PM The text in 4.1.6 User-defined Indexing says in part: An /indexable type/ is (a view of) a user-defined tagged type with at least one of the aspects Constant_Indexing or Variable_Indexing specified. ... Why does this say "user-defined"? Taken literally, that would mean that the type Vector in Ada.Containers.Vectors is not an indexable type, since it is not "user-defined". That's surely not what we want! (And I'm sure Adam will ask soon if unchanged.) I note that 4.1.5 (User-defined References) doesn't say anything about "user-defined" in the similar text. So I conclude this is just a mistake. Right?? **************************************************************** From: Bob Duff Sent: Friday, June 10, 2011 6:09 AM > I note that 4.1.5 (User-defined References) doesn't say anything about > "user-defined" in the similar text. So I conclude this is just a mistake. > Right?? Right. **************************************************************** From: Tucker Taft Sent: Friday, June 10, 2011 7:52 AM Your fix sounds fine. I think "user-defined" is as opposed to "predefined," not "language-defined." Even language-defined packages can have "user-defined primitive subprograms" overriding "predefined" operators. But if you think it is creating confusion, or simply that it doesn't add anything, then feel free to get rid of it. **************************************************************** From: Randy Brukardt Sent: Thursday, June 9, 2011 11:13 PM In 5.5.2 (generalized iterators), we have: In a container element iterator, if the /iterable_/name denotes a constant, then the Constant_Indexing aspect shall be specified for the type of the /iterable_/name. This rule is weird, because it says nothing about Variable_Indexing. If the name denotes a variable, there are no requirements for any indexing aspect to be specified, which is not going to work. We need at least one of the two aspects specified for the type, and if the object is a constant, it has to be Constant_Indexing. (If the object is a variable, it can be either Variable_Indexing or Constant_Indexing.) So I replaced this paragraph by the following: In a container element iterator whose /iterable_/name has type T, if the /iterable_/name denotes a constant or the Variable_Indexing aspect is not specified for T, then the Constant_Indexing aspect shall be specified for T. I introduced the name "T" to avoid having to say "type of the /iterable_/name" twice close together. Perhaps this can be worded more elegantly. **************************************************************** From: Tucker Taft Sent: Friday, June 10, 2011 7:53 AM Your fix sounds fine, though I have a suspicion that if you include the larger context, it may be redundant. **************************************************************** From: Bob Duff Sent: Friday, June 10, 2011 8:16 AM > So I replaced this paragraph by the following: > > In a container element iterator whose /iterable_/name has type T, > if the /iterable_/name denotes a constant or the Variable_Indexing > aspect is not specified for T, then the Constant_Indexing aspect > shall be specified for T. How about this: In an iterator whose /iterable_/name has type T, the Variable_Indexing or Constant_Indexing aspect (or both) shall be specified for T. If the /iterable_/name denotes a constant, the Constant_Indexing aspect shall be specified for T. **************************************************************** From: Tucker Taft Sent: Friday, June 10, 2011 8:35 AM To be more precise, the expected type for a container element iterator is "any iterable type" and from 5.5.1: An /iterable type/ is an indexable type ... and from 5.5: An /indexable type/ is (a view of) a user-defined tagged type with at least one of the aspects Constant_Indexing or Variable_Indexing specified. So I believe your change is redundant. ****************************************************************