Rationale for Ada 2012
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
Ada.Strings.UTF_Encoding
–
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.
Ada.Strings.UTF_Encoding.Conversions
–
declares five functions
Convert between the
UTF schemes.
Ada.Strings.UTF_Encoding.Strings
–
declares functions
Encode and
Decode
between the type
String and the UTF schemes.
Ada.Strings.UTF_Encoding.Wide_Strings
–
declares six similar functions for the type
Wide_String.
Ada.Strings.UTF_Encoding.Wide_Wide_Strings
–
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
generic
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)
AC.Unbounded_Synchronized_Queues,
AC.Bounded_Synchronized_Queues,
AC.Unbounded_Priority_Queues,
AC.Bounded_Priority_Queues.
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)
generic
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;
generic
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
overriding
entry Enqueue(New_Item: in Queue_Interfaces.ET)
overriding
entry Dequeue(Element: out Queue_Interfaces.ET);
overriding
function Current_Use return Count_Type;
overriding
function Peak_Use return Count_Type;
private
...
end Queue;
private
...
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
AC.Unbounded_Synchronized_Queues(My_Interface);
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_Queue.Enqueue(37);
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
generic
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
loop
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.
© 2011, 2012, 2013 John Barnes Informatics.
Sponsored in part by: