Rationale for Ada 2012

John Barnes
Contents   Index   References   Search   Previous   Next 

8.3 Iterating and updating containers

This topic was largely covered in Chapter 6, “Iterators, Pools, etc.” on iterators and pools which introduced the generic package Ada.Iterator.Interfaces whose specification is
   type Cursor;
   with function Has_Element(Position: Cursor) return Boolean;
package Ada.Iterator_Interfaces is
   pragma Pure(Iterator_Interfaces);
   type Forward_Iterator is limited interface;
   function First(Object: Forward_Iterator) return Cursor is abstract;
   function Next(Object: Forward_Iterator; Position: Cursor)
                      return Cursor is abstract;
   type Reversible_Iterator is limited interface and Forward_Iterator;
   function Last(Object: Reversible_Iterator) return Cursor is abstract;
   function Previous(Object: Reversible_Iterator;
                       Position: Cursor) return Cursor is abstract;
end Ada.Iterator_Interfaces;
This generic package is used by both existing and new container packages. For illustration we consider the list container Ada.Containers.Doubly_Linked_Lists. Here is its specification giving all new and changed material in full (marked -- 12) and identifying most existing entities by comment only.
with Ada.Iterator_Interfaces;    -- 12
   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)    -- 12
   type List is tagged private    -- 12
      with Constant_Indexing => Constant_Reference,
              Variable_Indexing => Reference,
              Default_Iterator => Iterate,
              Iterator_Element => Element_Type;
   pragma Preelaborable_Initialization(List);
   type Cursor is private;
   pragma Preelaborable_Initialization(Cursor);
   Empty_List: constant List;
   No_Element: constant Cursor;
   function Has_Element(Position: Cursor)
            return Boolean;    -- moved 12
   package List_Iterator_Interfaces is    -- 12
            new Ada.Iterator_Interfaces(Cursor, Has_Element);
   ...    -- functions "=", Length, Is_Empty, Clear, Element
   ...    -- procedures Replace_, Query_, Update_Element
   type Constant_Reference_Type    -- 12
      (Element: not null access constant Element_Type) is private
      with Implicit_Dereference => Element;
   type Reference_Type    -- 12
      (Element: not null access Element_Type) is private
      with Implicit_Dereference => Element;
   function Constant_Reference    -- 12
            (Container: aliased in List;
             Position: in Cursor) return Constant_Reference_Type;
   function Reference    -- 12
            (Container: aliased in out List;
             Position: in Cursor) return Reference_Type;
   procedure Assign(Target: in out List; Source: in List);    -- 12
   function Copy(Source: List) return List;    -- 12
   ...    -- Move, Insert, Prepend, Append,
   ...    -- Delete, Delete_First, Delete_Last,
   ...    -- Reverse_Elements, Swap, Swap_Links, Splice,
   ...    -- First, First_Element, Last, Last_Element,
   ...    -- Next, Previous, Find, Reverse_Find,
   ...    -- Contains, Iterate, Reverse_Iterate
   function Iterate(Container: in List) return    -- 12
   function Iterate    -- 12
            (Container: in List;
             Start: in Cursor) return
   ...    -- generic package Generic_Sorting
    ... -- not specified by the language
end Ada.Containers.Doubly_Linked_Lists;
Note that the function Has_Element has been moved. In Ada 2005 it was declared towards the end between Contains and Iterate. It has been moved so that it can be used as an actual parameter in the declaration of List_Iterator_Interfaces using the instantiation of Ada.Iterator_Interfaces.
It will be recalled from Section 6.3 on iteration that in Ada 2012 we can simply write
for C in The_List.Iterate loop
   ...   --do something via cursor C
end loop;
or even
for E of The_List loop
   ...   -- do something to Element E
end loop;
rather than the laborious and error prone
C: The_List.Cursor;
E: Twin;
F: Forward_Iterator'Class := The_List.Iterate;
C := F.First;
   exit when not The_List.Has_Element(C);
   E := The_List.Element(C);
   ...   -- do something to E
   C := F.Next(C);
end loop;
Note that in the case of
for C in The_List.Iterate loop
   ...   -- do something via cursor C
end loop;
we are not permitted to assign to C since that would upset the mechanism of the loop. There is an analogy with the traditional loop statement. If we write
for K in A'Range loop
   A(K) := 0;
end loop;
then the language prevents us from making a direct assignment to the loop parameter K.
If we write
for E of The_List loop
   ...   -- do something to Element E
end loop;
then we can change the element E unless The_List has been declared as constant.
It will be recalled that subprograms Replace_Element, Query_Element and Update_Element are defined for all containers in Ada 2005. Query_Element and Update_Element permit in situ operations. Thus in order to find the value of some component Q of an element of The_List identified by cursor C we can write either
X := Element(C).Q;
or we can first declare a slave procedure
procedure Get_Q(E: in Element_Type) is
   X := E.Q;
end Get_Q;
and then call Query_Element thus
Query_Element(C, Get_Q'Access);
The advantage of the former is that it is easy but it could be slow because it copies the whole element which could be enormous. The advantage of the latter is that it does not copy the element; its disadvantage is that it is somewhat incomprehensible.
In Ada 2012, we can do much better. The type List now has new functions Reference and Constant_Reference, so we can write for example
X := The_List.Constant_Reference(C).Q;
This works because the function Constant_Reference returns a value of Constant_Reference_Type and this moreover has aspect Implicit_Dereference whose value is Element.
However, we can simplify this even more because the type List has aspects Constant_Indexing and Variable_Indexing which refer to the functions Constant_Reference and Reference. The result is that we can simply write
X := The_List(C).Q;    -- gosh that's better
which is a lot better than calling Query_Element.
Similarly, if we just want to update the component Q of some element given by a cursor C, then in Ada 2005 we either have to create a whole new element with the new value for Q and then use Replace_Element thus
Temp: E_Type := Element(C);
Temp.Q := X;
Replace_Element(The_List, C, Temp);
or declare a slave procedure and use Update_Element thus
procedure Put_Q(E: in out Element_Type) is
   E.Q := X;
end Put_Q;
Update_Element(The_List, C, Put_Q'Access);
Again the first is slow, the second is gruesome (well, they are both gruesome really).
In Ada 2012 we simply write
The_List(C).Q := X;    -- gosh again
which implicitly uses the aspect Variable_Indexing to call the function Reference which gives access to the element.
It will be remembered that there are dire warnings in Ada 2005 about tampering with elements and cursors. Thus we must not use Update_Element (that is via Put_Q in the example above) to do other things such as add new elements.
Although tampering is still possible in Ada 2012; the new features discourage it. Thus if we write
The_List(C).Q := X;
rather than calling Update_Element then no tampering can occur (unless X is some gruesome function).
Similarly if we write
for C in My_Container loop
   Delete(My_Container, Position => C);    --illegal
end loop;
then we are prevented from madness since the parameter Position of Delete is of mode in out and this is not matched by the loop parameter C which is a constant. However, if we write the loop out using First and Next as illustrated earlier then we could get into trouble.

Contents   Index   References   Search   Previous   Next 
© 2011, 2012, 2013 John Barnes Informatics.
Sponsored in part by:
The Ada Resource Association:


and   Ada-Europe: