Sub programs, covering both procedures and functions, are the basis of all programs in Ada. Ada provides features which will be new to Pascal and C programmers. Overloading, named parameters, default parameter values, new parameter modes and return values of any type all make Ada sub programs significantly different.
Procedures in Ada are similar to those in Pascal. A procedure can contain return statements.
procedure Demo(x:integer; y:float) is declarations; begin statements; end demo;
Procedures are called in the normal Pascal style:
Functions are very similar to procedures except that they also return a value to the calling sub program. The use of return statements is very similar to C. Functions can have as many return statements as required. A function returning a variable of a given type can be used anywhere a variable of that type can be used.
function Even( Number : Integer) return boolean is begin if Number mod 2 = 0 then return true; else return false; end if; end;
Ada has been designed with separate compilation very much in mind. To this end we can produce just a specification of a subprogram and submit this to the compiler. Once compiled and the description stored in an attribute file, it can be checked for compatibility with other procedures (and packages) when they are compiled. By producing a large number of procedure stubs we can pre test the design of a system and pick up any design errors before any more work is done.
A procedure specification for the above procedure would be:
procedure demo( x:integer; y:float);
If we wish to use a seperately compiled subprogram we can with it in another subprogram.
with demo; procedure use_demo is .... begin ... demo(4,5); end use_demo;
A function specification may appear as:
function Even(Number : Integer) return Boolean;
Like Pascal and unlike C, a subprogram can contain nested subprograms within them that are not visible outside the subprogram.
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure Display_Even_Numbers is
function even (number:integer) return boolean is
return number mod 2 = 0;
for i in 1..10 loop
if even(i) then
Ada provides three parameter modes for procedures and functions.
o in out
These modes do not correspond directly to any modes in other languages, they will be discussed below. The following should be noted.
o All parameters to subprograms are by default in.
The parameter passing mechanism is by copy-in, copy-out for in/out scalars. The language specifies that any other types can be passed by copy-in/copy-out, or by reference.
Ada95 mandates that limited private types (see later) are passed by reference, to avoid problems with the breaking of privacy.
Parameters supplied with this mode are like value parameters in Pascal, and normal parameters in C with the exception that they cannot be assigned a value inside the subprogram. The formal parameter (that in the sub program) is a constant and permits only reading of the value of the associated actual parameter.
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO; procedure Demo(x : in integer; y : in integer) is begin x := 5; -- illegal, in parameters are read only. put(y); get(y); -- also illegal end demo;
This mode corresponds directly to the var parameters of Pascal. Actual parameters can be used on either the left or the right hand side of statements in procedures. These parameters are effectively read/write.
procedure Demo( x : in out integer;
y : in integer) is
x := z*y; -- this is ok!
The formal parameter is a variable and may be assigned values, however it's initial value is not necessarily defined, and should not be relied upon.
procedure demo( x:out integer; y:in integer) is
z : integer := x; -- not wise, X not initialised begin x := y; end demo;
Caution - out mode parameters that aren't initialized!
procedure Location( target : in key; position : out Small_Integer_Range; found : out boolean) is
Here if the target is not found, then if position is not assigned a value, the copy out parameter mode causes the unitialised value to be placed into the awaiting actual parameter, the associated range check may cause a constraint error.
declare The_Position : Small_Integer_Range; begin Location( Some_Key, The_Position, Result); ...
If result = false, a constraint error may be generated when the procedure returns.
Normally the association between the formal (defined in the sub program specification) and actual parameters (supplied in the sub program call) is on a one to one basis i.e. it is positional. The first formal parameter is associated with the first actual parameter, etc.
To enhance the readability of sub program calls (Ada is designed to be readable) we can associate the name of the formal parameter and the actual parameter. This feature makes sub program calls immensely more readable.
procedure demo(x:integer; y:integer); -- procedure specification
demo(x=>5, y=> 3*45); -- when calling, associate formal
-- and actual parameters.
Lining up the parameters vertically can also be an aid to readability.
demo( x => 5, y => 3*45);
Because the association is made explicit (instead of the implicit association with positional parameters) there is no need to supply them in the same order as in the sub program specification. There is no restriction on the order in which named parameters are written.
demo( y => 3*45, x => 5); -- the order of named parameters -- is irrelavent
Positional parameters and named parameters can be mixed with the one
positional parameters must precede the named parameters.
procedure square(result : out integer;
number :in integer) is
could be called in the following manners:
square(x,4); square(x,number => 4); square(result => x,number => 4); square(number => 4, result => x); square(number => 4, x) -- illegal as positional follows named.
A default value can be be given for any in parameters in the procedure specification. The expression syntax is the same as that for pre initialised variables and is:
with Ada.Text_IO; use Ada.Text_IO; procedure print_lines(no_of_lines: integer:=1) is begin for count in 1 . . no_of_lines loop new_line; end loop; end print_lines;
This assigns a value for no_of_lines if the procedure is called without a corresponding parameter (either positional or named).
E.g. the procedure could be called as
print_lines; -- this prints 1 line. print_lines(6); -- overrides the default value of 1.
Similarly if a procedure write_lines was defined as
with Ada.Text_IO; use Ada.Text_IO; procedure write_lines(letter :in char:='*'; no_of_lines:in integer:=1) is begin for i in 1 . . no_of_lines loop for i in 1 . . 80 loop put(letter); end loop; new_line; end loop; end write_lines;
then it could be called as
write_lines; -- default character, default -- no. of lines. write_lines('-'); -- default no. of lines. write_lines(no_of_lines => 5); -- default character write_lines('-',5) -- specifying both.
So far the subprograms presented have all been independent compilation units. It is possible to embed subprograms in another subprogram such that only one compilation unit is constructed. These local subprograms can only be referenced from the surrounding subprogram. This is identical to the features provided by Pascal.
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure ive_got_a_procedure is
procedure display_values(number:integer) is
In this example the scope of procedure display_values is limited to inside the procedure ive_got_a_procedure - it can't be 'seen' or called from anywhere else.
In the previous example if any change is made to any of the code then both procedures must be resubmitted to the compiler (because they are in the one source file). We can separate the two components into seperate files while still retaining the limited scope of procedure display_values. This is a little like the #include directive in C, but the files are now independent compilation units.
In the first file...
with Ada.Text_IO; use Ada.Text_IO; with Ada.Integer_Text_IO; use Ada.Integer_Text_IO; procedure Ive_got_a_procedure is x :integer:=6; y :integer:=5; procedure display_values(number:integer) is separate; begin display_values(x); display_values(y); end ive_got_a_procedure;
In the second file...
separate(Ive_got_a_procedure) -- note no trailing semicolon procedure display_values(number:integer) is begin put(number); new_line; end display_values;
Apart from being in another file (and being a seperate compilation unit) the code is identical in all respects to the previous version. However if the inner subprograms change then only they have to be submitted to the compiler. This also allows a program to be broken up into several pieces, which can ease the job of understanding it.
Finding new names for functions that do the same thing to variables of different types is always a problem. The procedure insert is a good example. To give programmers a bit more breathing space, Ada allows sub programs to have the same name, it only insists that they are distinguishable. This is called overloading.
Two sub programs with the same name are distinguishable if their profile is different. The profile consists of the number of parameters, thier type and, if it is a function, the return type.
So long as the compiler can tell which sub program you requested by matching the profile of the call to the specifications of the sub programs you have supplied, it's happy; otherwise you'll get an ambiguous reference error.
procedure Insert(Item : Integer); -- Two procedures with the
procedure Insert(Item : Float); -- same name, but different.
The procedures Put and Get in the package Ada.Text_IO are examples of overloaded subprograms.
In languages such as Pascal the + operator is overloaded. Sometimes it is used to add integers, sometimes reals, sometimes strings. It is quite obvious that this one operator is used to represent very different code.
Ada allows programmers to overload operators with thier own code. One restriction on this overloading of the operator name, is as expected, that the functions are distinguishable from the originals supplied, i.e. its profile is unique. Even this problem can be overcome by specifying the package name (see the next section).
Overloaded operators cannot be seperate compilation units. - they must be contained in another unit such as a procedure, function or package.
Consider an example where we wish to provide facilities to add two vectors together.
procedure add_demo is type Vector is array (positive range <>) of Integer; a : Vector(1..5); b : Vector(1..5); c : Vector(1..5); function "+"(left,right:vector) return vector is
result : Vector(left'first..left'last); offset : constant Natural := right'first-1; begin if left'length /= right'length then raise program_error; -- an exception, -- see later end if; for i in left'range loop result(i):=left(i) + right(i - offset); end loop; return result; end "+"; begin a:=(1,2,3,4,5); b:=(1,2,3,4,5); c:= a + b; end add_demo;
This example uses most features discussed in the last few chapters.