Version 1.14 of ai05s/ai05-0139-2.txt
!standard 4.1(2/2) 11-04-06 AI05-0139-2/10
!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 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.<discrim>.all," where "<discrim>" 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; --
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;
--
procedure Enter(Tab : in out T_Table; Key : String; Value : T);
--
function Lookup_Or_Create(Tab : access T_Table; Key : String) return access T;
--
--
--
function Element(Tab : access T_Table; Key : String) return Ref_T;
--
private
...
end Whatever;
package body Whatever is
function Element(Tab : access T_Table; Key : String) return Ref_T is
--
begin
--
return Ref_T'(Data => Lookup(Tab, Key));
end Element;
...
procedure Do_Something(Tab : access T_Table) is
--
--
--
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
--
--
--
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 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;
--
--
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 is private;
No_Element : in Cursor;
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 No_Element 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 No_Element 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 No_Element 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 No_Element 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 _Cursor /= No_Element loop
declare
Elem : Container_Type'Iterator_Element renames Container(_Cursor);
--
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 ...
package Iterators is new Ada.Iterator_Interfaces(Cursor_Type, No_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;
--
--
end loop;
Some examples of the second iterator would be:
Arr : array(1..10) of Natural := ...
...
for X : Natural of Arr loop
--
--
--
X := X + 1;
end loop;
--
Vec : Int_Vectors.Vector;
...
for X of Vec loop
--
--
--
--
--
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 section 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;
--
--
function Find(C : access Container; Key : String) return Ref_Element;
--
...
Find(C, "abc") := Element'(...); --
--
--
Add a new section 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 user-defined 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 Contant_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 general_indexing, then the general_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 general_indexing is interpreted as a /variable indexing/.
When a general_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 a new section 5.5.1:
5.5.1 User-Defined Iterator Types
The following language-defined generic library package exists:
generic
type Cursor is private;
No_Element : in Cursor;
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 /iteratable type/ is an indexable type with specified Default_iterator and
Iterator_Element aspects. A /reverse iteratable type/ is an
iteratable type with the default iterator type being a reversible
iterator type. An /iteratable object/ is an object of an
iteratable type. A /reverse iteratable object/ is an object of a
reverse iteratable type.
Legality Rules
The Constant_Indexing aspect (if any) of an iteratable 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 iteratable 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] /iteratable_/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
/iteratable_/name is any iteratable 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 /iteratable_/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 /iteratable_/name.
In a container element iterator, if the /iteratable_/name denotes a
constant, then the Constant_Indexing aspect shall be specified for
the type of the /iteratable_/name.
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
/iteratable_/name.
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 /iteratable_/name denotes a constant, or if the
Variable_Indexing aspect is not specified for the type of the
/iteratable_/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 initial value is
equal to No_Element, 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 loop parameter is equal to No_Element, 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 /iteratable_/name is evaluated
and the iteratable object denoted by the name becomes the /iteratable
object for the loop/. The default iterator function for the type of
the iteratable object for the loop is called on the iteratable 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 initial value is equal to
No_Element, 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 iteratable 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 loop cursor is equal to No_Element, 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 iteratable 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.
!ACATS test
!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 ".<acc_discrim>.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 ".<acc_discrim>.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.<some_function>(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).<accdim>.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(<cursor>) where <cursor> in Default_Iterator(Vec).
> -- and Vec(<cursor>) is equiv to "()"(Vec, <cursor>).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 <Sum> 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 <Constant_Indexing> (Arr : in Array_Type; Index : in
Index_Subtype) return Component_Type;
function <Variable_Indexing> (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 iterable 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 iterable 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.
****************************************************************
Questions? Ask the ACAA Technical Agent