Rationale for Ada 2012
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
generic
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
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) -- 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
List_Iterator_Interfaces.Reversible_Iterator'Class;
function Iterate -- 12
(Container: in List;
Start: in Cursor) return
List_Iterator_Interfaces.Reversible_Iterator'Class;
... -- generic package Generic_Sorting
private
... -- 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;
loop
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
begin
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
begin
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.
© 2011, 2012, 2013 John Barnes Informatics.
Sponsored in part by: