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
.
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.
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. |
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 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 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 |
|
||||||||||||||||||||||||
Composite Types |
|
Ranges are used by Ada for many purposes.
type Rankings is new Integer range 1..10;
type Voltages is digits 12 range -12.0..12.0;
type Days is (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday);
subtype Weekends is Days range Saturday..Sunday;
for Num in 1..10 loop
Put_Line(Integer'Image(Num));
end loop;
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 |
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 |
Range |
Evaluates to the range
of values for a range. For an array the Range attribute corresponds
to Array'First..Array'Last . |
Buffer'Range |
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.
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
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.
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;
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.
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.
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.
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.
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;
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);
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.
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
.
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.
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.
There are three kinds of operations on protected objects.
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.
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.
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.
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.