!standard 5.5 10-05-18 AI05-0139-2/05 !class Amendment 09-02-13 !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 Define a "magic" interface, a "magic" generic unit, and some additional "aspects" for the aspect_specification, all of which are linked to some appropriate syntactic sugar for accessors, containers, and iterators: 1) SUPPORT FOR ACCESSORS/REFERENCES We propose to add a "magic" interface called Reference. Any type descended from Reference with exactly one access discriminant is considered a "reference" type, and provides implicit dereference in all contexts, with the "implicit dereference" of an object Ref of such a type being equivalent to "Ref..all," where "" is the name of the 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. The Reference interface is defined in its own package: package Ada.References is type Reference is limited interface; end Ada.References; 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.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 ... -- This type is now a "reference" type procedure Enter(Tab : in out T_Table; Key : String; Value : T); -- Add the given T Value to the T_Table Tab. function Lookup_Or_Create(Tab : access T_Table; Key : String) return access T; -- Return a pointer to an element of the table with the given Key. -- If it doesn't exist, add a default-initialized value of type -- T (i.e. Null_T) using the given Key. function Element(Tab : access T_Table; Key : String) return Ref_T; -- Return a "reference" to an element of the table private ... end Whatever; package body Whatever is function Element(Tab : access T_Table; Key : String) return Ref_T is -- Return a "reference" to an element of the table begin -- "Wrap" the pointer in a reference object return Ref_T'(Data => Lookup(Tab, Key)); end Element; ... procedure Do_Something(Tab : access T_Table) is -- Illustrate the implicit dereference -- of the objects of type Ref_T returned -- by function Element. begin Tab.Element("Apple") := New_T(...); if Tab.Element("Banana") = Null_T then Tab.Element("Banana") := New_T(...); end if; ... end Do_Something; My_Tab : aliased T_Table; begin Do_Something(My_Tab'Access); end Whatever; As you can see, references provides a very natural interface for dealing with elements of a container. 2) SUPPORT FOR CONTAINER INDEXING We now take this one step further by allowing the definition of two new aspects, Constant_Indexing and Variable_Indexing, which allow the elimination of the function name in uses like that above, simplifying the notation to simply "Tab("Banana")". This works as follows: package Whatever is ... as above type T_Table is tagged limited private with Variable_Indexing => Element; Constant_Indexing => Element_RO; type Ref_Const_T(Data : access constant T) is new Ada.Finalization.Limited_Controlled and Ada.References.Reference with ... function Element_RO(Tab : access constant T_Table; Key : String) return Ref_Const_T; ... as above end Whatever; Now the body of Do_Something can be simply: procedure Do_Something(Tab : access T_Table) is -- Illustrate the implicit dereference -- of the objects of type Ref_T returned -- by function Element. begin Tab("Apple") := New_T(...); if Tab("Banana") = Null_T then Tab("Banana") := New_T(...); end if; ... end Do_Something; Essentially we are allowing a container to be "indexed" in the same way as an array is indexed, with the key (or potentially various different key types) being arbitrary. The basic rule with the Variable_Indexing and Constant_Indexing aspects is that all subprograms with the given name declared immediately within the same declaration list as the type, must be suitable for calling using prefix notation, given a variable or constant (respectively) of the associated type, meaning that it is either a primitive operation or a class-wide operation of the tagged type (via its first parameter). It must have at least two parameters. There seems no particular reason it couldn't have more than two parameters. There are no predefined _Indexing aspects, though one could imagine that all array types have such aspects, if it helps in understanding the model. If only the Variable_Indexing aspect is defined, then indexing may only be applied to variables. If only the Constant_Indexing aspect is defined then indexing may be applied to a variable or a constant, but not in a context that requires the result to be a variable. If both aspects are defined, then the Variable_Indexing aspect is used when the indexing is used in a context that requires a variable, or in an object renaming where the prefix is a variable; the Constant_Indexing aspect is used otherwise. With these two additional aspects defined, we can now use the "Tab(Key)" notation on both the left-hand-side of an assignment (on variables) and on the right-hand-side (on variables or constants). Note that we try to use Constant_Indexing wherever it is legal since it presumably has lower overhead, particularly if indexing into a "persistent" constainer. 3) SUPPORT FOR ITERATORS Given these two capabilities, we are now ready to define a more general "iterator" syntax, that is, a variant of the "for" loop statement which works naturally on containers. In fact we propose two distinct iterator syntaxes: a) The first syntax is intended for iterating over the set of cursors or indices that select elements of a container. This is most analogous to what is already available for arrays by writing "for I in Arr'Range loop": for Cursor in Iterate(Container) loop Container(Cursor) := Container(Cursor) + 1; end loop; What the above presumes is that the function Iterate returns an object of a type derived from a "magic" iterator type (see below). The iterator type provides a First and Next operation which return a Cursor type, and a 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 _Indexing aspect -- is defined. Cursor := Next(_iter, Cursor); end loop; end; The generic package Iterator_Interfaces defines a pair of "magic" interfaces used to "enable" the above iterator syntax for a given cursor type: generic type Cursor is private; No_Element : in Cursor; package Ada.Iterator_Interfaces is type Forward_Iterator is limited interface; function First (Object : Forward_Iterator) return Cursor; function Next (Object : Forward_Iterator; Position : Cursor) return Cursor; type Reversible_Iterator is limited interface and Forward_Iterator; function Last (Object : Reversible_Iterator) return Cursor; function Previous (Object : Reversible_Iterator; Position : Cursor) return Cursor; 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); -- This requires an appropriate _Indexing aspect being defined begin Elem := Elem + 1; _Cursor := Next(_Iter, _Cursor); end; end loop; end; Here is how this generic and the iterator-related aspects might be used: type Cursor_Type is ... 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 Element_Ref(E : access Element_Type) is limited new Ada.References.Reference with private; type Element_CRef(E : access constant Element_Type) is limited new Ada.References.Reference with private; type Iterator_Type is new Iterators.Reversible_Iterator with ... function Iterate(Con : Container) return Iterator_Type; function Element(Con : access Container; Cur : Cursor_Type) return Element_Ref; function Element_RO(Con : access constant Container; Cur : Cursor_Type) return Element_CRef; ... These two new kinds of "for" loops would require two new kinds of "for" loop parameter specifications: defining_identifier IN [REVERSE] iterator_expression defining_identifier [: subtype_indication] OF container_expression The first requires an expression returning an object of a type descended from Forward_Iterator, or Reversible_Iterator if REVERSE appears. The defining_identifier is of the corresponding Cursor type, and can be used to index into the container. The second requires an expression returning an array, or an object of a type which has aspects Default_Iterator and Iterator_Element specified. The subtype_indication is optional, since the Iterator_Element aspect of the container type determines the element type. Similarly for an array, the element type is simply the array component type. One purpose of the subtype_indication might be to tighten the requirements on the elements of the container/array, acting essentially as an assertion about the values stored in the container/array at the time of the iteration. Another purpose is simply to help the reader, and provide an extra check that the Iterator_Element type of the container is as expected. Here are some additional examples of iterators. An example of the first iterator would be: V : Int_Vectors.Vector; ... for C in V.Iterate loop V(C) := V(C) + 1; -- Note that this presumes appropriate -- _Indexing aspects have been defined. end loop; Some examples of the second iterator would be: Arr : array(1..10) of Natural := ... ... for X : Natural of Arr loop -- The ": Natural" part is optional. -- X renames Arr() where in Arr'Range. -- X is a variable if Arr is a variable. X := X + 1; end loop; -------- Vec : Int_Vectors.Vector; ... for X of Vec loop -- X renames Vec() where in Default_Iterator(Vec). -- If Vec is a variable, Vec() is equiv to -- Vec'Variable_Indexing(Vec, ).Discrim.all -- and if Vec is a constant, is equiv to -- Vec'Constant_Indexing(Vec, ).Discrim.all X := X + 1; end loop; The "of" syntax, suggested by J.P. Rosen, seems to help identify X as an element *of* the array/container rather than as an index or cursor *in* the sequence defined by the discrete range or iterator. !wording ** TBD ** !discussion [NOT FULLY UPDATED -- SEE PROPOSAL FOR SOME NEWER DISCUSSION] This is based on the earlier proposal and discussion recorded in AC-0112.TXT. This proposal was modified to address the comments made on that one, and has been simplified considerably. Note that the iterator interface can be directly applied to a container type (allowing it to be directly iterated) or it can be part of a separate function (which would allow adding additional parameters to the iterator and/or adding additional storage for the iterators). By using interfaces, we give maximum flexibility to the programmer that is using these iterators. If the creator of the iterator needs extra storage to accomplish the iteration, they can include it in the type of the iterator. If the creator of the iterator needs to get control when the loop is exited prematurely, they can define a controlled iterator type. In that case, (presuming that the iterator_expression was a function call) when the loop is exited, the Finalization routine for the iterator object will be called. We provide a form for reverse iterators. Reverse iteration makes sense for many kinds of containers (vectors and lists, for instance). Given that the syntax already exists and every Ada programmer is familiar with it, not providing it would quickly become one of the most common frustrations of programming in Ada. That seems silly. We could have simplified the proposal by requiring that all iterators support Last and Previous, but iterators where reverse iteration does not make sense would have to raise an exception when Last is called. The proposed features, in contrast, make a compile-time determination as to whether reverse is allowed. That will detect errors earlier; it generally is better to detect errors at compile-time if possible. It might appear that this proposal would require some solution to the instantiation for private types problem, but as the examples below show, this is not true. Note that the actual iterator type can be either limited or non-limited. The value of a limited iterator object is that an iterator implementation can assume (presuming that it only exports functions and not types) that the iterator object is created and destroyed with the loop. This allows safe cleanup operations when the loop is exited (as in the examples below). If that capability is not needed, an iterator implementation allow reuse of iterator objects (especially if no temporary storage is needed). Note that if temporary storage or loop exit actions are needed, the iterator object *must be* separate from the container object that it is iterating on. Otherwise nested loops on the same container would not work. for I in My_Container.Iterator loop for J in My_Container.Iterator loop ... end loop; end loop; The I and J loops need separate temporary storage, which must be provided by the iterator object (the loop can only provide the loop parameter cursor object). Issues: The use of a controlled type for an iterator in this proposal would require bounded containers to be preelaborable units, rather than pure units. We add a prohibition on the actual container from being controlled, so that finalization averse users could still use the bounded containers (but obviously not the iterators). This seems OK, given that the current accessor proposal (AI05-0142-4) has similar requirements. It would be nice for an iterator implementation to be able to prevent the creation of iterator objects not associated with a loop. For instance, given the implementation of the example below: My_Iter : Some_Instance.Forward_Iterator'Class := My_List.Iterator; would have the effect setting tampering on My_List and keeping it set until the object is destroyed (potentially a long time in the future). But this is not the natural thing to do -- it requires a lot more typing than the intended usage -- and no major definitional problems are encountered in this case. We would need a lot more mechanism to prevent the creation of objects outside of the loop context, so we do not try to prevent this misuse. Alternatives considered: One option considered was including the name of the cursor type somewhere in the syntax. Something like: defining_identifier : subtype_mark in [reverse] iterator_expression That was not done mainly because differs from the existing syntax for no clear benefit. It would make the type of the identifier clearer, but it also would mean that switching from a discrete iterator to a container one would require some syntax changes that don't really fit into the model. (One solution to that problem would be to optionally add the type name to the discrete for loop as well, but that seems like a bunch of additional changes for little value.) It should be noted that with this formulation, the existing discrete iterators could be considered just a predefined instantiation of this magic generic for discrete types. The analogy breaks down a bit for ranges (they don't look like a function call), but it isn't hard to imagine such a unification. !examples [NOT FULLY UPDATED -- SEE PROPOSAL FOR ADDITIONAL EXAMPLES] This feature could easily be used in the existing Ada.Containers packages. We give a suggested implementation for Ada.Containers.Doubly_Linked_Lists; the other containers could be similar. The start of the visible part of Ada.Containers.Doubly_Linked_Lists would be modified as follows: with Ada.Iterator_Interfaces; generic type Element_Type is private; with function "=" (Left, Right : Element_Type) return Boolean is <>; package Ada.Containers.Doubly_Linked_Lists is pragma Preelaborate(Doubly_Linked_Lists); pragma Remote_Types(Doubly_Linked_Lists); package Cursors is type Cursor is private; pragma Preelaborable_Initialization(Cursor); No_Element : constant Cursor; private ... -- not specified by the language end Cursors; subtype Cursor is Cursors.Cursor; No_Element : Cursor renames Cursors.No_Element; package Iterators is new Ada.Iterator_Interfaces (Cursors, No_Element); type List is new Iterators with private; pragma Preelaborable_Initialization(List); Empty_List : constant List; ... Note that the changes here are the addition of the nested package Cursors, the addition of the instantiation, and the subtype and renames to eliminate the effect of the nested package. The majority of the package is unchanged. After the existing procedure Reverse_Iterate, we would add a new function: function Iterate (Container : in List) return List_Iterators.Reversible_Iterator'Class; This would be described as Iterate returns an object that can be used in a loop_statement to loop with a loop parameter designating each node in Container, starting with the first node and moving the cursor as per the Next function if the reserved word *reverse* is not used in the user_loop_parameter_statement, and starting with the last node and moving the cursor as per the Previous function otherwise. Program_Error is propagated if any operation (in particular, the sequence_of_statements of the loop_statement whose iterator_expression denotes this object) tampers with the cursors of Container while the returned object exists. This could be implemented by defining a controlled type in the private part of Ada.Containers.Doubly_Linked_Lists: type Our_List_Iterator is new Ada.Controlled.Limited_Controlled and List_Iterators.Reversible_Iterator with record Our_List : access List; end record; function First (Object : Our_List_Iterator) return Cursor; function Next (Object : Our_List_Iterator; Position : Cursor) return Cursor; function Last (Object : Our_List_Iterator) return Cursor; function Previous (Object : Our_List_Iterator; Position : Cursor) return Cursor; procedure Finalize (Object : in out Our_List_Iterator); The implementation of these operations would be something like: function Iterate (Container : in List) return List_Iterators.Reversible_Iterator'Class is begin return Iter:Our_List_Iterator do Iter.Our_List := Container'Unchecked_Access; -- This is safe unless someone does an Unchecked_Deallocation on -- the container in the loop; we don't try to protect against that -- in other cases for Ada.Containers, so we won't here, either. -- Set a tampering context for Iter.Our_List.all. end return; end Iterate; function First (Object : Our_List_Iterator) return Cursor is begin return Object.Our_List.First; end First; function Next (Object : Our_List_Iterator; Position : Cursor) return Cursor is begin return Next (Position); end Next; -- Similar implementations for Last and Previous. procedure Finalize (Object : in out Our_List_Iterator) is begin -- Clear the tampering context for Object.Our_List. end Finalize; Note that we didn't add these interfaces to the actual container types. That was because we wanted these iterators to be a tampering context (thus ensuring that they terminate and do not lead to erroneous execution). Users would be very surprised if a "safe" construct like a for loop caused erroneous execution (which will happen if the node designated by the loop parameter is deleted). The function Iterate would normally be used directly in a loop: for C in My_List.Iterate loop ... Element (C) ... end loop; We could also define a partial list iterator: function Partial_Iterate (Container : in List; Start : in Cursor; Finish : in Cursor := No_Element) return List_Iterators.Reversible_Iterator'Class; where Start and Finish (if not No_Element) would have to be cursors for Container. In this case, the iteration would run from Start to Finish (or No_Element, if Finish is not on the list after/before Start) forward or in reverse. (This would also be a tampering context as above.) We would use Partial_Iterator directly in a loop: for C in Partial_Iterate (My_List, Start, Finish) loop ... Element (C) ... end loop; !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 "..all" syntactic sugar. I don't see any privacy breaking here, so you should explain where you see it. This idea of using an interface type as effectively a "marker" is used successfully in other languages. You could use a pragma or aspect clause/specification, but having the presence of a pragma or aspect clause affect syntax seems in particularly bad taste. We already give some special capabilities to types having Controlled or Storage_Pool as an ancestor, so this feels like a modest step up from that. > So I think we need the magic generic for the accessors (and honestly, > I don't see what the concern is, the users will almost never > instantiate these things, and when they do, they'll copy the > containers implementation). The incantation for enabling magic can be > as complex as necessary in order for it to work. > > The only case where this idea might work is the "Reference" one. In > that case, the interface is directly placed on the Reference_Type > implementation, and that probably is enough to describe some semantics of omitting the .all. It's not just the ".all", it is omitting the ".Element.all", which is certainly not currently allowed by Ada. > IMHO, that's the least interesting of the three, given that Ada allows > you to omit the .all in most contexts anyway. (I do see how you could > use that to implement most of AI-111, which has been my point all > along vis-a-vis that AI: we don't need the complex mechanisms so long > as we have a way to get finalization right). It's surely the one I > could live without (and I have serious doubts that it can be resolved without ambiguity). Not surprisingly, I don't agree. Eliminating the need to write ".Element.all" all over the place changes the dynamic significantly, and makes these References actually usable and powerful. **************************************************************** From: Randy Brukardt Sent: Wednesday, February 3, 2010 3:59 PM > > Doing so requires the relationship between the three types to be > > exposed in the interface. There is no way to do that with a single > > magic interface unless we are willing to destroy privacy. > > I don't follow your logic. Can you drag me through it? I don't see how the fact that you've used the shorthand for a container reference would lead the compiler to select the correct function to implement that syntax. The only way I can think of is for the compiler to look inside the representation of Reference_Type and see that it in fact references a Vector. And even that is tenuous. ... > I don't understand where the privacy breaking comes into play. > > I was suggesting that by declaring a container as a descendant of the > Magic.Container interface, you "activate" > some of the syntactic sugar. This is the problem. > I was further suggesting that > by declaring the Reference_Type as a descendant of Magic.Reference, > you "activate" the "..all" > syntactic sugar. This is OK. > I don't see any privacy breaking here, so you should explain where you > see it. I was thinking backwards, but so are you. I presume that the effect of the Magic.Container interface would be to allow writing the call: Container_Object.(params) as Container_Object(params) (I'm presuming that your model is that the reference part is separate magic, so these are separable magic items. That's good, IMHO). But the problem here is that the function name is now gone. How can the compiler decide what function is being called? The original interface idea made it clear: it was named in the interface. But now you have left that out. So you end up having to look at *every* primitive and class-wide function whose first parameter has the right type (the latter because this is prefix notation, and it ought to work like it). You can't do that by name (as you do for everything else in Ada resolution), you have to pick up everything. That means a wide symboltable search, through all of the appropriate packages. And when does this apply? I have to think it has to apply to any function, because you have nothing else to say. That means that you can replace: Container_Object.Capacity and Container_Object.Length by Container_Object That would appear to make Container_Object itself ambiguous. OK, so we could insist on two parameters. We can then replace: Container_Object.To_Cursor (1) by Container_Object(1) But that's going to be ambiguous with the intended use (result resolution should bail us out most of the time, but not always). And then the killer: Container_Object.Element (1) and Container_Object.Reference (1).Element.all (assuming the use of Magic.Reference on the latter) would both be: Container_Object(1) And both have the same result type. So our new syntax is always ambiguous. What's the point of that?? I suppose you could fix this by allowing the special "omission" syntax to apply only to function returning a type with a Reference interface. But that seems very limiting; the new Magic.Container interface becomes a "gazebo" (as Dmitry put it on in a c.l.a. discussion); it only works in one particular case specifically tied to the implementation of Ada.Containers. It would not be usable in a different containers design. For instance, if you willing to directly return an anonymous access, you couldn't use this syntax. That seems insane. So I think it is critical that the marker (currently a container interface) declare the result type that is intended. It would be best if it could also declare the function whose name can be omitted. The only sane ways to do that is to use a magic generic interface, or mark using a pragma or new syntax (the latter being too heavy). And, assuming we have one for iterators, I don't see what the problem of having more magic generic interfaces. That is, you are trying to solve a non-problem. Note that using an interface to select the function is also less than ideal, because it forces the subprogram to have a particular name, and only allows one such function per container. Both of those are quite limiting as well. (This is the real problem with interfaces: a single type can either have or not have a particular interface. This is the same problem we have with stream attributes: it makes perfect sense to have multiple hierarchies of attributes for different purposes [say XML and binary streaming], but only one set can exist, and the other has to be created completely manually without any composition. Sometimes this is appropriate [finalization comes to mind], but it usually isn't [shouldn't fence in your users]. At least if the interface is in a generic, you can make multiple interfaces to cover different needs, and you can use renames to get the intended names of your functions -- but it's all harder than necessary.) > This idea of using an interface type as effectively a "marker" is used > successfully in other languages. You could use a pragma or aspect > clause/specification, but having the presence of a pragma or aspect > clause affect syntax seems in particularly bad taste. > We already give some special capabilities to types having Controlled > or Storage_Pool as an ancestor, so this feels like a modest step up > from that. If it was "modest", I'd agree. But the need to search for all primitive functions that match some profile (rather than searching by name as I suspect all Ada symboltables are currently set up) makes this a very expensive idea. Plus it is very limited in utility *or* Truly, the good thing that I see here is that you've proven that the interfaces for the Reference part and the Accessor part are completely separate logically. You had the pieces confused in the past, and that added to the complexity. But the Accessor interface needs to have nothing at all to do with the Reference interface, and indeed it is better if they are unrelated. (Presuming you believe in the need for the Reference at all, I'm still dubious.) ... > > The only case where this idea might work is the "Reference" one. In > > that case, the interface is directly placed on the Reference_Type > > implementation, and that probably is enough to describe some > > semantics of omitting the .all. > > It's not just the ".all", it is omitting the ".Element.all", which is > certainly not currently allowed by Ada. That's true, but .Element is an access discriminant that is being used only because the lack of some better way to get the right accessibility. I never wanted the .Element part in the first place; the whole mechanism is about getting rid of the .all and still having the right lifetime for the access value. The .Element got introduced for the lifetime reasons only, and only to avoid adding some other mechanism to get the right lifetime. Anyone who uses access discriminants on purpose is either the most brilliant Ada programmer possible or is delusional. You might in fact be the former, but I don't think we need to invent language features just for you. :-) For me, this is all about getting rid of the .all and having the right lifetime if someone tries to abuse the reference. The existence of the access discriminant is a (goofy) means to that end, and I'd prefer to never even think it exists. And I can't imagine anyone doing this on purpose outside a container library. > > IMHO, that's the least interesting of the three, given that Ada > > allows you to omit the .all in most contexts anyway. (I do see how > > you could use that to implement most of AI-111, which has been my > > point all along vis-a-vis that AI: we don't need the complex > > mechanisms so long as we have a way to get finalization right). It's > > surely the one I could live without (and I have serious doubts that > > it can be resolved without ambiguity). > > Not surprisingly, I don't agree. Eliminating the need to write > ".Element.all" all over the place changes the dynamic significantly, > and makes these References actually usable and powerful. Let's agree to disagree on this one. It doesn't matter anyway, I don't think there is a problem with this particular idea, because no guessing/wide search is required to figure out what is meant. I just don't think it works sanely for the container part. **************************************************************** From: Tucker Taft Sent: Wednesday, February 3, 2010 4:26 PM > But the problem here is that the function name is now gone. How can > the compiler decide what function is being called? The original > interface idea made it clear: it was named in the interface. But now > you have left that out. ... Ahh, I see your confusion. We (language designers) would still have to specify the implicit function name that is used in the expansion of the syntactic sugar, in the same way we choose a particular function name in any language-defined package. I had suggested something like "Element" or "Index", but conceivably we could define a new operator designator such as "()" for this purpose. Suppose we choose "Gazebo" ;-). Then for a type descended from Magic.Container, the syntax: Container_Object(params) would expand into Container_Object.Gazebo(params) And then if the result type were a Reference, it would expand further into Container_Object.Gazebo(params)..all One possibility would be to use something quite explicit and unlikely to collide with some existing function, such as "Container_Component" or "Container_Element." **************************************************************** From: Randy Brukardt Sent: Wednesday, February 3, 2010 4:50 PM > One possibility would be to use something quite explicit and unlikely > to collide with some existing function, such as "Container_Component" > or "Container_Element." Humm, if we decided on the operator designator "()" as you put it, note that we no longer need the interface at all. The use of "()" provides the syntactic marker needed. That is, if we have: function "()" (Container : aliased in out Vector; Position : in Cursor) return Reference_Type renames Reference; we know we are allowing Container_Object (My_Cursor) to stand for Container_Object."()" (My_Cursor) or, if you prefer Container_Object.Reference (My_Cursor) And even better, this isn't tied to particular return types or names; you can have as many as you want so long as the results are unambiguous. One would probably have to require that the first parameter be one that is allowed in a prefix notation call (otherwise, you couldn't use the special syntax, so what would be the point). This avoid having to come up with a magic name, as well. (OK, "()" is the magic name, but it is completely magic, just as all other operator symbols are. Seems right to me.) **************************************************************** From: Tucker Taft Sent: Wednesday, February 10, 2010 4:39 PM Here is an update to AI-139-2, [This is version /03 - Editor] which proposes syntactic "sugar" for using iterators, accessors, and containers. This new revision grew out of a recent phone discussion. The major changes involve providing a single "magic" Reference interface instead of a generic package, and an "()" operator to support indexing into a Container as though it were an array. I'm not personally convinced the "default iterator" stuff is worth the added complexity. It works easily for arrays, but it may be overkill to try to get it to work for containers as well. As usual, any and all comments welcome. **************************************************************** From: Tucker Taft Sent: Thursday, February 11, 2010 7:19 AM I didn't like the "default iterator" solution in version 3, so here is version 4 which uses the specification of two aspects, Default_Iterator and Iterator_Element, rather than the signature generic. This makes the solution more palatable to me. I recommend you ignore version 3. **************************************************************** From: Randy Brukardt Sent: Thursday, February 11, 2010 10:17 PM [The first of several free association thoughts on this proposal.] It would be appealing to define: function "()" (S : Unbounded_String; I : Positive) renames Element; but unfortunately Unbounded_String is not tagged, so this doesn't work. **************************************************************** From: Randy Brukardt Sent: Thursday, February 11, 2010 10:23 PM > One relatively important rule for "()" is that one can define two > operators, one that requires the controlling operand to be a variable, > and one that allows it to be a constant, and overload resolution will > resolve to the one that requires a variable if the actual is a > variable, and otherwise it will resolve to the one that allows a > constant. This rule is important so that the same notation, such as > "Container(Key)", can be used when Container is a constant as when it > is a variable, and the variableness of the result can be determined by > the variableness of the Container. Unfortunately, this is purely fantasy. Resolution rules do not take anything about constant vs. variable into account -- those are legality rules. Even if it wasn't fantasy, it still wouldn't make sense, since a variable can always be passed where a constant is allowed. It would seem odd to eliminate that. This seems like a downside of any proposal to eliminate the function names from calls. (Unless you are proposing a new kind of resolution to make this work; it would in fact make sense in this case, but I have to wonder about a preference rule since they're usually problematic.) **************************************************************** From: Randy Brukardt Sent: Thursday, February 11, 2010 10:38 PM In your example, you have the following: > function "()"(Tab : access T_Table; Key : String) return Ref_T; This works OK, but it is important to note that it isn't safe, in that the access value returned as the discriminant of Ref_T can outlive the Ref_T object (at least if I understand AI-51's model, which perhaps I don't in some subtle case). The "aliased" parameters of AI05-0142-4 were designed to eliminate that problem (and the need for dynamic accessibility checking). In that case, the specification would look like: function "()"(Tab : aliased in out T_Table; Key : String) return Ref_T; I couldn't tell whether you were proposing that the original formulation was enough in all cases (which didn't seem to be the case last year, and I doubt that's changed), or whether you were just avoiding the AI-142-4 and AI-143 features necessary to make the intended formulation work in the interest of avoiding mixing of a bunch of new features. **************************************************************** From: Tucker Taft Sent: Friday, February 12, 2010 6:34 AM I agree that "aliased in out" is preferable to "access" but I didn't want to tie the two AIs too closely at this point. I don't believe there is a safety issue. If there is, then AI-51 needs to be fixed. I am certainly hoping we are going to tackle AI-51 at this meeting! **************************************************************** From: Bob Duff Sent: Friday, February 12, 2010 12:57 PM This syntactic sugar is really great! I've been wanting things like this for a long time. Can we add user-defined literals to this AI, so unbounded strings can be tolerable? I'm disappointed that the "of" iterators require the cursor stuff. It would be much cleaner to define them in terms of things like: procedure Iterate (Container : in Vector; Process : not null access procedure (Position : in Cursor)); But I don't see how that can work, because then you can't "exit". Sigh. ... > Using an iterator over one of the Ada 2005 containers libraries > requires writing a subprogram which gets called repeatedly with each element of the container. > That effectively requires writing the loop body out-of-line from the > loop, making the code inside-out of its normal flow, requiring the > writing of a lot of extra text, and thus making it much harder to read and understand. Not to mention having to give it a meaningless name. ... > type Ref_T(Data : access T) is > new Ada.Finalization.Limited_Controlled > and Ada.References.Reference with ... Why not "null record"? ... > package body Whatever is > function Element(Tab : access T_Table; Key : String) return Ref_T is > -- Return a "reference" to an element of the table > begin > -- "Wrap" the pointer in a reference object > return Ref_T'(Data => Lookup(Tab, Key)); Is Lookup_Or_Create meant? ... > package Whatever is > ... as above > > function "()"(Tab : access T_Table; Key : String) return Ref_T; Here and below, id the "access" significant? Could it have been "in out"? ... > The basic rule with "()" is that it must be suitable for prefix > notation, meaning that it is either a primitive operation or a > class-wide operation of some tagged type (via its first parameter), > and if class-wide, is declared immediately within a package where some > tagged type covered by the class-wide type is immediately declared. > It must have at least two parameters. There seems no particular > reason it couldn't have more than two parameters. There are no predefined "()" > operators, though one could imagine that all array types have such > operators, if it helps in understanding the model. It seems annoying to restrict it to tagged types. Is that really necessary? I see below that cursor types will have to be tagged to take advantage of this notation. That's even more annoying, because cursors can often be quite lightweight. > One relatively important rule for "()" is that one can define two > operators, one that requires the controlling operand to be a variable, > and one that allows it to be a constant, and overload resolution will > resolve to the one that requires a variable if the actual is a > variable, and otherwise it will resolve to the one that allows a > constant. This rule is important so that the same notation, such as > "Container(Key)", can be used when Container is a constant as when it > is a variable, and the variableness of the result can be determined by > the variableness of the Container. Well, that's a radical notion! I see that it's necessary. Do you mean "allows a constant" or "requires a constant"? If the former, I think it introduces a Beaujolias effect. Would it make any sense to instead resolve on the basis of whether the context requires a variable? So for "X(C) := X(C) + 1;" the first would resolve to the variable one, and the second to the constant one. ... > What the above presumes is that the function Iterate returns an object > of a type derived from a "magic" iterator type (see below). The > iterator type provides a First and Next operation which return a > Cursor type, and a No_Element cursor value to indicate end of > iteration. The above expands into: > > declare > _Iter : Iterator_Type := Iterate(Container); > Cursor : Cursor_Type := First(_Iter); > begin > while Cursor /= No_Element loop Are you sure you want to require every cursor type to have a special No_Element value? ... > Note that the routines defined in this magic generic generally match > those used in iteration for the Ada.Containers packages, and their > meaning is the same. The only difference is that we've added an > iterator object parameter to the Next and Previous functions (which is > necessary to make them part of the interface). Are we going to add all this stuff to the existing container packages? ... > This second syntax is more convenient, but less flexible, in that > there is no way to specify the particular iterator to use, so it > requires that there be a "default" iterator. Only for containers -- not arrays. ... > The second requires an expression returning an array, or an object of > a type which has aspects Default_Iterator and Iterator_Element > specified. The subtype_indication is optional, since the > Iterator_Element aspect of the container type determines the Element > type. Similarly for an array, the Element type is simply the array > component type. One purpose of the subtype_indication might be to > tighten the requirements on the elements of the container/array, > acting essentially as an assertion about the values stored in the > container/array at the time of the iteration. Another purpose is > simply to help the reader, and provide an extra check that the > Iterator_Element type of the container is as expected. Hmm. I'm inclined to always use a subtype_indication, for readability. Should we require that? If so, we can change "OF" to "IN" in the syntax, which seems a slight improvement. ... > Vec : Int_Vectors.Vector; > ... > for X of Vec loop > -- X renames Vec() where in Default_Iterator(Vec). > -- and Vec() is equiv to "()"(Vec, ).Discrim.all. > -- X is a variable if Vec is a variable. > > X := X + 1; > end loop; > > The "of" syntax, suggested by J.P. Rosen, seems to help identify X as > an element *of* the array/container rather than as an index or cursor > *in* the sequence defined by the discrete range or iterator. For some reason, "of" rubs me the wrong way. The elements are "in" the container, just as much as indices are "in" a range. I didn't read further, since you said you hadn't updated the rest. **************************************************************** From: Tucker Taft Sent: Friday, February 12, 2010 2:32 PM ... > Can we add user-defined literals to this AI, so unbounded strings can > be tolerable? You don't like the "+" operator for this? I suppose we could define another new operator for something like this, or an aspect/attribute. If you have something in mind, go ahead and suggest it. I'm pretty content with using "+". > I'm disappointed that the "of" iterators require the cursor stuff. It > would be much cleaner to define them in terms of things like: > > procedure Iterate > (Container : in Vector; > Process : not null access procedure (Position : in Cursor)); > > But I don't see how that can work, because then you can't "exit". > Sigh. You could have it take a function rather than a procedure, and return True or False to select "exit" or "continue". But you can also exit a loop prematurely using a "goto", a "return", a "requeue", an "exit" to an outer loop, etc. You put that all together and it creates a need to return a "continuation," and I think that is a bit too radical, even for Ada 2012. ;-) > ... >> with Ada.References; >> with Ada.Finalization; >> package Whatever is >> type T is private; >> Null_T : constant T; -- default initial value for T >> function New_T(...) return T; >> >> type T_Table is tagged limited private; >> >> type Ref_T(Data : access T) is >> new Ada.Finalization.Limited_Controlled >> and Ada.References.Reference with ... > > Why not "null record"? That would probably work in many cases. It depends on what sort of cleanup action is required when the reference goes away. ... >> package body Whatever is >> function Element(Tab : access T_Table; Key : String) return Ref_T is >> -- Return a "reference" to an element of the table >> begin >> -- "Wrap" the pointer in a reference object >> return Ref_T'(Data => Lookup(Tab, Key)); > > Is Lookup_Or_Create meant? Oops, yes I meant Lookup_Or_Create. >> package Whatever is >> ... as above >> >> function "()"(Tab : access T_Table; Key : String) return >> Ref_T; > > Here and below, id the "access" significant? Could it have been "in > out"? I think the param might need to also be "aliased", and I didn't want to tie this too closely to the "aliased" parameter mode AI. >> The basic rule with "()" is that it must be suitable for prefix >> notation, meaning that it is either a primitive operation or a >> class-wide operation of some tagged type (via its first parameter), >> and if class-wide, is declared immediately within a package where >> some tagged type covered by the class-wide type is immediately >> declared. It must have at least two parameters. There seems no >> particular reason it couldn't have more than two parameters. There are no predefined "()" >> operators, though one could imagine that all array types have such >> operators, if it helps in understanding the model. > > It seems annoying to restrict it to tagged types. > Is that really necessary? Probably not absolutely necessary. Since we only have prefix notation for tagged types, it seemed natural to piggy-back on that. But these are really pretty different, I suppose, so we could consider defining these for untagged types. > I see below that cursor types will have to be tagged to take advantage > of this notation. That's even more annoying, because cursors can > often be quite lightweight. I didn't mean for cursors to have to be tagged. Where does that requirement come in? It was not intentional. >> One relatively important rule for "()" is that one can define two >> operators, one that requires the controlling operand to be a >> variable, and one that allows it to be a constant, and overload >> resolution will resolve to the one that requires a variable if the >> actual is a variable, and otherwise it will resolve to the one that >> allows a constant. This rule is important so that the same notation, >> such as "Container(Key)", can be used when Container is a constant as >> when it is a variable, and the variableness of the result can be >> determined by the variableness of the Container. > > Well, that's a radical notion! I see that it's necessary. > > Do you mean "allows a constant" or "requires a constant"? > If the former, I think it introduces a Beaujolias effect. > > Would it make any sense to instead resolve on the basis of whether the > context requires a variable? > So for "X(C) := X(C) + 1;" the first would resolve to the variable > one, and the second to the constant one. That might be better. I know this concerned Randy as well. I doubt if there is a Beaujolais effect, because this preference would presumably only apply to operators declared in the same scope. If the preference might affect resolution across packages, that would definitely be an issue. I guess one important issue is whether this is a visibility issue at all. This is more like the prefix-notation than like a visibility lookup. So I see it more as "selecting" the right "()" operator rather than using normal overload resolution. Clearly the devil will be in the wording for this one, if we decide we want this functionality. > ... >> for Element of Arr loop >> Element := Element + 1; >> end loop; >> >> This second syntax is more convenient, but less flexible, in that >> there is no way to specify the particular iterator to use, so it >> requires that there be a "default" iterator. > > Only for containers -- not arrays. Correct. > ... The subtype_indication is optional, since the Iterator_Element >> aspect of the container type determines the Element type. Similarly >> for an array, the Element type is simply the array component type. >> One purpose of the subtype_indication might be to tighten the >> requirements on the elements of the container/array, acting >> essentially as an assertion about the values stored in the >> container/array at the time of the iteration. Another purpose is >> simply to help the reader, and provide an extra check that the >> Iterator_Element type of the container is as expected. > > Hmm. I'm inclined to always use a subtype_indication, for > readability. Should we require that? > If so, we can change "OF" to "IN" in the syntax, which seems a slight > improvement. I don't agree. Making the presence of a subtype_indication completely change the meaning seems weird to me. I think we want the syntax distinction as well. ... >> The "of" syntax, suggested by J.P. Rosen, seems to help identify X as >> an element *of* the array/container rather than as an index or cursor >> *in* the sequence defined by the discrete range or iterator. > > For some reason, "of" rubs me the wrong way. The elements are "in" > the container, just as much as indices are "in" a range. I think you naturally talk about the elements "of" a container, at least as often as you talk about the elements "in" a container. For arrays, we talk about an array "of" integers, not an array "containing" integers. I realize that is using "of" in the opposite sense, but "of" is a more symmetrical preposition than "in" in any case. Anne of Green Gables. Green Gables of Anne. Both make sense. ;-) > I didn't read further, since you said you hadn't updated the rest. Right-O. I actually tried to delete what wasn't applicable anymore, so the discussion and example sections are still worth reading, but they aren't complete at all. In any case, thanks for your careful reading of the new proposal section. **************************************************************** From: Bob Duff Sent: Friday, February 12, 2010 3:28 PM > > Can we add user-defined literals to this AI, so unbounded strings > > can be tolerable? > > You don't like the "+" operator for this? I think it's an ugly hack. But I do tolerate it. It's kind of annoying that this is a syntax error: X : Unbounded_String := +"Hello"; Y : Unbounded_String := X & +", world."; > I suppose we could define another new operator for something like > this, or an aspect/attribute. > If you have something in mind, go ahead and suggest it. I'm pretty > content with using "+". I don't like the "new operator" idea. That's just a different-looking but equally-ugly hack. My idea would be to have an aspect like Literal_Value that's called implicitly, so: X : T := "Hello"; Y : T2 := 123; is equivalent to X : T := T'Literal_Value("Hello"); Y : T2 := T'Literal_Value("123"); This AI is all about syntactic sugar, so I thought maybe I could sneak this in. ;-) > > I'm disappointed that the "of" iterators require the cursor stuff. > > It would be much cleaner to define them in terms of things like: > > > > procedure Iterate > > (Container : in Vector; > > Process : not null access procedure (Position : in Cursor)); > > > > But I don't see how that can work, because then you can't "exit". > > Sigh. > > You could have it take a function rather than a procedure, and return > True or False to select "exit" or "continue". So an "exit" in the loop would turn into "return True"? Hmm. Maybe. Doesn't work for nested loops. > But you can also exit a loop prematurely using a "goto", a "return", a > "requeue", an "exit" to an outer loop, etc. > You put that all together and it creates a need to return a > "continuation," and I think that is a bit too radical, even for Ada > 2012. ;-) Right, that's what I meant by "But I don't see how that can work". You just need coroutines, not the full mind-bending power of continuations, I think. It's disappointing, though, because it's often easier (and never harder) to write an iterator that way. Think about an iterator that's a recursive tree walk. To implement the get-next-item style, you have to horse around with an explicit stack that is guaranteed to be both too small and too big (or too inefficient). > > It seems annoying to restrict it to tagged types. > > Is that really necessary? > > Probably not absolutely necessary. Since we only have prefix notation > for tagged types, it seemed natural to piggy-back on that. But these > are really pretty different, I suppose, so we could consider defining > these for untagged types. OK, good. Let's try to do that. > > > > I see below that cursor types will have to be tagged to take > > advantage of this notation. That's even more annoying, because > > cursors can often be quite lightweight. > > I didn't mean for cursors to have to be tagged. > Where does that requirement come in? It was not intentional. They have to be tagged if you want to use "()", which I think is common, isn't it? > > Do you mean "allows a constant" or "requires a constant"? > > If the former, I think it introduces a Beaujolias effect. > > > > Would it make any sense to instead resolve on the basis of whether > > the context requires a variable? > > So for "X(C) := X(C) + 1;" the first would resolve to the variable > > one, and the second to the constant one. > > That might be better. But it doesn't work if you rename X(C), right? >...I know this concerned Randy as well. > I doubt if there is a Beaujolais effect, because this preference >would presumably only apply to operators declared in the same scope. >If the preference might affect resolution across packages, that would >definitely be an issue. It's still a Beaujolais effect if it's within the same package. You could argue that such Beaujolais effects are not so bad. I'm thinking that you have just the constant one. X is a variable, and you say "Put(X(C));". Then you add the variable version of "()", and now it silently switches to that one (under one interpretation of your words). > I guess one important issue is whether this is a visibility issue at > all. This is more like the prefix-notation than like a visibility > lookup. So I see it more as "selecting" the right "()" operator > rather than using normal overload resolution. Clearly the devil will > be in the wording for this one, if we decide we want this > functionality. Seems like we need this functionality, or the whole feature is rather crippled. > > Hmm. I'm inclined to always use a subtype_indication, for > > readability. Should we require that? > > If so, we can change "OF" to "IN" in the syntax, which seems a > > slight improvement. > > I don't agree. Making the presence of a subtype_indication completely > change the meaning seems weird to me. > I think we want the syntax distinction as well. A matter of taste, I suppose. (But ": subtype_indication" is syntax!) We could still consider requiring the subtype_indication and keep "of". What do you think? I'm not sure. Maybe there are cases where the type is so obvious you want to leave it out. > I think you naturally talk about the elements "of" a container, at > least as often as you talk about the elements "in" a container. > For arrays, we talk about an array "of" integers, not an array > "containing" integers. I realize that is using "of" in the opposite > sense, but "of" is a more symmetrical preposition than "in" in any > case. Anne of Green Gables. Green Gables of Anne. > Both make sense. ;-) Well, if nobody agrees with me, I'll give in. It's not important. (Note that I still sometimes type "case X of^H^His" N years after leaving the Pascal world for Ada. ;-)) **************************************************************** From: Bob Duff Sent: Friday, February 12, 2010 3:38 PM > But you can also exit a loop prematurely using a "goto", a "return", a > "requeue", an "exit" to an outer loop, etc. > You put that all together and it creates a need to return a > "continuation," and I think that is a bit too radical, even for Ada > 2012. ;-) Actually, all you really need is an implementation of all those goto-like things in terms of exceptions. By the way, the most elegant iterators I've ever seen are in Sather, which is a little-known follow-on to Eiffel. (I think Sather is the name of a tower near Stanford U. Get it?) A little too elegant, in fact. **************************************************************** From: Tucker Taft Sent: Friday, February 12, 2010 3:54 PM >> You don't like the "+" operator for this? > > I think it's an ugly hack. But I do tolerate it. > > It's kind of annoying that this is a syntax error: > > X : Unbounded_String := +"Hello"; > Y : Unbounded_String := X & +", world."; You presumably don't need to use "+" for an operand of "&" since you can overload that directly on String. > >> I suppose we could define another new operator for something like >> this, or an aspect/attribute. >> If you have something in mind, go ahead and suggest it. I'm pretty >> content with using "+". > > I don't like the "new operator" idea. That's just a different-looking > but equally-ugly hack. > > My idea would be to have an aspect like Literal_Value that's called > implicitly, so: > > X : T := "Hello"; > Y : T2 := 123; > > is equivalent to > > X : T := T'Literal_Value("Hello"); > Y : T2 := T'Literal_Value("123"); > > This AI is all about syntactic sugar, so I thought maybe I could sneak > this in. ;-) With a single Literal_Value aspect/attribute, it seems hard to know which of the following should be legal for a given type: Y : T2 := "123"; or Y : T2 := 123; You might find my ideas about this in my pie-in-the-sky language "ParaSail" interesting: http://parasail-programming-language.blogspot.com/2009/12/parasail-character-string-and-numeric.html and http://parasail-programming-language.blogspot.com/2009/12/parasail-universal-types-in-annotations.html These rely on being able to specify universal types explicitly in the declarations of certain operators. But perhaps something analogous could be done in Ada. > ... >>> It seems annoying to restrict it to tagged types. >>> Is that really necessary? >> Probably not absolutely necessary. Since we only have prefix >> notation for tagged types, it seemed natural to piggy-back on that. >> But these are really pretty different, I suppose, so we could >> consider defining these for untagged types. > > OK, good. Let's try to do that. > >>> I see below that cursor types will have to be tagged to take >>> advantage of this notation. That's even more annoying, because >>> cursors can often be quite lightweight. >> I didn't mean for cursors to have to be tagged. >> Where does that requirement come in? It was not intentional. > > They have to be tagged if you want to use "()", which I think is > common, isn't it? Only the container needed to be tagged, not the "index" type (which is where the Cursor fits in). If I stated otherwise, that wasn't my intent. >>> Do you mean "allows a constant" or "requires a constant"? >>> If the former, I think it introduces a Beaujolias effect. >>> >>> Would it make any sense to instead resolve on the basis of whether >>> the context requires a variable? >>> So for "X(C) := X(C) + 1;" the first would resolve to the variable >>> one, and the second to the constant one. >> That might be better. > > But it doesn't work if you rename X(C), right? Yes, in a rename you would clearly want to make it a variable if C were a variable, so you are back to looking at the variableness of C, so you might as well always use that as the determining factor. >> ...I know this concerned Randy as well. >> I doubt if there is a Beaujolais effect, because this preference >> would presumably only apply to operators declared in the same scope. >> If the preference might affect resolution across packages, that would >> definitely be an issue. > > It's still a Beaujolais effect if it's within the same package. You > could argue that such Beaujolais effects are not so bad. I don't follow that. You'll have to show me an example. > I'm thinking that you have just the constant one. > X is a variable, and you say "Put(X(C));". > Then you add the variable version of "()", and now it silently > switches to that one (under one interpretation of your words). Oh, I see. That hardly seems like a maintenance issue. That almost seems more like a feature. ... >>> Hmm. I'm inclined to always use a subtype_indication, for >>> readability. Should we require that? >>> If so, we can change "OF" to "IN" in the syntax, which seems a >>> slight improvement. >> I don't agree. Making the presence of a subtype_indication >> completely change the meaning seems weird to me. >> I think we want the syntax distinction as well. > > A matter of taste, I suppose. (But ": subtype_indication" > is syntax!) > > We could still consider requiring the subtype_indication and keep > "of". What do you think? I'm not sure. > Maybe there are cases where the type is so obvious you want to leave > it out. For an array it would be annoying I would think to have to specify the component subtype in an iterator over its components, particularly if it is a long-winded name. It feels like an annoying "test" that doesn't (im)prove anything. **************************************************************** From: Randy Brukardt Sent: Friday, February 12, 2010 3:44 PM ... > My idea would be to have an aspect like Literal_Value that's called > implicitly, so: > > X : T := "Hello"; > Y : T2 := 123; > > is equivalent to > > X : T := T'Literal_Value("Hello"); > Y : T2 := T'Literal_Value("123"); > > This AI is all about syntactic sugar, so I thought maybe I could sneak > this in. ;-) I'm in favor of this idea. But we looked at it last time, and concluded that it would make most Unbounded_String operations ambiguous to call with literals. So the #1 use for it wouldn't work. Consider Append, for instance. It is defined as: procedure Append (Source : in out Unbounded_String; New_Item : in Unbounded_String); -- (1) procedure Append (Source : in out Unbounded_String; New_Item : in String); --(2) procedure Append (Source : in out Unbounded_String; New_Item : in Character); So a call Append (UBS, "Whatever"); works today. If you add an automatic conversion possibility, this call becomes ambiguous, as both (1) and (2) would match. The only way out would be a preference rule, and those are typically trouble. P.S. I'd rather discuss the various unrelated subjects separately, so that people can comment on the one thing that interests them and not a litany of other things. Thus several separate answers. **************************************************************** From: Bob Duff Sent: Friday, February 12, 2010 4:03 PM > I'm in favor of this idea. But we looked at it last time, and > concluded that it would make most Unbounded_String operations > ambiguous to call with literals. So the #1 use for it wouldn't work. Good point. And there is probably lots of existing code that has the same problem -- people add lots of overloadings precisely to get around the lack of user-defined literals. On the other hand, it would be useful for new packages. > If you add an automatic conversion possibility, this call becomes > ambiguous, as both (1) and (2) would match. The only way out would be > a preference rule, and those are typically trouble. Well, Tucker has argued recently that Beaujolais effects aren't so bad (or shouldn't be considered Beaujolais effects) if they're within a single package. Hmm... Or we could remove the offending overloadings. That would be incompatible, but not very much so... Or we could add Ada.Strings.New_Improved_Unbounded_Strings. Only half kidding here... **************************************************************** From: Randy Brukardt Sent: Friday, February 12, 2010 3:54 PM > > Probably not absolutely necessary. Since we only have prefix > > notation for tagged types, it seemed natural to piggy-back on that. > > But these are really pretty different, I suppose, so we could > > consider defining these for untagged types. > > OK, good. Let's try to do that. I think it would be a good idea, but I don't see any way to do that without losing all of the existing magic that Tucker is leaning on. Currently, he has: Container(...) equivalent to Container."()"(...) But of course the latter form isn't allowed for untagged types (for various good reasons). We could of course say the equivalence is instead to: "()"(Container, ...) But that would prevent the use of the magic of prefix notation: (1) No use-clause needed for visibility; (2) Automatic lookup for class-wide operations; (3) Automatic addition of .all and 'Access as necessary. (2) of course doesn't matter for untagged types, but surely the other two do. Tucker's example in fact requires the automatic addition of 'Access. You'd never want to allow that on untagged types (unless the objects are aliased). We could duplicate all of that magic, but that seems like a mess. Or we could try to allow prefix notation on untagged types, but we already *know* that's a mess. > > > I see below that cursor types will have to be tagged to take > > > advantage of this notation. That's even more annoying, because > > > cursors can often be quite lightweight. > > > > I didn't mean for cursors to have to be tagged. > > Where does that requirement come in? It was not intentional. > > They have to be tagged if you want to use "()", which I think is > common, isn't it? You don't want to use "()" on a cursor, the cursor is the parameter. The parameter has no restrictions. And in any case, if you allow untagged "()" (which is appealing), then it doesn't matter. **************************************************************** From: Randy Brukardt Sent: Friday, February 12, 2010 4:02 PM ... > > > Do you mean "allows a constant" or "requires a constant"? > > > If the former, I think it introduces a Beaujolias effect. > > > > > > Would it make any sense to instead resolve on the basis of whether > > > the context requires a variable? > > > So for "X(C) := X(C) + 1;" the first would resolve to the variable > > > one, and the second to the constant one. > > > > That might be better. > > But it doesn't work if you rename X(C), right? I read this whole section as meaning that resolution already did this, not that you were proposing to change resolution. > >...I know this concerned Randy as well. > > I doubt if there is a Beaujolais effect, because this preference > >would presumably only apply to operators declared in the same scope. > >If the preference might affect resolution across packages, that > >would definitely be an issue. > > It's still a Beaujolais effect if it's within the same package. You > could argue that such Beaujolais effects are not so bad. But the "()" doesn't have to be in the same package, it just has to be in an ancestor (think class-wide lookup). And if you have two forms, they could be in different packages. Note that the alternative of *requiring* const/var matching would be pain if all you want is to define read access (which I think will be fairly common). In that case you'd still need to define two routines with the same result type. We could "fix" both problems by getting rid of the classwide lookup and requiring both routines to always be declared together (only as primitive operations of a type, and both are required). That seems like overkill, however. > I'm thinking that you have just the constant one. > X is a variable, and you say "Put(X(C));". > Then you add the variable version of "()", and now it silently > switches to that one (under one interpretation of your words). Right. The rule I suggest above would fix that, by not allowing there not to be a variable one. But it would clearly be a pain. > > I guess one important issue is whether this is a visibility issue at > > all. This is more like the prefix-notation than like a visibility > > lookup. So I see it more as "selecting" the right "()" operator > > rather than using normal overload resolution. Clearly the devil > > will be in the wording for this one, if we decide we want this > > functionality. > > Seems like we need this functionality, or the whole feature is rather > crippled. That's true, it's more like prefix-notation of an operator. One could simply duplicate the whole set of rules for prefix calls, and change the ones that we don't like or need. Sounds like a bigger mess, though. **************************************************************** From: Tucker Taft Sent: Friday, February 12, 2010 4:08 PM Can you remind me of some of the "good reasons" why prefix notation is not permitted for untagged types? I can't remember the trouble they caused... **************************************************************** From: Randy Brukardt Sent: Friday, February 12, 2010 4:31 PM > Can you remind me of some of the "good reasons" > why prefix notation is not permitted for untagged types? I can't > remember the trouble they caused... Gee, I don't remember either. I just remember we had problems and eventually gave up and just restricted to tagged. Better go look in the old AIs. Nice that we index them from the text changes: the numbers in question are AI95-0252 and AI95-0407 (based on 4.1.3(9.2/2) - there's a more recent fix to that, too, but it's not relevant). AI-0252 just has a paragraph at the end of the discussion: We originally generalized this to support non-tagged types, but the added complexity this brought to handling access types seemed more than the anticipated benefit, since we would have to consider primitives of the access type itself as well as those of its designated type. I think the problem comes about because of the automatic dereference. And I recall some pretty hairy examples of what that would cause. Probably have to go look up the minutes and/or read the old e-mail to recall the full extent. I'll leave that to you. (It strikes me that limiting "()" to composite types might effectively fix that problem.) **************************************************************** From: Tucker Taft Sent: Friday, February 12, 2010 5:31 PM I also remember it being related to access types. If we stick to composite types for "()" that would seem to avoid the problem. Of course a private type might turn out to be an access type. Not sure what mischief that would cause... **************************************************************** From: Randy Brukardt Sent: Monday, February 15, 2010 10:17 PM > I also remember it being related to access types. > If we stick to composite types for "()" that would seem to avoid the > problem. Of course a private type might turn out to be an access > type. Not sure what mischief that would cause... I thought of that, too. But I don't think that has to be a problem; "()" has to be primitive as you currently have it, and thus we could make it illegal to define it on an elementary type (even if we don't find out the type is elementary until the full definition -- it still has to be in the same package). I don't think that there is a problem if you *never* know that the type is elementary: package P is type Priv is private; private type Priv is access ... end P; with P; package Q is type My_Type is new P.Priv; function "()" (O : aliased in out My_Type; ...)... end Q; My_Type will never appear to be an access type to any user, even in the body of P, so you'll never have to consider ".all" of it. But this is sort of a last resort; if we can find a way to make these work without such a rule, that would be better still. **************************************************************** From: Bob Duff Sent: Friday, February 12, 2010 12:57 PM > > Y : Unbounded_String := X & +", world."; > > You presumably don't need to use "+" for an operand of "&" since you > can overload that directly on String. True, but the whole point of having user-defined literals is to avoid having so many overloadings that just do "convert some args from String to T and then do the real work". > With a single Literal_Value aspect/attribute, it seems hard to know > which of the following should be legal for a given type: > > Y : T2 := "123"; > or > Y : T2 := 123; Maybe both? > You might find my ideas about this in my pie-in-the-sky language > "ParaSail" interesting: I'm sure I would. I keep intending to pay attention to that, but I never seem to find the time. By the way, a blog seems like a rather unsuitable way to communicate a language design. How about giving me a Parasail Reference Manual? ;-) > Only the container needed to be tagged, not the "index" type (which is > where the Cursor fits in). > If I stated otherwise, that wasn't my intent. I guess I misunderstood. > > I'm thinking that you have just the constant one. > > X is a variable, and you say "Put(X(C));". > > Then you add the variable version of "()", and now it silently > > switches to that one (under one interpretation of your words). > > Oh, I see. That hardly seems like a maintenance issue. That almost > seems more like a feature. Perhaps it is. By the way, I guess you're normally going to have two "()" ops, and their bodies are going to be identical. That's a little bit annoying. > For an array it would be annoying I would think to have to specify the > component subtype in an iterator over its components, particularly if > it is a long-winded name. > It feels like an annoying "test" that doesn't (im)prove anything. Well, I suppose can buy that, but I'm not sure why you say "For an array". If it's annoying, isn't it equally annoying for any container-of-T, to have to specify T? Anyway, I'm happy to be _allowed_ to specify T. I think "for C: Character of S loop" is nicely readable. When I do "Iterate(X, Action => Blah'Access);" I am annoyed that Blah is in the wrong place, and that it's not anonymous, and that I have to say 'Access. But I'm not particularly annoyed that Blah's parameter's type is explicit. **************************************************************** From: Randy Brukardt Sent: Friday, February 12, 2010 7:48 PM > True, but the whole point of having user-defined literals is to avoid > having so many overloadings that just do "convert some args from > String to T and then do the real work". OK, but you started this discussion with add this "so unbounded strings can be tolerable". But unbounded string *have* all of those overloadings, and we surely aren't going to remove them (the compatibility cost would be horrendous). Now, it would make sense to create a new package by taking Unbounded_Strings, giving the package a new name, replacing all occurrences of "String" by "Unbounded_String", and removing all of the duplicate routines. (We have to do this replacement because there are a number of routines that *only* take String parameters, and those have to be converted.) Then adding the new "()" and literals routines. But I tried such a suggestion last time and it got no traction (I was told "Unbounded_Strings" is good enough). And we tried a proposal much like this and abandoned it because it wouldn't help Unbounded_Strings. Thus I can see why this would be a good idea, but since we wouldn't be able to use it for anything (predefined), it's hard to imagine that it would be worth the effort. **************************************************************** From: Tucker Taft Sent: Friday, February 12, 2010 10:49 PM > By the way, I guess you're normally going to have two "()" ops, and > their bodies are going to be identical. That's a little bit > annoying... I don't see them as identical in some cases. A read-only reference can be cheaper to manage than a read-write reference. There may be less or different locking, and if you are talking about a persistent data structure, there is no need to mark the page with the given element as "dirty" if the reference is read-only. **************************************************************** From: Randy Brukardt Sent: Monday, February 15, 2010 10:18 PM > I also remember it being related to access types. > If we stick to composite types for "()" that would seem to avoid the > problem. Of course a private type might turn out to be an access > type. Not sure what mischief that would cause... I thought of that, too. But I don't think that has to be a problem; "()" has to be primitive as you currently have it, and thus we could make it illegal to define it on an elementary type (even if we don't find out the type is elementary until the full definition -- it still has to be in the same package). I don't think that there is a problem if you *never* know that the type is elementary: package P is type Priv is private; private type Priv is access ... end P; with P; package Q is type My_Type is new P.Priv; function "()" (O : aliased in out My_Type; ...)... end Q; My_Type will never appear to be an access type to any user, even in the body of P, so you'll never have to consider ".all" of it. But this is sort of a last resort; if we can find a way to make these work without such a rule, that would be better still. **************************************************************** From: Brad Moore Sent: Wednesday, February 17, 2010 11:33 PM > > As usual, any and all comments welcome. I am very much in favor of the intent behind this proposal. I have have some suggestions however, that might further improve things. I admit I haven't fully thought these ideas through, but I wanted to see if they had any appeal. In a nutshell, if possible, I would like to see; 1) Syntax even more harmonious with existing array/iterator syntax. 2) An optional extension to the existing loop syntax to allow more than one type of "magic" expansion. Essentially, allowing the programmer to provide/override the implementation of the loop. For point 1, I am wondering if it would make sense to have syntax like; for I in Container'Range loop Container (I) := Container (I) + 1; end loop; instead of; for Cursor in Iterate(Container) loop Container(Cursor) := Container(Cursor) + 1; end loop; This would be more in line with existing syntax for iterating through arrays. In this idea, the 'Range attribute would be user definable for a container. Something like; package Ada.Containers.Doubly_Linked_Lists is type List is tagged private; type Cursor is private; function First (Container : List) return Cursor; function Last (Container : List) return Cursor; package List_Range is new Ada.Range_Attribute (Index_Type => Cursor, Start => First, Finish => Last); for List'Range use List_Range.Magical_Thingy If such a construct is even possible, then perhaps one could also write Position1 : Cursor := ...; Position2 : Cursor := ...; for I in Position1 .. Position2 loop Container (I) := Container (I) + 1; end loop; Which seems useful. > a) The first syntax is intended for iterating over the set of cursors > or indices that select elements of a container. This is most > analogous to what is already available for arrays by writing "for I in > Arr'Range loop": > > for Cursor in Iterate(Container) loop > Container(Cursor) := Container(Cursor) + 1; > end loop; > > What the above presumes is that the function Iterate returns an object > of a type derived from a "magic" iterator type (see below). The > iterator type provides a First and Next operation which return a > Cursor type, and a No_Element cursor value to indicate end of > iteration. The above expands into: > > declare > _Iter : Iterator_Type := Iterate(Container); > Cursor : Cursor_Type := First(_Iter); > begin > while Cursor /= No_Element loop > Container(Cursor) := Container(Cursor) + 1; > -- This presumes an appropriate "()" operator > -- is defined. > > Cursor := Next(_iter, Cursor); > end loop; > end; > Could not this also expand to; declare procedure Iterate (Position : Lists.Cursor) is begin Container (Cursor) := Container (Cursor) + 1; end Iterate; begin Container.Iterate (Iterate'Access); end; Then there is less "magic" needed it seems to me. There is no need to involve an iterator interface with a function returning the First cursor, and other function returning the Next. This way, the expansion also works for containers where it doesn't make as much sense to start at the first element. For example, a recursive binary tree container would probably want to start the iteration at the root node of the tree, rather than start at the left most leaf node. If we can assume such an Iterate function has been defined, then the container implementation can decide how best to iterate through the container. As a further example, I have implemented a parallel recursive binary tree container which makes even less sense to think of starting the iteration at First, and iterating through the elements using Next. I do provide an Iterate procedure that manages the parallelism. > b) The second syntax is intended for iterating directly over the > elements of a container or an array: > > for Element of Container loop > Element := Element + 1; > end loop; > > or > > for Element of Arr loop > Element := Element + 1; > end loop; > These forms could also expand to the same expansion I suggested above, could they not? ---------------- Now the the second main suggestion. I would ideally like to see flexibility in the syntax so that the compiler could generate different expansions depending on the magical interface utilized by the programmer. For example, the expansions so far discussed in this AI center around making it easier to write iterators for containers, using a sequential approach. I would ideally like to be able to see expansions that would allow parallel executions through loops, including containers, arrays, and simple loops. The idea is that loop syntax would allow the programmer to optionally specify the name of a procedure/function used to implement the loop. Perhaps this procedure or function would be associated with a magical interface from a set of several available/defined interfaces so that the interface associated with the procedure would indicate to the compiler how the code should be expanded. For example, For the case of sequentially iterating through a container, one could optionally write something like; for I in Container'Range with Container.Iterate loop Container (I) := Container (I) + 1; end loop; The with clause allows the programmer to specify the name of the procedure (implementation) to use for executing the iteration. This could default to name a primitive of the container such as "Iterate", which would be used if this was not specified by the programmer. i.e. for I in Container'Range loop ... implies "with Container.Iterate" since that was not specified. Perhaps a different expansion would be like the one Tucker suggests for when you have a container where First and Next make sense, and where we would not want to expose an Iterate procedure, since the new syntax makes it easier to iterate through the container. Now consider a simple loop that we would like to execute in parallel. declare Sum : Integer := 0; begin for I in 1 .. 100_000_000 loop Sum := Sum + I; end loop end; We could use similar syntactic sugar to allow the programmer to write this as something like; with Integer_Addition_Reducer; -- an instantiation of a generic procedure declare Sum : Integer := 0; begin for I in 1 .. 100_000_000 with Integer_Addition_Reducer loop Sum := Sum + I; end loop end; In this fantasy syntax, the variables enclosed by angle brackets (if any) represent variables that are modified by the loop but are declared in scope outside the loop. The generic needs to treat these specially to avoid race conditions when executing the loop in parallel. This might expand to; declare Sum : Integer := 0; begin declare procedure Iteration (Start, Finish : Positive; Sum : in out Integer) is begin for I in Start .. Finish loop Sum := Sum + I; end loop; end Iteration; begin Integer_Addition_Reducer (From => 1, To => 100_000_000, Process => Iteration'Access, Item => Sum); end; I am currently working on a paper that utilizes such generics to assist in writing parallel loop iterations and parallel recursion. I am planning to present this at Ada Europe in Valencia, if anyone is interested. While I see these generics being useful without the benefit of syntactic sugar, some added sweetener would be nice, particularly if it is compatible with whatever syntax is used for regular container iterators. **************************************************************** From: Randy Brukardt Sent: Thursday, February 18, 2010 12:47 AM ... > > > As usual, any and all comments welcome. > > I am very much in favor of the intent behind this proposal. Good. > I have have some suggestions however, that might further improve > things. I admit I haven't fully thought these ideas through, but I > wanted to see if they had any appeal. ... > For point 1, I am wondering if it would make sense to have syntax > like; > > for I in Container'Range loop > Container (I) := Container (I) + 1; > end loop; > > instead of; > > for Cursor in Iterate(Container) loop > Container(Cursor) := Container(Cursor) + 1; > end loop; Not really. The first form would only allow one iterator per container, and that would be a mistake. For instance, in the multiway tree containers, you might want to iterate on a particular set of children (starting from a particular node) or on the entire tree. That implies that we need multiple iterators and we need the capability of additional parameters. You also want the possibility of local storage for the iterator (such storage can't be in the container or you are restricting yourself to a single iteration at a time). Also recall that we have prefix notation, so the proposed form could easily be written: for I in Container.Iterate loop Container(I) := Container(I) + 1; end loop; which is hardly different. ... > If such a construct is even possible, then perhaps one could also > write > > Position1 : Cursor := ...; > Position2 : Cursor := ...; > > for I in Position1 .. Position2 loop > Container (I) := Container (I) + 1; > end loop; > > Which seems useful. You can write such an iterator easily. Indeed, I proposed one for the list container: for I in Container.Partial_Iterate (Position1, Position2) loop Container (I) := Container (I) + 1; end loop; Here I'm using the possibility for parameters to the iterator creation function to control the iteration. ... > Could not this also expand to; > > declare > procedure Iterate (Position : Lists.Cursor) is > begin > Container (Cursor) := Container (Cursor) + 1; > end Iterate; > begin > Container.Iterate (Iterate'Access); > end; No, as Bob and Tucker were talking about the other day, the loop has to support Exit (including multi-level exits) and Goto and all of the other transfers of control. A goto can't leave a subprogram body! ... > This way, the expansion also works for containers where it doesn't > make as much sense to start at the first element. For example, a > recursive binary tree container would probably want to start the > iteration at the root node of the tree, rather than start at the left > most leaf node. I don't see this, as you can define First however you need for the iterator object. An iterator is *not* a container, and you don't have to use the same definition of First for the iterator as for the container. As Tucker notes, there is a single default iterator that allows you to use special syntax, but that is far too limiting for a general concept. > If we can > assume such an Iterate function has been defined, then the container > implementation can decide how best to iterate through the container. That's already true; the "Next" function can do whatever is appropriate for a particular iterator. It doesn't have to iterate in a particular order (although I would expect that most iterators would indeed have such an order). > As a further example, I have > implemented a parallel recursive binary tree container which makes > even less sense to think of starting the iteration at First, and > iterating through the elements using Next. I do provide an Iterate > procedure that manages the parallelism. A procedure seems appropriate for that, as it must not have an dependency between iterators nor any transfers of control. With a procedure body, dependencies are tough to write and exceptions are the only transfer of control. The body of a loop is a very different animal; it's easily nested in a declare block, and exits and returns are commonly used. > These forms could also expand to the same expansion I suggested above, > could they not? No, for the same reasons as noted above. > ---------------- > > Now the the second main suggestion. > > I would ideally like to see flexibility in the syntax so that the > compiler could generate different expansions depending on the magical > interface utilized by the programmer. For example, the expansions so > far discussed in this AI center around making it easier to write > iterators for containers, using a sequential approach. > I would ideally like to be able to see expansions that would allow > parallel executions through loops, including containers, arrays, and > simple loops. The idea is that loop syntax would allow the programmer > to optionally specify the name of a procedure/function used to > implement the loop. > Perhaps this procedure or function would be associated with a magical > interface from a set of several available/defined interfaces so that > the interface associated with the procedure would indicate to the > compiler how the code should be expanded. I think that parallel loops would need to be syntactically marked, since the loop bodies would have to be written with a restricted set of features: no dependence between iterations, use of atomic variables/protected objects for sums and the like, and limited or no transfers of control. In an ideal world, those restrictions would be enforced by the compiler. > For example, > For the case of sequentially iterating through a container, one could > optionally write something like; > > for I in Container'Range with Container.Iterate loop > Container (I) := Container (I) + 1; > end loop; Huh? Now you're proposing something more complex to write and use than the current proposal, which is: for I in Container.Iterate loop Container (I) := Container (I) + 1; end loop; And if you want to use another iterator for the same container: for I in Container.Child_Iterate (Some_Cursor) loop Container (I) := Container (I) + 1; end loop; You're setting up a straw man and then knocking it down and proposing something more complex. A parallel loop construct isn't a bad idea, but it doesn't make much sense by itself. You'd want to define other constructs that also could be executed in parallel (parallel blocks and the like). Remember that Ada doesn't allow anything to be executed in parallel other than tasks; you'd surely want to be able to make function calls in parallel. And for all of those things, you'd want to define various restrictions on the statements in the loop/block/subprogram body. That requires dedicated syntax so that the compiler and/or reader can know about those restrictions. Please don't kill this nice sequential proposal by loading up all kinds of parallel cruft on it. **************************************************************** From: Tucker Taft Sent: Thursday, February 18, 2010 8:09 AM I agree with Randy's comments on Brad's suggestions. **************************************************************** From: Randy Brukardt Sent: Thursday, February 18, 2010 9:30 PM > > By the way, I guess you're normally going to have two "()" ops, and > > their bodies are going to be identical. That's a little bit > > annoying... > > I don't see them as identical in some cases. > A read-only reference can be cheaper to manage than a read-write > reference. There may be less or different locking, and if you are > talking about a persistent data structure, there is no need to mark > the page with the given element as "dirty" if the reference is > read-only. I've been thinking about this problem some more. I had almost convinced myself that this would work with some tweaks when I realized that there is an additional problem with this idea of having two function "()"s differing only by the constantness of the first parameter: we would have to allow homographs to be declared. For instance: function "()" (Container : access constant Integer_Container; Index : in Natural) return Integer; and function "()" (Container : access Integer_Container; Index : in Natural) return Integer; are homographs. In order for this resolution trick to work, we'd have to allow homographs (preferably only in this particular case!). That doesn't have much impact on the intended use, presuming that we give the prefix form Obj(Ind) special powers. However, it does have an impact on other uses of the functions. Any uses of the name "()" or Pack."()" are going to be ambiguous, because there will be two matching homographs (resolution uses type conformance to tell differences, which these surely are). For instance, normal calls like Pack."()"(Obj, Ind); won't resolve. We could solve that by making the resolution rules depend on the name of the subprogram (which seems like a nasty wart - renames would work differently than the original subprograms), or by making a more general resolution change (which surely would have Beuajolias effects and possibly incompatibilities). We would also have similar problems with renames and uses as generic formal parameters. (The last isn't particularly important because a formal parameter couldn't be named "()" -- it is never primitive. But that too is weird.) I'm wondering if the proposal has gone off the rails a bit here by trying to have this new kind of subprogram with special resolution. It would be better if such magic was completely confined to the index-like call (which is new and isn't going to be a compatibility problem). As soon as we have an equivalent "normal" call, things start getting messy. So I've been thinking about alternatives. The obvious one would be to make these things a pair of aspects (I'm thinking "Variable_Indexing" and "Constant_Indexing"). Using aspects has several advantages: * The routines are clearly tied to the type, so there isn't any big problem supporting untagged types (or even scalar types); * For normal calls, users use the "real" name of the functions, so there isn't any resolution changes beyond the new magic notation; * We don't need any funny rules about where the routines are declared -- the need to specify the aspects would provide the appropriate result; * It's not a problem to give the same function (name) to both aspects, so there is no need to worry about having to duplicate the body; * Legality of calls would depend only on whether an appropriate profile exists for the appropriate aspect (and of course the normal rules for parameters); * Changing the aspects of a type declaration surely has the possibility of causing errors/changes down the line -- there is no doubt that it isn't Beaujolais or anything like it. OTOH, these would be pretty weird aspects: * The profile isn't fixed; it only needs the first parameter to be of the type; * There shouldn't be any limit to the number of routines specified; the only requirement beyond the first bullet is that the routines are not homographs. If we were to go this way, the "magic" *indexed call* would have at most two possible places to look for an operation: at the aspects of the prefix type, and if that is an access type, at the aspects of the designated type of the prefix type. The aspect to use would depend on whether or not the view of the prefix is a constant view (note that the object itself and the designated type could differ on that: dereferencing an access-to-constant variable would give a constant view.) Neither of these seem to have any particular difficulty. That would give one a set of profiles to resolve in the typical way: and no significant changes to resolution. One could imagine that arrays have a predefined version of these index aspects, with the obvious semantics; that would unify the syntax/semantics and avoid problems if someone tries to define these on an array type (otherwise we'd potentially have an ambiguity). Those would name a pair of anonymous functions like: function (Arr : in Array_Type; Index : in Index_Subtype) return Component_Type; function (Arr : access Array_Type; Index : in Index_Subtype) return access Ref_Component_Type; (The latter is a reference type.) If we go this way, I wonder if we would want a way to specify this aspect in generic contracts (it might make sense to be able to using indexing calls in a generic body). Anyway, food for thought. **************************************************************** From: Tucker Taft Sent: Thursday, February 18, 2010 10:23 PM I agree the "()" idea seems to be creating several resolution issues. Using an aspect specification may be cleaner. If we are using an aspect specification for the Default_Iterator, it might be preferable to stick with that approach for other syntactic sugaring. The aspect specification might just be specifying the name of the Variable_Indexing and Constant_Indexing functions, which could then be overloaded as desired to provide multiple indexing functions. **************************************************************** From: Brad Moore Sent: Friday, February 19, 2010 10:38 PM > > This way, the expansion also works for containers where it doesn't > > make as much sense to start at the first element. For example, a > > recursive binary tree container would probably want to start the > > iteration at the root node of the tree, rather than start at the > > left most leaf node. > > I don't see this, as you can define First however you need for the > iterator object. An iterator is *not* a container, and you don't have > to use the same definition of First for the iterator as for the > container. As Tucker notes, there is a single default iterator that > allows you to use special syntax, but that is far too limiting for a > general concept. I was thinking of a *recursive* binary tree container. I am having a difficult time seeing how one could use First and Next recursively to iterate through a tree. It is fairly easy to iterate through a tree non-recursively, and return a cursor to where the iteration last left off, and then picking up from where the iteration left off, with a call to Next. But I think this would be hard to do using a recursive stack-based approach. I suppose though, that for such a container, one would just have to use the Iterate procedure and not bother with this iterator syntax. If someone really wants to support the iterator syntax for such a container, they could provide a non-recursive First and Next as you suggest. > > As a further example, I have > > implemented a parallel recursive binary tree container which makes > > even less sense to think of starting the iteration at First, and > > iterating through the elements using Next. I do provide an Iterate > > procedure that manages the parallelism. > > A procedure seems appropriate for that, as it must not have an > dependency between iterators nor any transfers of control. With a > procedure body, dependencies are tough to write and exceptions are the > only transfer of control. Perhaps the proposed Preconditions, Postconditions would make it easier to specify the dependency rules. > I think that parallel loops would need to be syntactically marked, > since the loop bodies would have to be written with a restricted set > of features: no dependence between iterations, use of atomic > variables/protected objects for sums and the like, and limited or no > transfers of control. In an ideal world, those restrictions would be > enforced by the compiler. > A parallel loop construct isn't a bad idea, but it doesn't make much > sense by itself. You'd want to define other constructs that also could > be executed in parallel (parallel blocks and the like). I also have implemented generics that similarly make it easier to do parallel recursion. This allows me to write parallel functions and parallel procedures. Syntactic sugar might be able to provide a similar expansion to the parallel looping generics. The expansion is somewhat similar, but involves different generics. You have convinced me that it is probably not a good approach to try to unify the syntax between sequential iterators and parallel iterators. I am now thinking that instead, it would be better to try to unify syntax between parallel loops and parallel recursion. I think that a pragma based approach might work better. eg. Parallel looping: Sum : Integer := 0; for I in 1 .. 100_000_000 Sum := Sum + I; end loop; pragma Parallel_Loop (Work_Seeking_Integer_Addition_Reducer, Sum); In this case, Work_Seeking_Integer_Addition_Reducer is the name of a instantiated generic procedure, and Sum is the name of a global variable referenced within the loop. could expand to; with Work_Seeking_Integer_Addition_Reducer; Sum : Integer := 0; declare procedure Iteration (Start : Integer; Finish : in out Integer; Others_Seeking_Work : not null access Parallel.Work_Seeking_State; Sum : in out Integer) is begin for I in Start .. Finish loop Sum := Sum + I; if Others_Seeking_Work.all then Others_Seeking_Work.all := False; Finish := I; exit; end if; end loop; end Iteration; begin Work_Seeking_Integer_Addition_Reducer (From => 1, To => Integer'Value (Command_Line.Argument (2)), Process => Iteration'Access, Item => Sum); end; eg. Parallel Recursion: function Fibonacci (Number : Natural) return Natural is begin if Number < 2 then return Number; else return Fibonacci (Number - 2) + Fibonacci (Number - 1); end if; end Fibonacci; pragma Parallel_Recursion (Fibonacci, Two_Way_Recursive_Integer_Addition.Parallel_Recursion); In this case, Fibonacci is the name of the function to be executed in parallel, and Two_Way_Recursive_Integer_Addition.Parallel_Recursion is the name of a instantiated generic package. This could expand to the following; Note that this seems like a fair amount of code, it is mostly a template that follows a repeatable pattern. I used this same pattern to create recursive parallel iteration through a binary tree. The expansion essentially creates two versions of the code. One is the sequential version, which you want to execute most of the time, and the second version is the parallel version which gets executed when worker tasks become available to do more work. The parallel version looks very similar to the sequential version, but instead of calling the function recursively, it calls an instantiated function which ultimately causes a split in execution between available processors. with Two_Way_Recursive_Integer_Addition; use Two_Way_Recursive_Integer_Addition; function Fibonacci (Value : Natural) return Natural is function Parallel_Fibonacci (Number : Natural) return Natural; Supervisor : aliased Parallel_Recursion.Overall_Work_Supervisor (Process => Parallel_Fibonacci'Access); Others_Seeking_Work : aliased Parallel_Recursion.Work_Seeking_State; use type Parallel_Recursion.Work_Seeking_State; function Parallel_Fibonacci (Number : Natural) return Natural is begin if Number < 2 then return Number; elsif not Others_Seeking_Work then return Parallel_Fibonacci (Number - 2) + Parallel_Fibonacci (Number - 1); else declare Reduction : aliased Parallel_Recursion.Work_Team_Manager; Sequential_Result : Natural; begin Sequential_Result := Parallel_Recursion.Recurse (Number - 2, Left, Reduction'Unchecked_Access, Supervisor) + Parallel_Recursion.Recurse (Number - 1, Right, Reduction'Unchecked_Access, Supervisor); -- Use the parallel result if work was split parallally, -- otherwise use the sequential result return Parallel_Recursion.Result (Reduction, Sequential_Result); end; end if; end Parallel_Fibonacci; Result : Integer := 0; begin Parallel_Recursion.Initialize (Supervisor'Unchecked_Access, Others_Seeking_Work'Unchecked_Access); Result := Parallel_Fibonacci (Value); Parallel_Recursion.Finalize (Supervisor); return Result; end Fibonacci; The pragmas would cause needed restrictions to be enforced within the parallel bodies. If a compiler vendor didn't want to support the parallel expansion, then they could ignore the pragma and the code would execute as it does today, sequentially. > Please don't kill this nice sequential proposal by loading up all > kinds of parallel cruft on it. It was definitely not my intent to kill this AI. I was looking to see if it could be expanded to cover both parallel and sequential iterators, since it seems on the surface that would be a fair bit of common ground between these two types of iterators. I see now that these are two vastly different beasts. I also wanted to minimize the chance that we might paint ourselves into a corner with new syntax that might preclude parallel support in the language in the future. I think you have convinced me that this is not a concern here. The question might be, is there anything that could be done will all this parallel cruft I have been rambling on about. Does it seem like a pragma based approach like I have suggested would be worth developing into an AI? Obviously, this is too late for the current amendment. We have a lot of AI's to deal with right now and so probably if these ideas have enough merit to develop into an AI, they should be more or less shelved until we get through the pile of other AI' waiting to be processed. I would certainly be willing to work on this in the background though, or at least I hope to create a white paper to better explain how these generics work. **************************************************************** From: Tucker Taft Sent: Tuesday, May 18, 2010 8:53 PM Here is an update to AI05-0139-2. [This is version /05 of the AI - Editor.] This does *not* have wording yet. It just fixes the !proposal part to match the changes specified in the minutes. I will work on the wording for the actual ARG meeting. The !proposal section might be worth reviewing in the upcoming ARG subgroup phone call. ****************************************************************