Introducing Ada

Jim Rogers


Introduction

Common wisdom states that software development time is 10% design, 10% coding, 60% debug and 20% test. Note that the last 80% of the project is spent trying to find and eliminate mistakes made in the first 20% of the project. Would you consider using a tool that changed those numbers to 20% design, 60% coding, 10% debug, and 10% test, while cutting the entire cycle time in half? 

Studies have shown that Ada produces these kinds of productivity enhancements when compared with C. Of course, if you already use C, you have a significant investment in support tools. You have language sensitive editors, code building tools, debugging tools, and syntax checking tools. It is hard to justify investment in new tools when the old tools seem to be working so well. Of course, it is pretty difficult to cut your overall development time in half using your current tools.

Ada produces these benefits because it is designed to support software engineering as a human activity. It provides unsurpassed expressiveness for defining data. The data definitions provide information usable by the compiler to perform correctness checking and, when appropriate, to generate bounds checking code in your executable. Compare this with C or C++. Those languages are designed on the assumption that the software developer knows what he or she is doing and never makes mistakes. In fact, one of the challenges for C and C++ programming is developing the expertise to avoid the subtle programming traps provided by those languages.

A more complete description of how Ada is designed to be used can be found in the Ada 95 Rationale . A full description of all Ada features can be found in the Ada 95 Language Reference Manual .

History of Ada

Ada is an unusual language because it was developed in response to a detailed set of requirements. In the early 1970s the United States Air Force discovered that their software systems were written in hundreds of different languages, and dialects of languages. This was seen as a serious problem. During military conflicts it is critical that military software must be maintained and updated efficiently. It is just not efficient to have to support hundreds of different languages. The Air Force joined with the other branches of the military to develop a set of requirements for a modern high level programming language that would meet their many needs. Once they had those requirements, they surveyed the available set of languages in an attempt to find one to use as a development standard. No suitable language was found.

The requirements were revised after comments from various software experts in government, industry , and academia. A competition was then created for companies to create a language meeting the government requirements. Those requirements included

After several rounds of competition the winner was a team from France lead by Jean Ichbiah. A name for this new language was chosen by members of the government team. The decision was to name the language in honor of Augusta Ada Byron, Countess of Lovelace. Ada was the mathematician who developed the programming for Charles Babbage's difference engine in the early 1800s. She is recognized as the world's first programmer.

Ada achieved ANSI standardization in 1980, and ISO standardization in 1983. In 1995 the language standard was updated. This update caused Ada to become the world's first internationally standardized object oriented language.

Current Features

Feature
Description
Strongly Typed
Ada is perhaps the most strongly typed language in commercial use. Strong typing is seen as a tool to ensure data correctness. C++, Java, and C# all claim to be strongly typed. Ada is more strongly typed than those languages. Unlike C, C++, Java, or C#, Ada does not provide implicit conversions between numeric types. Strong typing allows the compiler to identify some computational errors that can only be found through inspection, testing, or debugging in less strongly typed languages.
Modular
Ada provides a mechanism called packages that provide encapsulation and name spaces. Packages can be defined as separate compilation units, allowing a very efficient separate compilation model for developers. Packages are split into two pieces. The package specification defines the public interface to all the data and subprograms defined in the package. The package body provides the implementation of all the subprograms. This combination forces the programmer to define a contract to be used by all modules interfacing with a package.
Object Oriented
Ada fully supports programming by extension. It also actively supports polymorphic subprogram calls.
Concurrent
Ada has a very mature and robust syntax for creating concurrent (sometimes called threaded) applications. Most of the Ada concurrency features are over 20 years old, yet they are still far ahead of other commonly used languages.
Readable
Ada syntax derives from the Algol family of languages. Other members of this family include Pascal, Modula, and PL/SQL. Even managers can read Ada code.
Expressible
Ada allows you to define your own numeric types. The compiler can then determine if  those types are being used appropriately.
   type voltages is digits 12 range -1.0..1.0;

The above type definition defines a type called voltages with 12 decimal digits of precision and a valid range of values from -1.0 through 1.0. Any attempt to assign a value to a variable of this type outside this range will have one of two results. If the assignment is static, performed at compile time, the compiler will issue an error. If the assignment is performed at run time the compiler supplied runtime checks will detect the error and raise an exception.

Ada arrays can start at any value, not only 0 or 1. This allows you to map index values directly to problem descriptions.

Basic Ada Syntax

Every program must have a starting place. In the C family of languages this starting place is always a function called main. Ada has no rule about the name of the program starting point. The program starting point is a compilation unit containing only a procedure with no parameters. The simplest Ada programs can be written entirely in one procedure. The following "hello world" example demonstrates how to write a very simple Ada program.

Hello World

-----------------------------------------------------------
-- Hello World program
-----------------------------------------------------------

with Ada.Text_Io;
use Ada.Text_Io;

procedure Hello is
begin
Put_Line("Hello World!");
end Hello;

The first three lines are comments. Ada comments start with a pair of hyphens and continue to the end of the line. The with clause declares that this program requires access to a package named Ada.Text_IO. This makes the package visible to the program, but does not make its contents directly visible. The use clause makes the contents of the Ada.Text_IO package visible to this program.

Ada identifiers are not case sensitive. Thus procedure and Procedure are equivalent.

Hello is the name of this procedure, which is the starting point for our little program.
Put_Line is a procedure from the Ada.Text_IO package that takes a string as a parameter and outputs to standard output.
Put_Line appends a newline character to the end of the output.

The combination of begin and end defines the body of the procedure.

Ada does not provide a preprocessor. Preprocessors were left out of the language on purpose. The view of the Ada language developers was that preprocessor macros are unsafe. Interestingly, many C++ developers also believe that preprocessor macros are unsafe and should be avoided whenever possible. The Ada withwith the same module as many times as you want without causing compilation or linking problems. clause establishes a compilation dependency. It does not cause the build tools to modify the source code like a preprocessor does. This means that you can

Ada Types

Ada provides a large number of kinds of data types.

Ada does not have a predefined inheritance hierarchy like many object oriented programming languages. Ada allows you to define your own data types, including numeric data types. Defining your own type in Ada creates a new type. In C or C++, the typedef command simply creates an alias for a type.

Elementary Types
Access Types
Used to reference objects. Ada access types are similar to reference types in C++ and Java.
Scalar Types
Discrete Types
Enumeration Types

Ada provides some predefined enumeration types. The Boolean type is an enumeration type with two values: True and False. The Character type is another enumeration type. You are allowed to define your own enumeration types.

type Colors is (Red, Orange, Yellow, Green, Blue, Indigo, Violet);

Enumeration types are not numeric types.

Integer Types
Signed Integer Types

The predefined type Integer is a signed integer type. You are allowed to define your own signed integer types.

type My_Int is range 1..100;

Even though the type definition above does not allow negative values, the type My_Int is implemented as a signed integer. All arithmetic on signed integers must deal with overflow and underflow issues. Ada will raise the exception Constraint_Error if an attempt is made to assign an out of range value to a signed integer object.

Modular Types

You must define your own modular types.

type byte is mod 2**8;

The above definition defines an unsigned integer (modular) type named byte. The range of values for byte is 0 through 255, or the cycle of values derived from the modulus of 256. The expression 2**8 is read as 2 to the 8th power.

All arithmetic on modular types results in a value within the modular type's valid range. For instance, if you define an instance of the byte type described above:

A : byte := 0;

This instance, A, is explicitly initialized to 0. Subtracting 1 from A will result in a value of 255. Modular types will not generate overflow or underflow errors.


Real Types
Floating Point Types

Ada provides a predefined floating point type called float. You are allowed to define your own floating point types.

type Sin_Values is digits 10 range -1.0..1.0;

This type definition defines a type named Sin_Values with 10 decimal digits of precision and a valid range of values from -1.0 through 1.0.

type Double is digits 15;

This type definition defines a floating point type named Double with 15 decimal digits of precision. This type would correspond exactly to the double primitive type in Java. The range restriction shown in the Sin_Values
type definition is optional.

Fixed Point Types
Fixed point types are real types with a fixed number of digits to the right of the radix character. These types are very much like decimal types in Cobol. Fixed point types come in two kinds. Fixed point types always have a predefined distance between neighboring values.
Ordinary

The distance between values is implemented as a power of 2.

type Batting_Averages is delta 0.001 range 0.0..1.0;

Batting_Averages is a fixed point type whose values are evenly spaced real numbers in the range from 0 through 1. The distance between the values is no more than 1/1000. The actual distance between values may be 1/1024, which is 2-10 .

Decimal

The distance between values is implemented as a power of 10.

type Balances is delta 0.01 digits 9 range 0.0..9_999_999.99;

The important different in the definition of a decimal fixed point type from an ordinary fixed point type is the specification of the number of digits of precision. This example also shows how Ada allows you to specify numeric literals with underscores separating groups of digits. The underscore is used to improve readability.





Composite Types
array

All arrays in Ada are instances of some type. There are some predefined array types in Ada. The string type is defined as an array of characters. Array types may be constrained or unconstrained types. Constrained types have a predefined size. Unconstrained types may have instances of different sizes.

Constrained type definition
type Int_Buffer is array (1..10) of Integer;

Int_Buffer is an array type. The valid range of index values is 1 through 10. Each element of the array is an Integer.

Unconstrained type definition
type String is array (Positive range <>) of Character;

This is the actual definition of a string in Ada. A string is an array of characters. The array may be any length, but the index will be a subset of the predefined integer subtype named Positive . Positive is defined as all Integer values starting at 1 through the highest value of Integer . Each instance of an indefinite array type must have its size declared when the instance is defined.

Last_Name : String(1..20);

Last_Name is defined as a 20 character string.

Array Index Values

Ada array types must specify the range of values for their array index. Any discrete type can be used as an array index. The purpose of this freedom is to allow the programmer to cleanly model the real world in code.
For instance, if you want to collect the frequency of occurrence of numbers spaced around a normalized value you can define an array type as follows:

type Normalized_Distribution is array(-100..100) of Natural;

Natural is a predefined subtype of Integer with a minimum value of 0 and a maximum value of the highest value of Integer . If your program attempts to access an element outside the range of index values specified for your array type the exception Constraint_Error will be raised. 

If you want to collect the sales of a store for each day of the week you could provide the following declarations.

type Days is (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday);
type Daily_Sales is array(Days) of Float;

This defines an enumeration type for days of the week. It then defines an array type indexed by that enumeration type. How could we deal with such an array. After all, enumeration types are not numeric types. Iteration would seem to be an issue. Ada solves this problem with the syntax of its for loop.

function Weekly_Total(Shop_Sales : Daily_Sales) return Float is

Sum : Float := 0.0;
begin
for index
in Shop_Sales'Range loop
Sum := Sum + Shop_Sales(index);
end loop;
return Sum;
end Weekly_Total;

This function cleanly iterates through all values in the Daily_Sales object passed through the function parameter. 'Range is an attribute of an array that returns the range of values for the index. The for loop iterates through that range.

record

A record in Ada is equivalent to a struct in C, or a record in Pascal. A record is a grouping of possibly dissimilar data elements into a single type. Ada defines two basic kinds of records, ordinary records and tagged records. Tagged records are extensible through inheritance. Ordinary records are not extensible.

Ordinary Record Type
type Inventory_Item is record
UPC_Code : String(1..20);
Description : String(1..256);
Quantity  : Natural;
Unit_Cost : Float;
end record;

Every instance of Inventory_Item contains four fields: UPC_Code, Description, Quantity, and Unit_Cost.

Tagged Record Type
type Person is tagged record
Name : String(1..20);
Gender : Gender_Type;
Age : Natural;
end record;

Like an ordinary record, a tagged record may contain many fields. The difference is that a tagged record becomes the root of an inheritance hierarchy.

type Employee is new Person with record
Id : Integer;
Salary : Float;
end record;

Employee extends Person and add two additional fields, Id and Salary.

protected

Protected types are designed to safely implement data sharing between tasks. Protected types are protected from inappropriate simultaneous access by multiple tasks. The definition of a protected type is split into two parts, the interface and private definitions, and the implementation of the operations associated with the protected type.

protected type Counting_Semaphore is
procedure Release;
entry Acquire;
function Lock_Count return Natural;
private
Count : Natural := 0;
end Counting_Semaphore;

The code snippet above defines the public interface to a Counting_Semaphore. It also defines the private data member of that type. This code snippet is called the specification of the protected type. It provides a calling contract with any code module that needs to interface with an instance of Counting_Semaphore.

protected body Counting_Semaphore is
procedure Release is
begin
if Count > 0 then
Count := Count - 1;
end if;
end Release;

entry Acquire when Count < 11 is
begin
Count := Count + 1;
end Acquire;

function Lock_Count return Natural is
begin
return Count;
end Lock_Count;
end Counting_Semaphore;

There are three kinds of operations allowed on a protected type. Procedures are used to modify the state of the protected type. Procedures get exclusive access to the instance of the protected type. Entries also may update the state of the protected type, but they have a boundary condition that must be true before they can be executed. Entries get exclusive access to the instance of the protected type. Functions must not change the state of the protected type. They can only report state. Functions get shared access to the instance of the protected type.

task
Task types are used to define tasks with identical behavior. A task is a concurrent thread of execution in an Ada program. Tasks correspond to Threads in Java. Task definitions are split into two parts. The task specification defines the public interface to a task. This interface consists of some set of entry calls by which this task may be called from another task.

task type Producer is
entry Start(Num_Items : in Positive);
end Producer;

Producer is a task type with a single entry. The Start entry requires a calling task to pass in a parameter with the formal name of Num_Items. The actual behavior of a task is defined in the task body.

task body Producer is
Quantity : Positive;
begin
accept Stop (Num_Items : in Positive) do
Quantity := Num_Items;
end Stop;
for Iteration in 1..Quantity loop
Put_Line("Produced Item" & Positive'Image(Iteration));
end loop;
end Producer;

The instance of Producer will suspend at the accept call until some other task calls this task's Start entry. When the call happens the value in the formal parameter Num_Items is copied into the local variable Quantity . The Producer will then execute the loop for Quantity iterations. In this example, when the loop completes the task completes.


At Home on the Range

Ranges are used by Ada for many purposes.

A range is specified with the notation

low_value .. high_value

You need to specify a range when you define an array. Ada array indices may begin at any integer or enumeration value. The set of index values used for an array is determined by a range.

type Buffer is array (1..10) of Float;
type Normalized_Counts is array (-10..10) of Integer;
type Weekday_Sales is array(Days range Monday..Friday) of Float;

Ada provides a set of special functions called attributes to help in dealing with ranges. Attributes are evaluated by calling them with a special notation sometimes called a tick notation.

Attribute Name
Meaning
Example
First
Evaluates to the first or lowest value in a range. For scalar data types First evaluates to the lowest valid value for the type. For arrays First evaluates to the first or lowest index value.
Days'First
Normalized_Counts'First
Last
Evaluates to the last or highest value in a range. For scalar data types Last evaluates to the highest valid value for the type. For arrays Last evaluates to the last or highest index value.
Days'Last
Normalized_Counts'Last
Range
Evaluates to the range of values for a range. For an array the Range attribute corresponds to Array'First..Array'Last .
Buffer'Range
Voltages'Range


Ada Operators


Operator Java
C/C++ Ada
Assignment =
= :=
Equality ==
== =
NonEquality !=
!= /=
Greater Than
>
>
>
Less Than
<
<
<
Greater Than Or Equal
>=
>=
>=
Less Than Or Equal
<=
<=
<=
PlusEquals +=
+=
SubtractEquals -=
-=
MultiplyEquals *=
*=
DivisionEquals /=
/=
OrEquals |=
|=
AndEquals &=
&=
Modulus %
% mod
Remainder

rem
AbsoluteValue

abs
Exponentiation

**
Range

..
Membership
instanceof

in
Logical And
&&
&&
and
Logical Or
||
||
or
Logical Not
!
!
not
Bitwise And
&
&
and
Bitwise Or
|
|
or
Bitwise Exclusive Or
^
^
xor
Bitwise Not
~
~
not
String Concatenation
+

&

C++ allows extensive overloading of operators. Ada allows a limited overloading of operators. The exception in Ada is that the assignment operator ( := ) cannot be overridden. When you override the equality operator (= ) you also implicitly override the inequality operator ( /= ). Java does not allow overriding of operators.

If you want to achieve the equivalent of overriding the assignment operator in Ada you must declare your type to inherit from the abstract tagged type Controlled defined in the package Ada.Finalization. Controlled types provide three operations that can be overridden.

Ada also allows the definition of limited types. Any type declared limited has no predefined operators, including assignment. Use of limited types allows the programer to selectively restrict the available operations on a type. Only those operations specifically provided by the programmer will be available for a limited type.

The package Ada.Finalization defines a second abstract tagged type named Limited_Controlled. Limited_Controlled types do not have an adjust procedure.

Any attempt to assign a value to an object of a limited type will result in a compile time error message.

Operator Examples

This example is divided into three files. The first file is a package specification. This package specification defines two types and the subprograms for those types.

The Days type is an enumeration type containing the names of the days of the week in English. Days has the procedure Print_Message defined for it.

The Daily_Sales type is an array of floats indexed by the values in type Days. Daily_Sales has two functions defined for it: Total and Geometric_Mean.

The package Operator_Examples becomes visible to another compilation unit when that compilation unit names  Operator_Examples in a  with clause. Java does not require this explicit declaration of dependency. Instead, the compiler must scan every line of code to determine external dependencies. The public contents of this package specification become directly visible to a foreign compilation unit when it follows the with clause with a use clause. The Ada use clause is similar to a Java import statement. 

-------------------------------------------------------------------
-- Ada operator examples
-------------------------------------------------------------------

package Operator_Examples is
type Days is (Monday, Tuesday, Wednesday, Thursday, Friday,
Saturday, Sunday);
procedure Print_Message(For_Day : in Days);

type Daily_Sales is array(Days) of Float;
function Total(Sales : in Daily_Sales) return Float;
function Geometric_Mean(Sales : in Daily_Sales) return Float;
end Operator_Examples;

The next file is the package body, or implementation. The package body contains the definitions of all the subprograms declared in the package specification. In this example the package body also declares a dependency upon two packages: Ada.Text_Io and Ada.Numerics.Elementary_Functions . As you can see above, the exponentiation operation is defined as ** . The normal version of this operator takes an integer value as its power. I wanted to pass it a fractional value. I declared a dependency upon Ada.Numerics.Elementary_Functions and then included the same package in a use clause so that I could use the overloaded version of the exponentiation operator that takes a float for the exponent value.


with Ada.Text_Io; use Ada.Text_Io;
with Ada.Numerics.Elementary_Functions;
use Ada.Numerics.Elementary_Functions;

package body Operator_Examples is
procedure Print_Message(For_Day : in Days) is
begin
if For_Day in Saturday..Sunday then
Put_Line("Go Fishing!");
else
Put_Line("Go To Work!");
end if;
end Print_Message;

function Total(Sales : in Daily_Sales) return Float is
Sum : Float := 0.0;
begin
for I in Sales'range loop
Sum := Sum + Sales(I);
end loop;
return Sum;
end Total;

function Geometric_Mean(Sales : in Daily_Sales) return Float is
Product : Float := 1.0;
begin
for I in Sales'range loop
Product := Product * Sales(I);
end loop;
return Product**(1.0 / Float(Sales'Length));
end Geometric_Mean;
end Operator_Examples;

The third file contains the parameterless procedure used as the starting point for a program to exercise the Operator_Examples package. This package declares a dependency upon two different I/O packages. The package Ada.Text_Io defines text file operations and procedures for the input and output of strings and characters. The package Ada.Float_Text_Io defines text file input and output procedures for the type float . Both packages have Put procedures. The package Ada.Float_Text_Io overloads the Put operations defined in Ada.Text_Io .

-------------------------------------------------------------------
-- The driver procedure to exercise the Operator_Examples package.
-------------------------------------------------------------------

with Ada.Text_Io; use Ada.Text_Io;
with Ada.Float_Text_Io; use Ada.Float_Text_Io;
with Operator_Examples; use Operator_Examples;

procedure Operator_Exerciser is
My_Sales : Daily_Sales;
begin
My_Sales(Monday) := 1234.56;
My_Sales(Tuesday) := 1342.65;
My_Sales(Wednesday) := 1432.55;
My_Sales(Thursday) := 1332.44;
My_Sales(Friday) := 2345.67;
My_Sales(Saturday) := 2222.00;
My_Sales(Sunday) := 1232.33;

for Day in Days loop
Put(Days'Image(Day) & ": ");
Print_Message(Day);
end loop;
Put("Total sales: ");
Put(Item => Total(My_Sales), Aft => 2, Exp => 0);
New_Line;
Put("Mean Sales: ");
Put(Item => Geometric_Mean(My_Sales), Aft => 3, Exp => 0);
New_Line;
end Operator_Exerciser;

The output from this program is:

MONDAY: Go To Work!
TUESDAY: Go To Work!
WEDNESDAY: Go To Work!
THURSDAY: Go To Work!
FRIDAY: Go To Work!
SATURDAY: Go Fishing!
SUNDAY: Go Fishing!
Total sales: 11142.20
Mean Sales: 1537.634

Looping

Ada provides three different looping constructs.

Simple Loop

The simplest loop in Ada is an unconditional loop.
   loop
-- Loop body goes here
end loop;

This loop will continue forever, or until you execute an exit command within the loop. In Ada, the exit command terminates a loop. It does not terminate a program.

   loop
if condition then
exit;
end if;
end loop;

This construct is so common that a shortened version is available.

   loop
exit when condition;
end loop;

The simple loop combined with an exit statement is the most general form of looping in Ada, allowing you to perform your test at the top, bottom, or middle of each iteration.

While loop

The Ada while loop is like the while loop in most languages. It is a conditional loop that performs the condition test at the top of the loop.

   while condition loop
-- Loop body goes here
end loop;

For Loop

The Ada for loop iterates through an explicitly stated range of discrete values. The concept of a range is very important in Ada. A range is specified by the notation lowest_value .. highest_value .

   for variable in low_value .. high_value loop
-- Loop body goes here
end loop;

The name of a discrete type can be used in place of the value. In that case the loop will iterate over all values in the type, starting at the lowest value. Ranges always must be expressed as low_value..high_value. They can never be expressed in reverse order. You can iterate backwards through a range by adding the reserved word reverse.

   for variable in reverse low_value .. high_value loop
-- Loop body goes here
end loop;

The control variable used in a for loop is only visible within the loop body. It is a read-only variable within the loop body, meaning that you cannot explicitly assign a value to the control variable. You do not declare the control variable before you use it. The compiler determines the data type for the range and creates the control variable to be an instance of that type.

Conditional Statements

If Statements

The Ada if statement is fully blocked.

   if condition  then
-- statement(s)
end if;

if condition then
-- statement(s)
else
-- statement(s)
end if;

The condition must always evaluate to a Boolean value (False or True).

I will compare this with the C if statement.

In C the if statement controls a conditional jump to the statement immediately following the if statement.

   if (condition)
/* statement */

In C the statement following the if may be a simple statement or a compound statement. The condition must evaluate to an integer. C has no boolean data type. C considers 0 to be false and any non-zero value to be true. The C if statement presents several opportunities for coding traps. The simplest problem occurs when you place a semicolon directly after the condition:

   if (condition);
/* statement */

This is always erroneous, but always legal C code. The semicolon acts as a null statement for the if condition. Execution will always flow to the statement following the semicolon because that statement is not controlled by the if statement. This problem never happens in Ada. If you put the extra semicolon in Ada code the compiler will produce an error message indicating incorrect syntax.

Another problem with the C if statement is how it handles nested if statement with else alternatives.

   if (condition)
if(condition 2)
/* statement */
else
/*statement */

In this example the else is associated with the nearest if , not with the outer if. This mistake cannot be made with Ada.

Case Statements

The Ada case statement is used to make one of many choices based upon an expression that evaluates to a discrete type. The Ada case statement must either explicitly deal with all possible values of the discrete type or it must contain an Others clause.

   case expression is
when choice list =>
sequence-of-statements
when choice list =>
sequence-of-statements
when others =>
sequence-of-statements

end case;

The Others clause corresponds to the default option in a C switch statement. The Ada case statement does not suffer from fall through like the C switch statement. There is no need to explicitly break at the end of each case as must be done in C to prevent fall through.

   type Directions is (North, South, East, West);
Heading : Directions;

case Heading is
when North =>
Y := Y + 1;
when South =>
Y := Y - 1;
when East =>
X := X + 1;
when West =>
X := X - 1;
end case;

In this example the Others clause is not used because all possible values of the Directions type are explicitly handled.


Subprograms

Ada provides two kinds of subprograms, and makes a strong distinction between the two.

Procedures never return a value through a return statement, but may pass parameters out through their parameter list. Procedure parameters must have a passing mode. There are three passing modes: IN, OUT, and IN OUT. IN parameters are treated as constants within a subprogram. They may be read from but not written to. OUT parameters have no reliable initial value upon entering the procedure, but are expected to be written to. IN OUT parameters have an initial value and may be written to. If no mode is specified, the default mode is IN.

   procedure Swap(Left, Right : IN OUT Integer) is
Temp : Integer := Left;
begin
Left := Right;
Right := Temp;
end Swap;

Functions always return a value (except when the function raises an exception). Function parameters can only have the IN mode.

    function Count_Letters(Item : String) return Natural is
Count : Natural := 0;
begin
for I in Item'Range loop
if Is_Letter(Item(I)) then
Count := Count + 1;
end if;
end loop;
return Count;
end Count_Letters;

This function will count all the letters in any size string passed to it. The for loop determines the range to iterate through by inspecting the Range attribute of the string. Remember that a string is merely an array of characters.

Procedure parameter modes tell the compiler what you want to do with the parameters. They also document for the programmer what your intent is. Any parameter passed with an IN mode can cause no side effects in the calling code block. Any parameter passed with an OUT  or IN OUT mode can be expected to change.

Actual parameters to a subprogram may be passed by position, as in C, or using named notation. It is even possible to mix the notations, with some restrictions. In general, most Ada programmers prefer to use named notation when there are multiple parameters. The Put procedure that writes an unsigned integer type to standard output is defined as:

procedure Put(Item : in Num; 
Width : in Field := Default_Width;
Base : in Number_Base := Default_Base);

This procedure declares three formal parameters. The last two have default values. This means that you do not need to provide values for those parameters if you are happy with the defaults. When calling this procedure using all defaults you would use one of the following formats:

   Put(Count);

Put(Item => Count);

Both procedure calls above accomplish the same thing. The first one uses positional notation. The actual parameter Count is passed into the formal parameter Item. This is made explicit with the second call. Clearly, there is no great advantage to using named notation in this instance. If, however, you want to output your integer value in base 2 you must pass the value 2 to the Base formal parameter. In this case named notation is very useful.

   Put(Item => Count, Base => 2);

Put(Base => 2, Item => Count);

Put(Count, Base => 2);

All three forms are allowed. The first form is generally preferred. Note that the use of named notation allows you to specify parameters in any order you choose. The last example shows a mixture of named and positional notation. This form has an important rule. All the positional parameters must be passed before any named parameter. Named notation is useful for code maintenance. It documents exactly what the original programmer meant to do without requiring the maintenance programmer to look up the subprogram definition to understand which parameters were receiving which value.

Packages are all around us

The primary structure used for encapsulation in Ada is called a package . Packages are used to group data and subprograms. Packages can be hierarchical, allowing a structured and extensible relationship for data and code. All the standard Ada libraries are defined within packages.

Package definitions usually consist of two parts, a package specification and a package body. The package specification declares all the public and private components in a package including data types, constants, functions, and procedures. Ada's notion of private is actually closer to the C++ or Java notion of protected. Most package specifications require a corresponding package body. The exception is a package defining only constants. Package bodies contain the implementation of all the procedures, functions, protected types, and task types defined in a package specification. Package bodies can also declare and define data types, variables, constants, functions, and procedures not declared in the package specification. Anything declared withing a package body is visible only to functions, procedures, protected types, and task types defined in the same package body. Variables and constants declared in a package body correspond to private members of C++ or Java classes.

I will repeat the code shown earlier about operators. This code clearly demonstrates the use of a user defined package.

The first part is the package specification for a package named Operator_Example . This package defines two data types and the procedures and functions associated with those data types. No private data is defined in this package.

-------------------------------------------------------------------
-- Ada operator examples
-------------------------------------------------------------------

package Operator_Examples is
type Days is (Monday, Tuesday, Wednesday, Thursday, Friday,
Saturday, Sunday);
procedure Print_Message(For_Day : in Days);

type Daily_Sales is array(Days) of Float;
function Total(Sales : in Daily_Sales) return Float;
function Geometric_Mean(Sales : in Daily_Sales) return Float;
end Operator_Examples;

The second part is the package body for Operator_Example. The functions and procedures defined within this package need to call functions and procedures from other packages. The with clause is used to identify which packages are needed. The use clause is used to simplify the naming of those functions and procedures from other packages. For instance, without the clause

   use Ada.Text_Io;

The Print_Message procedure below would need to be written as follows:

   procedure Print_Message(For_Day : in Days) is
begin
if For_Day in Saturday..Sunday then
Ada.Text_Io.Put_Line("Go Fishing!");
else
Ada.Text_Io.Put_Line("Go To Work!");
end if;
end Print_Message;

With clauses must be placed before the beginning of the package body or specification. They cannot be scattered throughout the code. A use clause may be placed in the same area as the with clause, or it may be placed in the declarative region of a code block. If it is placed in the declarative region of a code block its influence is restricted to that code block.

with Ada.Text_Io; use Ada.Text_Io;
with Ada.Numerics.Elementary_Functions;
use Ada.Numerics.Elementary_Functions;

package body Operator_Examples is
procedure Print_Message(For_Day : in Days) is
begin
if For_Day in Saturday..Sunday then
Put_Line("Go Fishing!");
else
Put_Line("Go To Work!");
end if;
end Print_Message;

function Total(Sales : in Daily_Sales) return Float is
Sum : Float := 0.0;
begin
for I in Sales'range loop
Sum := Sum + Sales(I);
end loop;
return Sum;
end Total;

function Geometric_Mean(Sales : in Daily_Sales) return Float is
Product : Float := 1.0;
begin
for I in Sales'range loop
Product := Product * Sales(I);
end loop;
return Product**(1.0 / Float(Sales'Length));
end Geometric_Mean;
end Operator_Examples;

The last part is not a package at all, but a stand-alone procedure with no parameters. Ada requires you to create a stand-alone procedure with no parameters as the starting point of your program. This procedure needs to use types and subprograms from several packages. The appropriate with clauses are placed before the beginning of the procedure. Note that the with clause does not work like the preprocessor for C or C++. The contents of a package specification are not copied into the code at the point of a with clause. The C and C++ preprocessor will generate duplicate symbol names if a file is included into the same source file multiple times. Ada avoids that problem altogether.

-------------------------------------------------------------------
-- The driver procedure to exercise the Operator_Examples package.
-------------------------------------------------------------------

with Ada.Text_Io; use Ada.Text_Io;
with Ada.Float_Text_Io; use Ada.Float_Text_Io;
with Operator_Examples; use Operator_Examples;

procedure Operator_Exerciser is
My_Sales : Daily_Sales;
begin
My_Sales(Monday) := 1234.56;
My_Sales(Tuesday) := 1342.65;
My_Sales(Wednesday) := 1432.55;
My_Sales(Thursday) := 1332.44;
My_Sales(Friday) := 2345.67;
My_Sales(Saturday) := 2222.00;
My_Sales(Sunday) := 1232.33;

for Day in Days loop
Put(Days'Image(Day) & ": ");
Print_Message(Day);
end loop;
Put("Total sales: ");
Put(Item => Total(My_Sales), Aft => 2, Exp => 0);
New_Line;
Put("Mean Sales: ");
Put(Item => Geometric_Mean(My_Sales), Aft => 3, Exp => 0);
New_Line;
end Operator_Exerciser;

Record Definitions

You can create a composite type containing multiple fields, each with possibly a different type, using a record. A  record is very much like a struct in C.
   type Address is record
Street : Unbounded_String;
City : Unbounded_String;
Postal_Code : String(1..10);
end record;

My_Address : Address;

The Address type shown above has three fields. The variable My_Address is an instance of the Address type. Record fields are accessed using a simple dot (.) notation.

   A.Street := To_Unbounded_String("1700 Pennsylvania Avenue");
A.City := To_Unbounded_String("Washington, D.C.");
A.Postal_Code := "00000-0000";

An access type corresponds to a Java reference.

   type Address_Ref is access Address;
A_Ref := new Address;
A_Ref.Street := To_Unbounded_String("17 Raven Road");
A_Ref.City := To_Unbounded_String("San Anselmo");
A_Ref.Postal_Code := "94960-1234";

Unlike C, there is no notational difference for accessing a record field directly or through an access value. To refer to the entire record accessed by an access value use the following notation:

   Print(A_Ref.all);


What? Ada Has No Class?

Ada provides tools and constructs for extending types through inheritence. The most unusual feature about Ada's approach to constructing classes and objects is that Ada has no class reserved word. In many object oriented languages the concept of a class is overloaded. It is both the unit of encapsulation and a type definition. Ada separates these two concepts. Packages are used for encapsulation. Tagged types are used to define extensible types. This allows some interesting flexibilities. It is entirely legal to define more than one tagged type in a package. This ability is most useful when you need to define mutually referencing (or circular referencing) types.

Defining a Tagged Type

A tagged type definition is syntactically simply an extension of a record type definition.
   type Person is tagged record
Name : String(1..20);
Age : Natural;
end record;

Such a type definition is performed in a package. Immediately following the type definition must be the procedures and functions comprising the primitive operations defined for this type.  Primitive operations must have one of its parameters be of the tagged type, or for functions the return value can be an instance of the tagged type, allowing you to overload functions based upon their return type. This is a stark contrast to C++ and Java which do not allow you to overload based upon the return type of a function. The tagged type is extended by making a new tagged record based upon the original type.

   type Employee is new Person with record
Emloyee_Id : Positive;
Salary : Float;
end record;

Type Employee inherits all the fields and primitive operations of type Person.

Concurrent Programming

Ada provides an extensive set of capabilities for creating programs with concurrent code modules. Java achieves many of the same results using the Thread class. An Ada concurrent code module is called a task .

Tasks and Task Types

When you create a task you can choose to make a one of a kind task, or a task type which can be used to create many identical tasks. Tasks and task types are defined in two parts. The first part defines the public interface to the task, specifying any entry calls. The second part contains the implementation of the task code. Ada tasks can communicate with each other directly using the rendezvous mechanism. A rendezvous creates a synchronization or meeting point between the task calliing another task's entry and the called task. The first task to the rendezvous will suspend until another task gets to the same rendezvous.

Task Definition

   task Simple_Task is
entry Start(Num : in Integer);
entry Report(Num : out Integer);
end Simple_Task;

task body Simple_Task is
Local_Num : Integer;
begin
accept Start(Num : in Integer) do
Local_Num := Num;
end Start;
Local_Num := Local_Num * 2;
accept Report(Num : out Integer) do
Num := Local_Num;
end Report;
end Simple_Task;

The task Simple_Task is declared to have two entries: Start and Report. Another task can communicate with Simple_Task by calling Start and passing in an Integer, or by calling Report and passing out an Integer. Simple_Task starts executing as soon as the program starts, but it does not get very far. It encounters the accept Start statement as its first executable statement. Simple_Task suspends at that accept statement until some other task calls its Start entry. During the Start entry  Simple_Task assigns the value in the formal parameter Num to the local variable Local_Num . This assignment is necessary because the Num parameter is only in scope between the do and  done statements. Upon completion of this rendezvous Simple_Task continues executing until it encounters another accept statement.  Simple_Task  again suspends if no other task has yet called its Report entry. When another task does call the Report entry the value of   Local_Num is copied to the parameter Num. Upon completion of the Rendezvous the calling task has the value from Local_Num and Simple_Task completes because there are no more statements to execute.

The example of Simple_Task shown above has one major limitation. It is a one time definition of a single task. If you want to make many instances of this task you need to create a task type. The only difference in syntax is the addition of the word type in the task interface code.

   task type Simple_Task is
entry Start(Num : in Integer);
entry Report(Num : out Integer);
end Simple_Task;

task body Simple_Task is
Local_Num : Integer;
begin
accept Start(Num : in Integer) do
Local_Num := Num;
end Start;
Local_Num := Local_Num * 2;
accept Report(Num : out Integer) do
Num := Local_Num;
end Report;
end Simple_Task;

With the creation of a task type we have the opportunity of making as many instances as we need.

   type Task_Pool is array(Positive range 1..10) of Simple_Task;
My_Pool : Task_Pool;

Declaration of the array type does not create any tasks. Declaration of the array instance creates 10 instances of Simple_Task. All this can be put together in a small program.

with Ada.Text_IO; use Ada.Text_Io;

procedure Test_Simple is
task type Simple_Task is
entry Start(Num : in Integer);
entry Report(Num : out Integer);
end Simple_Task;

task body Simple_Task is
Local_Num : Integer;
begin
accept Start(Num : in Integer) do
Local_Num := Num;
end Start;
Local_Num := Local_Num * 2;
accept Report(Num : out Integer) do
Num := Local_Num;
end Report;
end Simple_Task;

type Task_Pool is array(Positive range 1..10) of Simple_Task;
My_Pool : Task_Pool;
The_Value : Integer;
begin
for num in My_Pool'Range loop
My_Pool(num).Start(num);
end loop;

for num in My_Pool'Range loop
My_Pool(num).Report(The_Value);
Put_Line("Task" & Integer'Image(num) & " reports" & Integer'Image(The_Value));
end loop;
end Test_Simple;

The Ada rendezvous mechanisim is useful but there are many designs that require a more asynchronous behavior between tasks. Ada provides an elegant and powerful approach to creating objects that can be shared between tasks. Those object are called Protected Objects.  Just as with tasks, you can make a single version or a Protected Type, which allows you to create many instances of the same kind of shared memory object. Protected objects are protected from inappropriate mutual access by tasks.

Selective Acceptance of Entry Calls

A task may need to respond to one of many entry calls each time through its major control loop. A task may need to check if an entry has been called, but proceed immediately if it has not. Alternatively a task may need to wait for an entry call, but no more than a specified amount of time. Ada provides forms of selective accept calls for this purpose.

Selective Accept

   loop
select
accept Stop;
exit;
else
Put_Line ("Not stopped yet");
end select;
delay 0.01;
end loop;

This example shows an infinite loop. Each time through the loop the code selectively accepts the Stop entry for this task. If the entry is accepted the exit command is executed, terminating the loop. If no task has called the Stop entry the code prints Not stopped yet then delays (suspends) for 0.01 seconds.

   loop
select
accept Stop;
exit;
or
delay 0.01;
end select;
Put_Line ("Not stopped yet");
end loop;

The syntax of this example is slightly different from the previous example. The example still has an infinite loop that checks if the Stop entry has been called each time through the loop. In this example the task will simultaneously start a delay timer. If the delay expires before the Stop entry is called the string Not stopped yet is printed. If the Stop entry is called before the timer expires the timer is cancelled and the exit command is executed, terminating the loop.

   loop
select
accept Stop;
exit;
or
accept Put(Item : in Integer) do
Local_Item := Item;
end Put;
Local_Item := Local_Item * 2;
else
Put_Line("No entry calls this time");
end select;
delay 0.01;
end loop;

Each time through this loop the task checks to see if either Stop or Put has been called. If Stop has been called the exit command is executed, terminating the loop. If Put has been called Local_Item is assigned the value of Item, then that value is multiplied by 2. If neither entry has been called the task prints No entry calls this time. If the loop has not been terminated the task delays for 0.01 seconds and repeats the loop. A selective accept may have several accept alternatives.

Protected Objects and Protected Types

There are three kinds of operations on protected objects.

The following protected object implements a counting semaphore. It allows up to 5 tasks to simultaneously hold the semaphore.
   protected type Counting_Semaphore is
entry Acuire;
procedure Release;
function Count return Natural;
private
Holding_Count : Natural := 0;
end Counting_Semaphore;

protected body Counting_Semaphore is
entry Acquire when Holding_Count < 5 is
begin
Holding_Count := Holding_Count + 1;
end Acquire;

procedure Release is
begin
if Holding_Count > 0 then
Holding_Count := Holding_Count - 1;
end if;
end Release;

function Count return Natural is
begin
return Holding_Count;
end Count;
end Counting_Semaphore;

This example demonstrates the use of all three protected operations. Protected types allow you to define any necessary shared memory design. 

Selective Entry Calls

When a task calls an entry, that call may be queued up due to a closed boundary condition. The calling task may not be able to suspend indefinitely due to strict timing requirements. If this is the case the calling task can use a selective entry call. This can either be a timed entry call, supplying a timeout, or a conditional entry call, providing an immediate alternative.
   select
Semaphore.Acquire;
Acquired := True;
or
delay 0.15;
Acquired := False;
end select;

This shows how a task could try to acquire a counting semaphore as shown above, but wait no more than 0.15 seconds for success.

   select 
Semaphore.Acquire;
else
raise Resources_Blocked;
end select;

This example attempts to immediately acquire the semaphore. If the semaphore is not immediately available the exception Resources_Blocked is raised.

Generics

Ada allows generic programming, similar to C++ templates. You can define any compilation unit to be generic. This allows you to define an algorithm independent of the data type it must be used with.

If you want to create a generic package or procedure and allow the use of any non-limited type then you must delcare the formal generic parameter to be private.
   generic
type Element_Type is private;
procedure Swap(Left, Right : in out Element_Type) is
Temp : Element_Type := Left;
begin
Left := Right;
Right := Temp;
end Swap;

This procedure must be instantiated for a specific type to be used.

   with Swap;

procedure Swap_Test is
procedure Exchange is new Swap(Integer);
A : Integer := 6;
B : Integer := -19;
begin
Exchange(A, B);
end Swap_Test;

In this example procedure Exchange is instantiated as a version of Swap taking Integer values as parameters. The generic procedure is instantiated in the declarative region of procedure Swap_Test. Exchange is called in the body of the procedure. After the call to Exchange, A will contain -19 and B will contain 6. Generic instantiation is performed at compile time. This allows the compiler to perform all parameter type checks on the call.

Generics are very useful when defining protected objects containing a buffer.

   generic
type Buffer_Type is private;
package Generic_Buffer is
protected type Buffer is
entry Get(Item : out Buffer_Type);
procedure Put(Item : in Buffer_Type);
private
Internal : Buffer_Type;
Is_New : Boolean := False;
end Buffer;
end Generic_Buffer;

package body Generic_Buffer is
protected body Buffer is
entry Get(Item : out Buffer_Type) when Is_New is
begin
Item := Internal;
Is_New := False;
end Get;

procedure Put(Item : in Buffer_Type) is
begin
Internal := Item;
Is_New := True;
end Put;
end Buffer;
end Generic_Buffer;

This generic buffer allows the writing task to write to the buffer unconditionally. The reading task can only read new data. It cannot read uninitialized data nor data it has already read. This buffer pattern is used to allow the reader to sample the output of the writer at any rate equal to or less than the rate the writer writes to the buffer. In other words, the reader will be no faster than the writer.

Conclusion

Ada is an effective language for the economical creation of correct software. Along with fairly common features such as modularity and the ability to program by extension, Ada adds a sophisticated set of tools for concurrent programming and a very rich type system that allows you to create your own customized primitive types. The compiler can then use those customizations to determine coding errors. The compiler will also, as a default behavior, automatically produce safety and correctness checks for run time error detection. 

The cost of Ada compilers ranges from free to expensive. The free compilers are very robust and complete, but come with no support. The commercial compilers come with enhanced development environments and very strong support. Most of the support offered comes in the form of training in the Ada language. Every Ada compiler currently on the market has passed the compiler correctness test suite. This ensures that Ada programs are highly portable across compilers as well as across operating systems.

Ada has established itself as the world's premier language for safety critical applications. It has also been shown to be among the most economical languages to use for serious programming efforts.