\chapter{Type system}
Ada's type system allows the programmer to construct powerful
abstractions that represent the real world, and to provide valuable
information to the compiler, so that the compiler can find many logic
or design errors before they become bugs. It is at the heart of the
language, and good Ada programmers learn to use it to great advantage.
Four principles govern the type system:
\begin{description}
\item[String typing] Types are incompatible with one another, so it is
not possible to mix apples and oranges. There are, however, ways to
convert between types.
\item[Static typing] The programmer must declare the type of all
objects, so that the compiler can check them. This is in contrast to
dynamically typed languages such as Python, Smalltalk, or Lisp.
Also, there is no type inference like in OCaml.
\item[Abstraction] Types represent the real world or the problem at
hand; not how the computer represents the data internally. There are
ways to specify exactly how a type must be represented at the bit
level, but we will defer that discussion to another chapter.
\item[Name Equivalence] Two types are compatible if and only if they
have the same name; not if they just happen to have the same size or
bit representation. You can thus declare two integer types with the
same ranges that are totally incompatible, or two record types with
the exact same components, but which are incompatible.
\end{description}
Types are incompatible with one another. However, each type can have
any number of subtypes, which are compatible with one another, and
with their base type.
\section{Predefined types}
\begin{description}
\item[Integer] This type covers at least the range $-2^{15}+1 ..
+2^{15}-1$ The
standard\footnote{http://www.adaic.com/standards/95lrm/html/RM-3-5-4.html}
also defines Natural and Positive subtypes of this type. Natural
starts at 0, positive starts at 1.
\item[Float] There is only a very weak implementation requirement on
this
type\footnote{http://www.adaic.com/standards/95lrm/html/RM-3-5-7.html};
most of the time you would define your own floating-point types, and
specify your precision and range requirements.
\item[Duration] A fixed point type used for timing. It represents a
period of time in seconds
\footnote{http://www.adaic.com/standards/95lrm/html/RM-A-1.html}
\item[Character] A special form of enumerations. There are two
predefined kinds of character types: 8-bit characters (called
Character) and 16-bit characters (called Wide\_Character). Ada 2005
adds a 32-bit character type called Wide\_Wide\_Character.
\item[String and Wide\_String] These are two undefinite array types,
of Character and Wide\_Char\-ac\-ter respectively. Ada 2005 adds a
Wide\_Wide\_String type. The standard library contains packages for
handling strings in three variants: fixed length
(Ada.Strings.Fixed), with varying length below a certain upper bound
(Ada.Strings.Bounded), and unbounded length (Ada.Strings.Unbounded).
Each of these packages has a Wide\_ and a Wide\_Wide\_ variant.
\item[Boolean] A Boolean in Ada is an Enumeration of False and True
with special semantics.
\end{description}
Packages System and System.Storage\_Elements predefine some types
which are primarily useful for low-level programming and interfacing
to hardware.
\begin{description}
\item[System.Address] an address in memory.
\item[System.Storage\_Elements.Storage\_Offset] an offset, which can
be added to an address to obtain a new address. You can also
substract one address from another to get the offset between them.
Together, Address, Storage\_Offset and their associated subprograms
provide for address arithmetic.
\item[System.Storage\_Elements.Storage\_Count] a subtype of
Storage\_Offset which cannot be negative, and represents the memory
size of a data structure (similar to C's size\_t).
\item[System.Storage\_Elements.Storage\_Element] in most computers,
this is a byte. Formally, it is the smallest unit of memory that has
an address.
\item[System.Storage\_Elements.Storage\_Array] an array of
Storage\_Elements without any meaning, useful when doing raw memory
access.
\end{description}
\section{The Type Hierarchy}
Types are organised hierarchically. A type inherits properties from
types above it in the hierarchy. For example, all scalar types
(integer, enumeration, modular, fixed-point and floating-point types)
have operators ``$<$'', ``$>$'' and arithmetic operators defined for
them, and all discrete types can serve as array indexes.
\begin{figure}[!htbp]
\includegraphics[width=\textwidth]{Ada_types.png}
\caption{Ada's type hierarchy}
\label{fig:types}
\end{figure}
Here is a broad overview of each category of types; please follow the
links for detailed explanations. Inside parenthesis there are
equivalences in C and Pascal for readers familiar with those
languages.
\begin{description}
\newcommand{\myitem}[2]{\item[#1] \textsl{\footnotesize #2}}
\myitem{Signed Integers}{(int, INTEGER)} Signed Integers are defined
via the range of values needed. \myitem{Unsigned
Integers}{(unsigned, CARDINAL)} Unsigned Integers are called
Modular Types. Apart from being unsigned they also have wrap-around
functionality. \myitem{Enumerations}{(enum, char, bool, BOOLEAN)}
Ada Enumeration types are a separate type family. \myitem{Floating
point} {(float, double, REAL)} Floating point values are defined
by the digits needed, the relative error bound. \myitem{Ordinary
and Decimal Fixed Point}{(DECIMAL)} Fixed point type are defined
by their delta, the absolute error bound. \myitem{Arrays} {([],
ARRAY [] OF, STRING)} Arrays with both compile-time and run-time
determined size are supported. \myitem{Record} {(struct, class,
RECORD OF)} A record is a composite type that groups one or more
fields. \myitem{Access} {(*, \^, POINTER TO)} Ada's Access types
may be more than just a simple memory address. \myitem{Task \&
Protected} {(no equivalence in C or Pascal)} Task and Protected
types allow the control of concurrency \myitem{Interfaces}{(no
equivalence in C or Pascal)}
\end{description}
\section{Concurrency Types}
The Ada language uses types for one more purpose in addition to
classifying data + operations. The type system integrates concurrency
(threading, parallelism). Programmers will use types for expressing
the concurrent threads of control of their programs.
The core pieces of this part of the type system, the task types and
the protected types are explained in greater depth in the tasking
chapter\ref{ch:tasking}.
\section{Limited Types}
Limiting a type means disallowing assignment. The ``concurreny types''
described above are always limited. Programmers can define their own
types to be limited, too, like this: \lstinline|type T is limited ...;|
(The ellipsis stands for private, or for a record definition, see
the corresponding subsection on this page.) A limited type also
doesn't have an equality operator unless the programmer defines one.
\section{Defining new types and subtypes}
You can define a new type with the following syntax:
\lstinline|type T is...| followed by the description of the type,
as explained in detail in each category of type.
Formally, the above declaration creates a type and its first subtype
named T. The type itself is named T'Base. But this is an academic
consideration; for most purposes, it is sufficient to think of T as a
type.
As explained above, all types are incompatible; thus:
\begin{mylstlisting}
type Integer_1 is range 1 .. 10;
type Integer_2 is range 1 .. 10;
A : Integer_1 := 8;
B : Integer_2 := A; -- illegal!
\end{mylstlisting}
\noindent is illegal, because Integer\_1 and Integer\_2 are different and
incompatible types. It is this feature which allows the compiler to
detect logic errors at compile time, such as adding a file descriptor
to a number of bytes, or a length to a weight. The fact that the two
types have the same range does not make them compatible: this is name
equivalence in action, as opposed to structural equivalence. (Below,
we will see how you can convert between incompatible types; there are
strict rules for this.)
\subsection{Creating subtypes}
You can also create new subtypes of a given type, which will be
compatible with each other, like this:
\begin{mylstlisting}
type Integer_1 is range 1 .. 10;
subtype Integer_2 is Integer_1 range 7 .. 11;
A : Integer_1 := 8;
B : Integer_2 := A; -- OK
\end{mylstlisting}
Now, \lstinline!Integer_1! and \lstinline{Integer_2} are compatible
because they are both subtypes of the same type, namely
\lstinline{Integer_1'Base}.
It is not necessary that the ranges overlap, or be included in one
another. The compiler inserts a run-time range check when you assign
A to B; if the value of A, at that point, happens to be outside the
range of \lstinline{Integer_2}, the program raises
\lstinline{Constraint_Error}. This is called an \emph{implicit type
conversion''}.
There are a few predefined subtypes which are very useful:
\begin{mylstlisting}
subtype Natural is Integer range 0 .. Integer'Last;
subtype Positive is Integer range 1 .. Integer'Last;
\end{mylstlisting}
\subsection{Derived types}
A derived type is a new, full-blown type created from an existing one.
Like any other type, it is incompatible with its parent; however, it
inherits the primitive operations defined for the parent type.
\begin{mylstlisting}
type Integer_1 is range 1 .. 10;
type Integer_2 is new Integer_1 range 2 .. 8;
A : Integer_1 := 8;
B : Integer_2 := A; -- illegal!
\end{mylstlisting}
Here both types are discrete; it is mandatory that the range of the
derived type be included in the range of its parent. Contrast this
with subtypes. The reason is that the derived type inherits the
primitive operations defined for its parent, and these operations
assume the range of the parent type. Here is an illustration of this
feature:
\begin{mylstlisting}
procedure Derived_Types is
package Pak is
type Integer_1 is range 1 .. 10;
procedure P (I : in Integer_1); -- primitive operation, assumes 1 .. 10
type Integer_2 is new Integer_1 range 8 .. 10; -- must not break P's assumption
end Pak;
package body Pak is
-- omitted
end Pak;
use Pak;
A : Integer_1 := 4;
B : Integer_2 := 9;
begin
P (B); -- OK, call the inherited operation
end Derived_Types;
\end{mylstlisting}
When we call
\lstinline{P (B)}, there is \emph{no} type conversion; we call a completely new version of \lstinline{P},
which accepts only parameters of type \lstinline{Integer_2}. Since there
is no type conversion, there is no range check either. This is why
the set of acceptable values for the derived type (here, 8 .. 10) must
be included in that of the parent type (1 .. 10).
\section{Subtype categories}
Ada supports various categories of subtypes which have different
abilities. Here is an overview in alphabetical order.
\subsection{Anonymous subtype}
A subtype which does not have a name assigned to it. Such a subtype is
created with a variable declaration:
\begin{mylstlisting}
X : String (1 .. 10) := others => ' ');
\end{mylstlisting}
Here, (1 .. 10) is the constraint. This variable declaration is equivalent to:
\begin{mylstlisting}
subtype Anonymous_String_Type is String (1 .. 10);
X : Anonymous_String_Type := others => ' ');
\end{mylstlisting}
\subsection{Base type}
Within this document we almost always speak of subtypes. But where
there is a subtype there must also be a base type from which it is
derived. In Ada all base types are anonymous and only subtypes may be
named.
But it is still possible to use base type by the use of the 'Base attribute.
\subsection{Constrained subtype}
A subtype of an indefinite subtype that adds a constraint. The
following example defines a 10 character string sub-type.
\begin{mylstlisting}
subtype String_10 is String (1 .. 10);
\end{mylstlisting}
If all constraints of an original indefinite subtype are defined then
the new sub-type is a definite subtype.
\subsection{Definite subtype}
A definite subtype is a subtype whose size is known at compile-time.
All subtypes which are not indefinite subtypes are, by definition,
definite subtypes.
Objects of definite subtypes may be declared without additional
constraints.
\subsection{Indefinite subtype}
An \emph{indefinite subtype} is a subtype whose size is not known at
compile-time but is dynamically calculated at run-time. An indefinite
subtype does not by itself provide enough information to create an
object; an additional constraint or explicit initialization expression
is necessary in order to calculate the actual size and therefore
create the object.
\begin{mylstlisting}
X : String := "This is a string";
\end{mylstlisting}
X is an object of the indefinite (sub)type String. Its constraint is
derived implicitly from its initial value. X may change its value, but
not its bounds.
It should be noted that it is not necessary to initialize the object
from a literal. You can also use a function. For example:
\begin{mylstlisting}
X : String := Ada.Command_Line.Argument (1);
\end{mylstlisting}
This statement reads the first command-line argument and assigns it to X.
\subsection{Named subtype}
A subtype which has a name assigned to it. These subtypes are created
with the keyword type (remember that types are always anonymous, the
name in a type declaration is the name of the first subtype) or
subtype. For example:
\begin{mylstlisting}
type Count_To_Ten is range 1 .. 10;
\end{mylstlisting}
Count\_to\_Ten is the first subtype of a suitable integer base type.
If you however would like to use this as an index constraint on
String, the following declaration is illegal:
\begin{mylstlisting}
subtype Ten_Characters is String (Count_to_Ten);
\end{mylstlisting}
This is because String has Positive as index, which is a subtype of
Integer (these declarations are taken from package Standard):
\begin{mylstlisting}
subtype Positive is Integer range 1 .. Integer'Last;
type String is (Positive range <>) of Character;
\end{mylstlisting}
So you have to use the following declarations:
\begin{mylstlisting}
subtype Count_To_Ten is Integer range 1 .. 10;
subtype Ten_Characters is String (Count_to_Ten);
\end{mylstlisting}
Now Ten\_Characters is the name of that subtype of String which is
constrained to Count\_To\_Ten. You see that posing constraints on
types versus subtypes has very different effects.
\subsection{Unconstrained subtype}
A subtype of an indefinite subtype that does not add a constraint only
introduces a new name for the original subtype.
\begin{mylstlisting}
subtype My_String is String;
\end{mylstlisting}
\lstinline|My_String| and \lstinline|String| are interchangeable.
\section{Qualified expressions}
In most cases, the compiler is able to infer the type of an
expression; for example:
\begin{mylstlisting}
type Enum is (A, B, C);
E : Enum := A;
\end{mylstlisting}
Here the compiler knows that \lstinline{A} is a value of the type Enum.
But consider:
\begin{mylstlisting}
procedure Bad is
type Enum_1 is (A, B, C);
procedure P (E : in Enum_1) is... -- omitted
type Enum_2 is (A, X, Y, Z);
procedure P (E : in Enum_2) is... -- omitted
begin
P (A); -- illegal: ambiguous
end Bad;
\end{mylstlisting}
The compiler cannot choose between the two versions of P; both would
be equally valid. To remove the ambiguity, you use a ``qualified
expression'':
\begin{mylstlisting}
P (Enum_1'(A)); OK
\end{mylstlisting}
As seen in the following example, this syntax if often used when
creating new objects. If you try to compile the example, it will fail
with a compilation error since the compiler will determine that 256 is
not in range of Byte.
\mylstinputlisting{code/convert_evaluate_as.adb}
\section{Type conversions}
Data does not always come in the format you need them and you face the
task of converting them. Ada as a true multi-purpose language with a
special emphasis on "mission critical", "system programming" and
"safety" has several conversion techniques. As the most difficult
part is choosing the right one, I have sorted them so that you should
try the first one first; the last technique is "the last resort if all
other fails". Also added are a few related techniques which you might
choose instead of actually converting the data.
Since the most important aspect is not the result of a successful
conversion, but how the system will react to an invalid conversion,
all examples also demonstrate '''faulty''' conversions.
\subsection{Explicit type conversion}
An explicit type conversion looks much like a function call; it does
not use the ''tick'' (apostrophe, ') like the qualified expression
does.
\begin{mylstlisting}
Type_Name (''Expression'')
\end{mylstlisting}
The compiler first checks that the conversion is legal, and if it is,
it inserts a run-time check at the point of the conversion; hence the
name ''checked conversion''. If the conversion fails, the program
raises \lstinline|Constraint_Error|. Most compilers are very smart
and optimise away the constraint checks; so, you need not worry about
any performance penalty. Some compilers can also warn that a
constraint check will always fail (and optimise the check with an
unconditional raise).
Explicit type conversions are legal:
\begin{itemize}
\item between any two numeric types
\item between any two subtypes of the same type
\item between any two types derived from the same type
\item and ''nowhere else''
\end{itemize}
\begin{mylstlisting}
I: Integer := Integer (10); --Unnecessary explicit type conversion
J: Integer := 10; --Implicit conversion from universal integer
K: Integer := Integer'(10); --Use the value 10 of type Integer: qualified expression
--(qualification not necessary here).
\end{mylstlisting}
This example illustrates explicit type conversions:
\mylstinputlisting{code/convert_checked.adb}
Explicit conversions are possible between any two numeric types:
integers, fixed-point and floating-point types. If one of the types
involved is a fixed-point or floating-point type, the compiler not
only checks for the range constraints, but also performs any loss of
precision necessary.
Example 1: the loss of precision causes the procedure to only ever
print "0" or "1", since \lstinline{P / 100} is an integer and is always
zero or one.
\begin{mylstlisting}
with Ada.Text_IO;
procedure Naive_Explicit_Conversion is
type Proportion is digits 4 range 0.0 .. 1.0;
type Percentage is range 0 .. 100;
function To_Proportion (P : in Percentage) return Proportion is
begin
return Proportion (P / 100);
end To_Proportion;
begin
Ada.Text_IO.Put_Line (Proportion'Image (To_Proportion (27)));
end Naive_Explicit_Conversion;
\end{mylstlisting}
Example 2: we use an intermediate floating-point type to guarantee the
precision.
\begin{mylstlisting}
with Ada.Text_IO;
procedure Explicit_Conversion is
type Proportion is digits 4 range 0.0 .. 1.0;
type Percentage is range 0 .. 100;
function To_Proportion (P : in Percentage) return Proportion is
type Prop is digits 4 range 0.0 .. 100.0;
begin
return Proportion (Prop (P) / 100.0);
end To_Proportion;
begin
Ada.Text_IO.Put_Line (Proportion'Image (To_Proportion (27)));
end Explicit_Conversion;
\end{mylstlisting}
\subsection{Checked conversion for non-numeric types}
The examples above all revolved around conversions between numeric
types; it is possible to convert between any two numeric types in this
way. But what happens between non-numeric types, e.g. between array
types or record types? The answer is two-fold:
\begin{itemize}
\item you can convert explicitly between a type and types derived from
it, or between types derived from the same type,
\item and that's all. No other conversions are possible.
\end{itemize}
Why would you want to derive a record type from another record type?
Because of representation clauses. Here we enter the realm of
low-level systems programming, which is not for the faint of heart,
nor is it useful for desktop applications. So hold on tight, and
let's dive in.
Suppose you have a record type which uses the default, efficient
representation. Now you want to write this record to a device, which
uses a special record format. This special representation is more
compact (uses fewer bits), but is grossly inefficient. You want to
have a layered programming interface: the upper layer, intended for
applications, uses the efficient representation. The lower layer is a
device driver that accesses the hardware directly and uses the
inefficient representation.
\begin{mylstlisting}
package Device_Driver is
type Size_Type is range 0 .. 64;
type Register is record
A, B : Boolean;
Size : Size_Type;
end record;
procedure Read (R : out Register);
procedure Write (R : in Register);
end Device_Driver;
\end{mylstlisting}
The compiler chooses a default, efficient representation for
\lstinline{Register}. For example, on a 32-bit machine, it would probably
use three 32-bit words, one for A, one for B and one for Size. This
efficient representation is good for applications, but at one point we
want to convert the entire record to just 8 bits, because that's what
our hardware requires.
\begin{mylstlisting}
package body Device_Driver is
type Hardware_Register is new Register; -- Derived type.
for Hardware_Register use record
A at 0 range 0 .. 0;
B at 0 range 1 .. 1;
Size at 0 range 2 .. 7;
end record;
function Get return Hardware_Register; -- Body omitted
procedure Put (H : in Hardware_Register); -- Body omitted
procedure Read (R : out Register) is
H : Hardware_Register := Get;
begin
R := Register (H); -- Explicit conversion.
end Read;
procedure Write (R : in Register) is
begin
Put (Hardware_Register (R)); -- Explicit conversion.
end Write;
end Device_Driver;
\end{mylstlisting}
In the above example, the package body declares a derived type with
the inefficient, but compact representation, and converts to and from
it.
This illustrates that \emph{type conversions can result in a change of
representation}.
\subsection{View conversion, in object-oriented programming}
Within object-oriented programming you have to distinguish between
specific types and class-wide types.
With specific types, only conversions to ancestors are possible and,
of course, are checked. During the conversion, you do not "drop" any
components that are present in the derived type and not in the parent
type; these components are still present, you just don't see them
anymore. This is called a ''view conversion''.
There are no conversions to derived types (where would you get
the further components from?); \emph{extension aggregates} have
to be used instead.
\begin{mylstlisting}
type Parent_Type is tagged null record;
type Child_Type is new Parent_Type with null record;
Child_Instance : Child_Type;
-- View conversion from the child type to the parent type:
Parent_View : Parent_Type := Parent_Type (Child_Instance);
\end{mylstlisting}
Since, in object-oriented programming, an object of child type
\emph{is an} object of the parent type, no run-time check is
necessary.
With class-wide types, conversions to ancestor and child types are
possible and are checked as well. These conversions are also view
conversions, no data is created or lost.
\begin{mylstlisting}
procedure P (Parent_View : Parent_Type'Class) is
-- View conversion to the child type:
One : Child_Type := Child_Type (Parent_View);
-- View conversion to the class-wide child type:
Two : Child_Type'Class := Child_Type'Class (Parent_View);
\end{mylstlisting}
This view conversion involves a run-time check to see if
\lstinline{Parent_View} is indeed a view of an object of type
\lstinline{Child_Type}. In the second case, the run-time check accepts
objects of type \lstinline{Child_Type} but also any type derived
from \lstinline{Child_Type}.
\subsubsection{View renaming}
A renaming declaration does not create any new object and performs no
conversion; it only gives a new name to something that already exists.
Performance is optimal since the renaming is completely done at
compile time. We mention it here because it is a common idiom in
object oriented programming to rename the result of a view conversion.
\begin{mylstlisting}
type Parent_Type is tagged null record;
type Child_Type is new Parent_Type with null record;
Child_Instance : Child_Type;
Parent_View : Parent_Type'Class renames Parent_Type'Class (Child_Instance);
\end{mylstlisting}
Now, \lstinline{Parent_View} is not a new object, but another name for
\lstinline{Child_Instance}.
\subsection{Address conversion}
Ada's access type is not just a memory location (a thin pointer).
Depending on implementation and the access type used, the access might
keep additional information (a fat pointer). For example GNAT keeps
two memory addresses for each access to an indefinite object --- one
for the data and one for the constraint informations (Size, First,Last).
If you want to convert an access to a simple memory location you can
use the package Address\_To\_Access\_Conversions. Note however that
an address and a fat pointer cannot be converted reversibly into one
another.
The address of an array object is the address of its first component.
Thus the bounds get lost in such a conversion.
\begin{mylstlisting}
type My_Array is array (Positive range <>) of Something;
A: My_Array (50 .. 100);
A'Address = A(A'First)'Address
\end{mylstlisting}
\subsection{Unchecked conversion}
One of the great criticisms of Pascal was ``there is no escape''. The reason was that sometimes you have to convert the incompatible. For this purpose, Ada has the generic function \lstinline|Unchecked_Conversion|:
\begin{mylstlisting}
generic
type Source (<>) is limited private;
type Target (<>) is limited private;
function Ada.Unchecked_Conversion (S : Source) return Target;
\end{mylstlisting}
\lstinline|Unchecked_Conversion| will bit-copy the data and there are
absolutely no checks. It is \emph{your} chore to make sure that the
requirements on unchecked conversion as stated in RM 13.9
\footnote{http://www.adaic.com/standards/95lrm/html/RM-13-9.html} are
fulfilled; if not, the result is implementation dependent and may even
lead to abnormal data.
A function call to (an instance of) \lstinline|Unchecked_Conversion| will
copy the source to the destination. The compiler may also do a
conversion in place (every instance has the convention intrinsic).
To use \lstinline|Unchecked_Conversion| you need to instantiate the generic.
In the example below, you can see how this is done. When run, the
example it will output ``A = -1, B = 255''. No error will be reported,
but is this the result you expect?
\mylstinputlisting{code/convert_unchecked.adb}
\subsection{Overlays}
If the copying of the result of \lstinline|Unchecked_Conversion| is too
much waste in terms of performance, then you can try overlays, i.e.
address mappings. By using overlays, both objects share the same
memory location. If you assign a value to one, the other changes as
well. The syntax is:
\begin{mylstlisting}
for Target'Address use expression;
pragma Import (Ada, Target);
\end{mylstlisting}
where \lstinline|expression| defines the address of the source object.
While overlays might look more elegant than
\lstinline|Unchecked_Conversion|, you should be aware that they are
even more dangerous and have even greater potential for doing
something very wrong. For example if \lstinline{Source'Size < Target'Size} and you assign a value to Target, you might
inadvertently write into memory allocated to a different object.
You have to take care also of implicit initialisations of objects of
the target type, since they would overwrite the actual value of the
source object. The Import pragma with convention Ada can be used to
prevent this, since it avoids the implicit initialisation, RM B.1
\footnote{http://www.adaic.com/standards/95lrm/html/RM-B-1.html}.
The example below does the same as the example from ``Unchecked Conversion''.
\mylstinputlisting{code/convert_address_mapping.adb}
\subsection{Export / Import}
Just for the record: There is still another method using the
\lstinline{Export} and \lstinline{Import} pragmas. However, since this
method completely undermines Ada's visibility and type concepts even
more than overlays, it has no place here in this language introduction
and is left to experts.
\section{See also }
\subsection{Ada Reference Manual}
\begin{itemize}
\item Type Declarations \footnote{http://www.adaic.com/standards/95lrm/html/RM-3-2-1.html}
\item Objects and Named Numbers \footnote{http://www.adaic.com/standards/95lrm/html/RM-3-3.html}
\item Discriminants \footnote{http://www.adaic.com/standards/95lrm/html/RM-3-7.html}
\item Access Types \footnote{http://www.adaic.com/standards/95lrm/html/RM-3-10.html}
\item Static Expressions and Static Subtypes \footnote{http://www.adaic.com/standards/95lrm/html/RM-4-9.html}
\item Unchecked Type Conversions \footnote{http://www.adaic.com/standards/95lrm/html/RM-13-9.html}
\item Operational and Representation Attributes \footnote{http://www.adaic.com/standards/95lrm/html/RM-13-3.html}
\item Annex K (informative) Language-Defined Attributes \footnote{http://www.adaic.com/standards/95lrm/html/RM-K.html}
\end{itemize}