Problem: The software crisis.
Solution: Break large programs into small independent modules.
Question: How is independent modularization achieved?
Answer: Information hiding (Parnas)
Ada's syntax, like that of most fourth-generation programming languages, is:
procedure <name> (<formal parameters>) is
<local declarations>
begin
<statements>
end <name>;
but
loop . . . end loop if . . . end if case . . . end case record . . . end record package <name> is . . . end <name>
elsif introduced to avoid problems of fully-bracketed, nested if statements
(making if more like cond).
if <condition 1> then
<statement 1>
elsif <condition 2> then
<statement 2>
elsif <condition 3> then
<statement 3>
else
<statement 4>
end if;
Note that only one 'end if' is needed.
Imperative -- two kinds:
I/O is implemented using two suggested standard packages.
Declarations -- five kinds:
Objects: variables and constants
Types: enumerations and types constructed from predefined types
Subprograms: like Algol/Pascal, but allows use of built-in operators as names of subprograms on user-defined types (operator overloading)
Packages: modules used for data abstraction
Tasks: modules used for concurrency
Separate compilation -- Ada supports true separate compilation.
Four predefined types
Integer Boolean Character Float
Float corresponds to the machine's usual precision, but programmers are
encouraged to use constraints instead for more machine independence.
type Coordinate is range -100 .. 100;type Coefficient is digits 10 range -1.0e10 .. 1.0e10;type Dollars is delta 0.01 range 0.00 .. 10_000_000.00;Preservation of Information Principle -- The user should be able to specify things at an abstract level for the compiler to implement at the low level.
Five constructors
Name equivalence
(See also the previous discussion of this topic and §4.3 Type Equivalence from Rationale for the Design of the AdaŽ Programming Language.)
A problem with name equivalence:
type Index is range 1 .. 100; AnInteger : Integer; AnIndex : Index; AnInteger := AnIndex; -- illegal with name equivalence AnIndex := AnIndex + 1; -- illegal assuming 1 is an Integer
Solution: A constraint is defined as producing a subtype.
Declaration of a subtype may be explicit.
E.g.,
subtype Index is Integer range 1 .. 100;
Declaration of a derived type must be explicit.
E.g.,
type Percent is new Integer range 0 .. 100;
Explicit coercion between a derived type and its subtype is allowed.
E.g.,
A_Percent := Percent( AnInteger );
Ada allows an identifier to belong to multiple enumerated types.
E.g.,
type Primary is ( Red, Blue, Green ); type StopLight is ( Red, Amber, Green );
Overloaded identifiers are disambiguated by context.
Index constraints allow general-purpose array procedures.
E.g.,
type Vector is array ( Integer range < > ) of Book; Collection : Vector ( 1 .. 10000 ); Fiction : Vector ( 1 .. 20000 ); procedure Sort ( aVector : Vector ) is . . .
The compiler must pass the actual bounds as hidden parameters to the procedure.
Bounds are accessed as <name>'First
and <name>'Last.
E.g.,
subtype Index is Integer range aVector'First .. aVector'Last;
Lower : Index;
Upper : Index;
Temp : Book;
begin
for Upper in A_Vector'Last .. A_Vector'First + 1 loop
for Lower in A_Vector'First .. Upper - 1 loop
if A_Vector( Lower ) > A_Vector( Lower + 1 ) then
Temp := A_Vector( Lower );
A_Vector( Lower ) := A_Vector( Lower + 1 );
A_Vector( Lower + 1 ) := Temp;
end if;
end loop;
end loop;
end Sort;
A name can be bound to:
Tax_Rate : Real := 1.0; PI : constant := 3.141592653589; Deduct_Rate : constant Real := Tax_Rate * 2.0;
Max_Size : constant Integer;
Max_Size : constant Integer := 256;
This allows a constant to be provided without defining its value as part of the interface.
(Wulf and Shaw, 1973)
function max ( x, y : integer );
begin
count := count + 1; { side effect }
if x > y then
max := x
else
max := y
end;
length := max( needed, requested );
This line modifies count without mentioning it. (See the Manifest Interface Principle.)

X := X + 1, has no access
to the outer X.
but in a block-structured language, access must be like

E.g., in Algol,
Algol decouples name access and definition from allocation with own variables.
In Pascal, decoupling is provided by use of pointers and dynamically allocated memory (new and dispose).
Ada uses packages to implement information hiding.
package Complex_Pack is
type Complex is private;
I : constant Complex; -- deferred constant
function "+" ( z1, z2 : Complex ) return Complex;
function Real_Part ( z : Complex ) return Complex;
function "+" ( x1 : Real; z2 : Complex ) return Complex;
. . .
private
type Complex is record
Re, Im : Real := 0.0;
end record;
I : constant Complex := ( 0.0, 1.0 );
end Complex_Pack;
declare
use Complex_Pack;
z : Complex;
z1 : Complex := 1.5 + 2.5 * I;
begin
. . .
Compare this approach with that of Modula-2: opaque types occupy one word.
package body Complex_Pack is
function "+" ( z1, z2 : Complex ) return Complex is
rp : constant Real := z1.re * z2.re - z1.im * z2.im;
ip : constant Real := z1.re * z2.im + z1.im * z2.re;
begin
return ( rp, ip );
end;
. . .
package Communication is
InPtr, OutPtr : Integer range 0..99 := 0;
Buffer : array ( 0 .. 99 ) of Character := ( 0..99 =>'' );
end Communication;
procedure Put is
use Communication;
begin
. . .
Buffer( InPtr ) := Next;
InPtr := ( InPtr + 1 ) mod 100;
. . .
end Put;
procedure Take is
use Communication;
begin
. . .
C := Buffer( OutPtr );
. . .
end Take;
package Stack is
procedure Push ( Item : in Integer );
procedure Pop ( Item : out Integer );
function Empty return Boolean;
function Full return Boolean;
Stack_Error : exception;
end Stack;
declare
use Stack;
First_Item : Integer;
Second_Item : Integer;
begin
. . .
Push( First_Item );
Pop( Second_Item );
. . .
if Empty() then
Push( Second_Item );
end if;
. . .
end;
package body Stack is
Stack : array ( 1..100 ) of Integer;
Top : Integer range 0..100 := 0;
procedure Push ( Item : in Integer ) is
begin
. . .
To have several instances of the same ADT, we use a generic package.
E.g.,
generic package Stack is
. . .
end Stack;
package A_Stack is new Stack; package B_Stack is new Stack;
A_Stack.Push( Item ); B_Stack.Pop( Item );
generic
Length : Natural := 100; -- default value
type Item is private;
package Stack is
. . .
procedure Push ( An_Item : in Item ) is
. . .
package A_Char_Stack is new Stack( 200, Character ); package An_Int_Stack is new Stack( 100, Integer );
package body Stack is
Stack : array ( 1..Length ) of Item;
Top : Integer range 0..Length := 0;
. . .
end Stack;
An_Int_Stack.Push( An_Item ); A_Char_Stack.Pop( An_Item );
or by using Ada's context mechanism.
e.g.,
declare
use A_Char_Stack;
use An_Int_Stack;
An_Int_Item : Integer;
A_Char_Item : Character;
begin
Push( An_Int_Item ); -- overloaded
Push( A_Char_Item );
if A_Char_Stack.Full() then -- qualified
. . .
end;
| Package Instantiation | Procedure Instantiation |
|---|---|
| separate data areas | separate data areas |
| common code area | common code area |
| static | dynamic |
Code sharing by generic packages can be complex:
No parameters

Same elements, different lengths (i.e., number of elements)
Different element types of the same size, different lengths
Elements of different sizes
Ada has seven classes of control structures, most of them fully bracketed:
if <boolean expr> then
<statements>
end if;
if <boolean expr> then
<statements>
else
<statements>
end if;
if <boolean expr> then
<statements>
elsif <boolean expr> then
<statements>
. . .
else
<statements>
end if;
infinite
loop
<statements>
end loop;
mid-decision
E.g., exit when KEY is found or when the search list is exhausted:
Index := List'First;
loop
if Index > List'Last then
exit;
end if;
if List ( Index ) = Key then
exit;
end if;
Index := Index + 1;
end loop;
An alternative, abbreviated notation is available, which has the added benefit of making the exit point more clearly visible:
Index := List'First;
loop
exit when Index > List'Last;
exit when List ( Index ) = Key;
Index := Index + 1;
end loop;
indefinite iterator
Index := List'First;
while Index <= List'Last;
loop
exit when List ( Index ) = Key;
Index := Index + 1;
end loop;
definite iterator
for Index in List'First .. List'Last
loop
exit when List ( Index ) = Key;
end loop;
but location of KEY is not known outside the loop, as I is local to the loop.
case Value is
when Value1 | Value2 =>
<statements>
when Value3 =>
<statements>
when Value4 | Value5 | Value6 =>
null;
when others
<statements>
end case;
Ada provides explicit support for three parameter modes:
inoutin outThis decouples the mode of a parameter from its implementation:
The programmer specifies how it is to be used (logic).
The compiler decides how it is to be implemented (performance).
| Compiler decides |
Copying | |||
|---|---|---|---|---|
| Reference | ||||
| In | Out | In out | ||
| Programmer decides | ||||
Ada also supports position-independent parameters with default values.
E.g.,
procedure Draw_Axes
( X_Origin, Y_Origin : Coordinate := 0;
X_Scale, Y_Scale : Real := 1.0;
X_Spacing, Y_Spacing : Natural := 1;
X_Label, Y_Label : Boolean := True;
X_Log, Y_Log : Boolean := False;
Full_Grid : Boolean := False );
The call to this procedure may specify parameters by label, omitting some:
Draw_Axes ( 500, 500, Y_Scale => 0.5, Y_Log => True,
X_Spacing => 10, Y_Spacing => 10 );
Position-independent parameters are an example of the Labelling Principle.
Default parameters are an example of the Localized Cost Principle, because they allow users to ignore options (parameters) which they do not require.
However, costs are associated with these features, as they are with all features:
complexity
feature interaction
E.g., operator overloading + default parameters:
procedure P ( X : Integer; B : Boolean := False ); procedure P ( X : Integer; Y : Integer := 0 );
What is the meaning of P(3)?
It could refer to either procedure, as the second parameter of each has a default value.
SOLUTION: A set of subprogram declarations is illegal if it introduces the potential for ambiguous calls.
E.g., overloaded enumerations + overloaded procedures:
type Primary is ( Red, Blue, Yellow ); type Stoplight is ( Red, Amber, Green ); procedure Switch ( Color : Primary; X, Y : Float ); procedure Switch ( Light : Stoplight; Y, X : Float );
Red and Switch are both overloaded.
There are no default values.
These procedure declarations are illegal, because they allow the following ambiguous call:
Switch ( Red, X => 0.0, Y => 0.0 );
EXERCISE: Was goto necessary, given that exit is provided?
E.g.,
procedure Producer ( . . . );
use Stack;
begin
. . .
Push ( Data ); -- If an exception is raised here
-- (by attempting to push to a full stack),
-- execution jumps to ***
. . .
exception
when Stack_Error =>
declare
Scratch : Integer;
begin -- *** exception handler
for I in 1 .. 3
loop
if not Empty () then
Pop ( Scratch );
end if;
end loop;
Push ( Data );
end;
end Producer;
Exceptions propagate from callee to caller until an exeption handler is found:
If an exception is raised in procedure A, then look for the corresponding handler in the procedure that called A (say, B).
If no handler for the raised exception is given in B, then look in the caller of B, etc.
If the main program is reached in this search down the dynamic chain and no exception handler is found there, terminate the program.
This means that exception handlers use dynamic scoping.
In implementation, an exception is like a dynamically scoped, non-local goto:
Execution of the subprogram in which the exception occurred and of the body of the calling subprogram is aborted.
If the dynamic chain must be scanned to find the appropriate handler, the activation record of any subprogram or block that doesn't define a handler is deleted.
Thus, exceptions violate both the Structure Principle and the Regularity Principle.
A tasking facility allows a program to do more than one thing at a time.
E.g., a word processor that allows the user to edit one file while printing another:
procedure Word_Processor is
task Edit;
end Edit;
task body Edit is
begin
-- edit some file
end Edit;
task Print;
end Print;
task body Print is
begin
-- print some file
end Print;
begin
-- initiate tasks and wait for their completion
end Word_Processor;
In general, there are three common means of synchronizing communication between concurrent processes:
Ada uses rendezvous:
Task entry declarations are like mailboxes or message ports.
E.g.,
task Retrieve;
entry Seek ( K : Key );
. . .
end Retrieve;
One task sends a message to a mailbox in the other task, using syntax that
looks like a procedure call with parameters.
E.g.
task body Summary is
. . .
Seek ( ID );
. . .
end Summary;
The receiving task accepts the message from the named mailbox, and
processes it.
E.g.,
task body Retrieve is
. . .
accept Seek ( K : Key ) do
Save_Key := K;
end Seek;
. . .
end Retrieve;
If one of the tasks is ready and the other is not, then the ready task must wait to rendezvous with the other task. However, once the rendezvous has occurred (i.e., the callee has accepted the parameters), both tasks continue executing concurrently (unlike a procedure call,
E.g., a system which retrieves records to produce a summary, such that records may be retrieved concurrently with the production of the summary:

procedure DB_System is
task Summary;
end Summary;
task body Summary is
begin
. . .
Seek ( ID );
. . .
Fetch ( New_Record );
. . .
end Summary;
task Retrieve;
entry Seek ( K : Key );
entry Fetch ( out P : Packet );
end Retrieve;
task body Retrieve is
begin
loop
accept Seek ( K : Key ) do
Save_Key := K;
end Seek;
-- seek packet record and put it in New_Packet
. . .
accept Fetch ( out P : Packet ) do
P := New_Packet;
end Fetch;
end loop;
end Retrieve;
begin
-- initiate tasks and wait for their completion
end DB_System;
Note that the Fetch message is sent from the task Summary to the task Retrieve, even though the data transfer of the record retrieved from the database is from Retrieve to Summary, because the mode of the Fetch message parameter is out -- when it receives a Fetch message, Retrieve copies the record it retrieved from the database into the out-mode parameter provided by Summary.
| Subprogram | Task |
|---|---|
| Parameters transmitted from caller to callee. | same |
| Caller is suspended and callee is activated. | Caller continues executing concurrently with callee. |
| When callee is deactivated, caller is reactivated. | Caller may not terminate until all local tasks finish. |
Ada provides a special synchronization control structure to allow a task to wait for any of
several rendezvous.
E.g.,
select
accept Send ( D : Document )
do
-- print the document
. . .
end Send;
or accept Terminate
do
exit
end Terminate;
end select;
A deadlock can occur if one task is waiting for a rendezvous which never takes place. One way to avoid deadlocks is to use guarded entries so that a message is accepted only if a condition is satisfied:
select
when Avail < Size accept Send ( D : in Document )
do
-- add document to buffer
Avail := Avail + 1;
end Send;
or when Avail > 0 accept Receive ( D : out Document )
do
-- return a document from the buffer
Avail := Avail - 1;
end Receive;
or accept Terminate
do
exit
end Terminate;
end select;
Copyright © 2000, 2001 Jonathan Mohr