Rationale for Ada 2012

John Barnes
Contents   Index   References   Search   Previous   Next 

1.3.6 Overview: Standard library

The main improvements in the standard library concern containers. But there are a number of other changes which will be described first.
In Ada 2005, additional versions of Index and Index_Non_Blank were added to the package Ada.Strings.Fixed with an additional parameter From indicating the start of the search. The same should have been done for Find_Token. So Ada 2012 adds
procedure Find_Token(
               Source: in String;
               Set: in Maps.Character_Set;
               From: in Positive;
               Test: in Membership;
               First: out Positive;
               Last: out Natural);
Similar versions are added for bounded and unbounded strings to the corresponding packages.
New child packages of Ada.Strings are added to provide conversions between strings, wide strings, or wide wide strings and UTF-8 or UTF-16 encodings. They are

declares a function Encoding to convert a String into types UTF_8, UTF_16BE, or UTF_16LE where BE and LE denote Big Endian and Little Endian respectively.

declares five functions Convert between the UTF schemes.

declares functions Encode and Decode between the type String and the UTF schemes.

declares six similar functions for the type Wide_String.

declares six similar functions for the type Wide_Wide_String.
Further new packages are Ada.Wide_Characters.Handling and Ada.Wide_Wide_Characters.Handling. These provide classification functions such as Is_Letter and Is_Lower and conversion functions such as To_Lower for the types Wide_Character and Wide_Wide_Character in a similar way to the existing package Ada.Characters.Handling for the type Character.
Experience with the package Ada.Directories added in Ada 2005 has revealed a few shortcomings.
One problem concerns case sensitivity. Unfortunately, common operating systems differ in their approach. To remedy this the following are added to Ada.Directories
type Name_Case_Kind is
 (Unknown, Case_Sensitive, Case_Insensitive, Case_Preserving);
function Name_Case_Equivalence(Name: in String) return Name_Case_Kind;
Calling Name_Case_Equivalence enables one to discover the situation for the operating system concerned.
Another problem is that the basic approach in Ada.Directories is a bit simplistic and assumes that file names can always be subdivided into a directory name and a simple name. Thus the existing function Compose is
function Compose
               (Containing_Directory: String := "";
                Name: String;
                Extension: String := "") return String;
and this requires that the Name is a simple name such as "My_File" with possibly an extension if one is not provided.
Accordingly, an optional child package is introduced, Ada.Directories.Hierarchical_File_Names, and this adds the concept of relative names and a new version of Compose whose second parameter is a relative name and various functions such as Is_Simple_Name and Is_Relative_Name.
Programs often need information about where they are being used. This is commonly called the Locale. As an example, in some regions of the world, a sum such as a million dollars is written as $1,000,000.00 whereas in others it appears as $1.000.000,00 with point and comma interchanged. An early attempt at providing facilities for doing the right thing was fraught with complexity. So Ada 2012 has adopted the simple solution of enabling a program to determine the country code (two characters) and the language code (three characters) and then do its own thing. The codes are given by ISO standards. Canada is interesting in that it has one country code ("CA") but uses two language codes ("eng" and "fra").
The information is provided by a new package Ada.Locales which declares the codes and the two functions Language and Country to return the current active locale (that is, the locale associated with the current task).
And finally, we consider the container library. Containers were a major and very valuable addition to Ada 2005 but again, experience with use has indicated that some enhancements are necessary.
We start with a brief summary of what is in Ada 2005. The parent package Ada.Containers has six main children namely Vectors, Doubly_Linked_Lists, Hashed_Maps, Ordered_Maps, Hashed_Sets, and Ordered_Sets. These manipulate definite types.
In addition there are another six for manipulating indefinite types with names such as Indefinite_Vectors and so on.
There are also two packages for sorting generic arrays, one for unconstrained types and one for constrained types.
There are four new kinds of containers in Ada 2012
bounded forms of the existing containers,
a container for a single indefinite object,
various containers for multiway trees, and
various containers for queues. 
In addition there are a number of auxiliary new facilities whose purpose is to simplify the use of containers.
We will start by briefly looking at each of the new kinds of containers in turn.
The existing containers are unbounded in the sense that there is no limit to the number of items that can be added to a list for example. The implementation is expected to use storage pools as necessary. However, many applications in high integrity and real-time areas forbid the use of access types and require a much more conservative approach. Accordingly, a range of containers is introduced with bounded capacity so that there is no need to acquire extra storage dynamically.
Thus there are additional packages with names such as Containers.Bounded_Doubly_Linked_Lists. A key thing is that the types List, Vector and so on all have a discriminant giving their capacity thus
type List(Capacity: Count_Type) is tagged private;
so that when a container is declared its capacity is fixed. A number of consequential changes are made as well. For example, the bounded form has to have a procedure Assign
procedure Assign(Target: in out List; Source: in List);
because using built-in assignment would raise Constraint_Error if the capacities were different. Using a procedure Assign means that the assignment will work provided the length of the source is not greater than the capacity of the target. If it is, the new exception Capacity_Error is raised.
Moreover, a similar procedure Assign is added to all existing unbounded containers so that converting from a bounded to an unbounded container or vice versa is (reasonably) straightforward.
A new function Copy is also needed for the bounded containers and for uniformity is similarly added to the unbounded versions.
Conversion between bounded and unbounded containers is also guaranteed with respect to streaming.
There are no bounded indefinite containers; this is because if the components are indefinite then dynamic space allocation is required for the components anyway and making the overall container bounded would be pointless.
In Ada, it is not possible to declare an object of an indefinite type that can hold any value of the type. Thus if we declare an object of type String then it becomes constrained by the mandatory initial value.
S: String := "Crocodile";
We can assign other strings to S but they must also have nine characters. We could assign "Alligator" but not "Elephant". (An elephant is clearly too small!)
This rigidity is rather a nuisance and so a new form of container is defined which enables the cunning declaration of an object of a definite type that can hold a single value of an indefinite type. In other words it is a wrapper. The new package is Ada.Containers.Indefinite_Holders and it takes a generic parameter of the indefinite type and declares a definite type Holder which is tagged private thus
   type Element_Type (<>) is private;
   with function "="(Left, Right: Element_Type) return Boolean is <>;
package Ada.Containers.Indefinite_Holders is
   type Holder is tagged private;
   ...    -- various operations
end Ada.Containers.Indefinite_Holders;
The various operations include a procedure Replace_Element which puts a value into the holder and a function Element which returns the current value in the holder.
Three new containers are added for multiway trees (unbounded, bounded, and indefinite). It might have been thought that it would be easy to use the existing containers (such as the list container) to create a tree structure. But it is difficult for various reasons concerning memory management. And so it was concluded that new containers for multiway trees should be added to Ada 2012.
The package Ada.Containers.Multiway_Trees is the unbounded form similar to the existing containers for other structures. It has all the operations required to operate on a tree structure where each node can have multiple child nodes to any depth. Thus there are operations on subtrees, the ability to find siblings, to insert and remove children and so on. The other new tree containers are Ada.Containers.Indefinite_Multiway_Trees and Ada.Containers.Bounded_Multiway_Trees which provide bounded and indefinite forms respectively.
Finally, there is a group of containers for queues. This topic is particularly interesting because it has its origins in the desire to provide container operations that are task safe. However, it turned out that it was not easy to make the existing containers task safe in a general way which would satisfy all users because there are so many possibilities.
However, there was no existing container for queues and in the case of queues it is easy to see how to make them task safe.
There are in fact four queue containers and all apply to queues where the element type is definite; these come in both bounded and unbounded forms and for synchronized and priority queues. We get (writing AC as an abbreviation for Ada.Containers)
These in turn are all derived from a single synchronized interface. This is a good illustration of the use of synchronized interfaces and especially the aspect Synchronization discussed earlier (see Section 1.3.4). First there is the following generic package which declares the type Queue as a synchronized interface (writing AC as an abbreviation for Ada.Containers and ET for Element_Type)
   type ET is private;  -- element type for definite queues
package AC.Synchronized_Queue_Interfaces is
   pragma Pure(...);
   type Queue is synchronized interface;
   procedure Enqueue
               (Container: in out Queue;
                New_Item: in ET)is abstract
                 with Synchronization => By_Entry;
   procedure Dequeue
               (Container: in out Queue;
                Element: out ET) is abstract
                 with Synchronization => By_Entry;
   function Current_Use(Container: Queue) return Count_Type is abstract;
   function Peak_Use(Container: Queue) return Count_Type is abstract;
end AC.Synchronized_Queue_Interfaces;
Then there are generic packages which enable us to declare actual queues. Thus the essence of the unbounded synchronized version is as follows (still with abbreviations AC for Ada.Containers, ET for Element_Type)
with System; use System;
with AC.Synchronized_Queue_Interfaces;
   with package Queue_Interfaces is new AC.Synchronized_Queue_Interfaces(<>);
   Default_Ceiling: Any_Priority := Priority'Last;
package AC.Unbounded_Synchronized_Queues is
   pragma Preelaborate(...);
   package Implementation is
       -- not specified by the language
   end Implementation;
   protected type Queue(Ceiling: Any_Priority := Default_Ceiling)
         with Priority => Ceiling
      is new Queue_Interfaces.Queue with
      entry Enqueue(New_Item: in Queue_Interfaces.ET)
      entry Dequeue(Element: out Queue_Interfaces.ET);
      function Current_Use return Count_Type;
      function Peak_Use return Count_Type;
   end Queue;
end AC.Unbounded_Synchronized_Queues;
The discriminant gives the ceiling priority and for convenience has a default value. Remember that a protected type is limited and when used to implement an interface (as here) is considered to be tagged. In Ada 2012, defaults are allowed for discriminants of tagged types provided they are limited as mentioned in Section 1.3.3.
Note that the Priority is given by an aspect specification. Programmers who are allergic to the multiple uses of with could of course use the old pragma Priority in their own code.
(The need for the package Implementation will be briefly explained in Section 8.6 and can be completely ignored by the user.)
Now to declare our own queue of integers say we first write
package My_Interface is new
    AC.Synchronized_Queue_Interfaces(ET => Integer);
This creates an interface for dealing with integers. Then to obtain an unbounded queue package for integers we write
package My_Q_Package is new
This creates a package which declares a protected type Queue. Now at last we can declare an object of this type and perform operations on it.
The_Queue: My_Q_Package.Queue;
The various calls of Enqueue and Dequeue are likely to be in different tasks and the protected object ensures that all is well.
The other generic queue packages follow a similar style. Note that unlike the other containers, there are no queue packages for indefinite types. Indefinite types can be catered for by using the holder container as a wrapper or by using an access type.
In Ada 2005 there are two generic procedures for sorting arrays; one is for constrained arrays and one is for unconstrained arrays. In Ada 2012, a third generic procedure is added which can be used to sort any indexable structure. Its specification is
   type Index_Type is (<>);
   with function Before(Left, Right: Index_Type) return Boolean;
   with procedure Swap(Left, Right: Index_Type);
procedure Ada.Containers.Generic_Sort(First, Last: Index_Type'Base);
pragma Pure(Ada.Containers.Generic_Sort);
Note that there is no parameter indicating the structure to be sorted; this is all done indirectly by the subprograms Before and Swap working over the range of values given by First and Last. It's almost magic!
A frequent requirement when dealing with containers is the need to visit every node and perform some action, in other words to iterate over the container. And there are probably many different iterations to be performed. In Ada 2005, this has to be done by the user defining a subprogram for each iteration or writing out detailed loops involving calling Next and checking for the last element of the container and so on. And we have to write out this mechanism for each such iteration.
In Ada 2012, after some preparatory work involving the new package Ada.Iterator.Interfaces it is possible to simplify such iterations hugely. For example, suppose we have a list container each of whose elements is a record containing two components of type Integer (P and Q say) and we want to add some global X to Q for all elements where P is a prime. In Ada 2005 we have to write the laborious
C := The_List.First;    -- C declared as of type Cursor
   exit when C = No_Element;
   E := Element(C);
   if Is_Prime(E.P) then
      Replace_Element(The_List, C, (E.P, E.Q + X));
   end if;
   C := Next(C);
end loop;
Not only is this tedious but there is lots of scope for errors. However, in Ada 2012 we can simply write
for E of The_List loop
   if Is_Prime(E.P) then E.Q := E.Q + X; end if;
end loop;
The mechanism is thus similar to that introduced in the previous section for arrays.
There are also a number of minor new facilities designed to simplify the use of containers. These include the introduction of case insensitive operations for comparing strings and for writing hash functions.

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


and   Ada-Europe: