Please send all questions & assignments to:
dsolarek@eng.utoledo.edu

 

Ada
Flow Control & Subprograms

Flow Control: Branching & Looping

An important part of any programming language is the ability to modify the normal top-to-bottom order of statement execution. In Ada (just as in other procedural languages), this is accomplished with decision structures (e.g., conditional statements) and loop structures. These structures provide the programmer with the ability to have the computer follow diverse paths through the code and make the computer a truly useful tool.

The  if Statement

Ada if statements determine if some Boolean condition is true or false, and then execute some sequence of statements depending on that determination. The following example program illustrates the basic syntax for the Ada if..then..end if statement, also known as the "guarded if" form.
 with Ada.Text_IO;
 use Ada.Text_IO;
 procedure IfTest is
   Answer : Character;
 begin
   Put("Is it morning (m) or afternoon (a)?");
   Get(Answer);
   if Answer = 'm' then
     Put_Line("Good morning!");
   end if;
 end IfTest;
A slight modification of the above program illustrates the use of an "else clause" as part of the if statement. The following example program shows the basic syntax for the Ada if..then..else..end if statement.
 with Ada.Text_IO;   use Ada.Text_IO; 
 procedure Greetings is
   Answer : Character;
 begin
   Put ("Is it morning (m) or afternoon (a)? ");
   Get (Answer);

   if Answer = 'm' then
     Put_Line ("Good morning!");
   else
     Put_Line ("Good afternoon!");
   end if;

 end Greetings;
In Ada, as with other algorithmic languages, if the Boolean "condition" evaluates to true the then part is executed. In this example, the Boolean condition is Answer = 'm'. If the user has typed a lowercase 'm' the condition is true, any other response makes the condition evaluate to false.

When there is a need to select among more than two alternatives, the if..then..elsif..then..end if version of the if statement can be used. As above, if the "condition" is true the then part is executed. Otherwise, the elsif clauses (if any) are checked in first-to-last order, again looking for a true condition. Finally, if none of the conditions are true, the else clause is executed (if there is an else clause). The example below is an improvement on the previous program. It uses the if statement to account for both upper and lowercase responses from the user. It also illustrates the if..then..elsif..then..end if form of the if statement.

 with Ada.Text_IO;   use Ada.Text_IO; 
 procedure Greetings is
   Answer : Character;
 begin
   Put ("Is it morning (m) or afternoon (a)? ");
   Get (Answer);

   if Answer = 'M' then
     Answer := 'm';
   elsif Answer = 'A' then
     Answer := 'a';
   end if;

   if Answer = 'm' then
     Put_Line ("Good morning!");
   elsif Answer = 'a' then
     Put_Line ("Good afternoon!");
   else
     Put_Line ("Please type 'm' or 'a'!");
   end if;

 end Greetings;
The following version of the Greetings program uses the Boolean connective or to account for the case of the user's response.
 with Ada.Text_IO;   use Ada.Text_IO; 
 procedure Greetings is
   Answer : Character;
 begin
   Put ("Is it morning (m) or afternoon (a)? ");
   Get (Answer);

   if Answer = 'm' or Answer = 'M' then
     Put_Line ("Good morning!");
   elsif Answer = 'a' or Answer = 'A' then
     Put_Line ("Good afternoon!");
   else
     Put_Line ("Please type 'm' or 'a'!");
   end if;

 end Greetings;
This program could handle incorrect user responses (e.g., something other than an a or m) better then it now does. After we have discussed the looping statements available in Ada, we will revise this program again.

Nested  if statements

Ada if statements can be nested to any level. The program below illustrates three levels of nesting.
 with Ada.Text_IO, Ada.Integer_Text_IO;
 use Ada.Text_IO, Ada.Integer_Text_IO;
 procedure IfDemo is
   Index, New_Index : Integer := 1;
 begin
  <<Again>> null;
  if Index <= 11 then
    Put("Index is");
    Put(Index, 3);
    if Index < 10 then
     if Index > 8 then
      Put(" and is less than 10 ");
      Put(" and greater than 8.");
     else
      Put(" and is less than or equal to 8.");
     end if;
    else
     Put(" and is 10 or greater.");
     for New_Index in 222..224 loop
      Put(" stutter");
     end loop;
    end if;
    New_Line;
  Index := Index + 1;
  Goto Again;
  end if;
 end IfDemo;
Whenever you find yourself nesting if statements to multiple levels, you might consider using an alternative structure (e.g., the case statement).

The previous program also illustrates the use of the Ada goto statement. It is used in the above example to avoid using the looping statements introduced later in this document ... and not because it is the recommended approach.

case statement

Sometimes you want to execute one of many different sequences of statements, based on the value of a single expression. As shown above, you can do this by using a set of elsif clauses, but most languages (including Ada) provide a simpler structure.

Ada's mechanism is the case statement. A case statement computes the value of an expression, and then compares this value to lists of values to determine what to execute next. The program below provides an example of the case statement:

 with Ada.Text_IO;   use Ada.Text_IO; 
 procedure Greetings is
  Answer : Character;
 begin
  Put ("Is it morning (m) or afternoon (a)? ");
  Get (Answer);

  case Answer is
    when 'M' | 'm' =>
     Put_Line ("Good morning!");
    when 'A' | 'a' =>
     Put_Line ("Good afternoon!");
    when others    =>
     Put_Line ("Please type 'm' or 'a'!");
  end case;

 end Greetings;
Note that after each when is a list of one or more possible values, separated by the | character. The when others clause matches anything that has not matched anything else. A case statement chooses one and only one alternative; the alternatives must be exhaustive (cover all possible cases) and exclusive (you cannot have two when 5 => clauses). An Ada compiler will detect missing or duplicate cases at compile-time.

Here is an example of the case statement that uses integer input and a range of values for each case.

 with Ada.Text_IO, Ada.Integer_Text_IO;
 use Ada.Text_IO, Ada.Integer_Text_IO;
 procedure NewAge is
  Age : Integer;
 begin
  Put ("Type you age in years: ");
  Get (Age);
  case Age is
    when 1..19 =>
     Put_Line ("You are a youngster.");
    when 20..39 =>
     Put_Line ("In the prime of life.");
    when others    =>
     Put_Line ("At the peak!");
  end case;
 end NewAge;
And yet another example of the case statement, this one using both integer and character input.
 with Ada.Text_IO, Ada.Integer_Text_IO;
 use  Ada.Text_IO, Ada.Integer_Text_IO;
 procedure Calculator is
  First, Second : Integer;
  Operator      : Character;
 begin
  Put ("Enter an expression: ");
  Get (First);
  Get (Operator);
  Get (Second);

  case Operator is
    when '+' =>
     Put (First + Second, Width => 1);
    when '-' =>
     Put (First - Second, Width => 1);
    when '*' =>
     Put (First * Second, Width => 1);
    when '/' =>
     Put (First / Second, Width => 1);
    when others =>
     Put ("Invalid operator '");
     Put (Operator);
     Put ("'");
  end case;

  New_Line;
 end Calculator;
The Ada case statement is functionally identical to C's switch statement. However, in Ada there is no need for a break or exit statement as part of each case. Exiting the case statement is automatic once an individual case is recognized and executed.

Looping

For repeated execution of program statements, loops are used. If you are familiar with other programming languages you have probably used for-loops, while-loops, and until-loops. Pascal has several loop constructs, each of which is detailed below. Ada refers to looping constructs as "iteration" statements.

loop statement

Simple loops. Ada has a number of "looping structures" for situations where something must repeat over and over again. The simplest looping construct just repeats "forever." A simple loop begins with the phrase loop, has statements to repeat, and ends with end loop;.
 with Ada.Text_IO;   use Ada.Text_IO; 
 procedure Greetings is
  Answer : Character;
 begin
  loop
    Put ("Is it morning (m) or afternoon (a)? ");
    Get (Answer);
    if Answer = 'm' or Answer = 'M' then
     Put_Line ("Good morning!");
     exit;
    elsif Answer = 'a' or Answer = 'A' then
     Put_Line ("Good afternoon!");
     exit;
    else
     Put_Line ("You must type m or a!");
    end if;
  end loop;
 end Greetings;
A loop can be terminated through an exit or an exit when clause (similar to C's break statement). An exit causes an immediate exiting of the innermost loop. An exit when clause causes exiting of the innermost loop only if the associated condition is true.

The exit when clause is particularly useful in circumstances where you must do some action(s) before you can figure out if the loop has to stop or not. These are called "loop-and-a-half" constructs - you start with loop, perform calculations to determine if the loop should stop, use an exit when to exit on that condition, and then work on the result. A variation using the exit statement.

 with Ada.Text_IO;   use Ada.Text_IO; 
 procedure Greetings is
  Answer : Character;
 begin
  loop
   Put("Is it morning (m) or afternoon (a)? ");
   Get(Answer);
   exit when Answer = 'm' or Answer = 'M'
          or Answer = 'a' or Answer = 'A';
   Put_Line("You must type m or a!");
  end loop;
  if Answer = 'm' or Answer = 'M' then
   Put_Line("Good morning!");
  else
   Put_Line("Good afternoon!");
  end if;
 end Greetings;
Here is an example which puts together the if, case, and loop statements.
 with Ada.Text_IO, Ada.Integer_Text_IO;
 use  Ada.Text_IO, Ada.Integer_Text_IO;
 procedure Calculator is
  Result   : Integer;
  Operator : Character;
  Operand  : Integer;
 begin
  Put ("Enter an expression: ");
  Get (Result);
  loop
   loop
    Get (Operator);
    exit when Operator /= ' ';
   end loop;
   if Operator = '.'then
    Put (Result, Width => 1);
    exit;
   else
    Get (Operand);
    case Operator is
      when '+' =>
        Result := Result + Operand;
      when '-' =>
        Result := Result - Operand;
      when '*' =>
        Result := Result * Operand;
      when '/' =>
        Result := Result / Operand;
      when others =>
        Put ("Invalid operator '");
        Put (Operator);
        Put ("'");
        exit;
    end case;
   end if;
  end loop;
  New_Line;
 end Calculator;

exception handling

If an error occurs during runtime some action must be taken, or the program will fail. Ada provides the programmer with the ability to handle runtime errors and avoid program termination. When a runtime error occurs, control of the program can jump to a special branch that deals with the error. This branch is called an exception. The program below illustrates the syntax for implementing an exception branch. In this case, we are expecting the user of the program to enter an integer. If they enter any other type, it is treated as an exception.
 with Ada.Text_IO, Ada.Integer_Text_IO;
 use  Ada.Text_IO, Ada.Integer_Text_IO;
 procedure Get_Integer is
  X : Integer;
 begin
  loop
    begin
      Put ("Enter an integer: ");
      Get (X);
      exit;
    exception
      when Constraint_Error | Data_Error =>
        Put ("Error in input --");
        Put_Line (" please try again.");
        Skip_Line;
    end;
  end loop;

  Put ("The value entered was ");
  Put (X, Width=>1);
  New_Line;
 end Get_Integer;
Since the programmer has no control over the user, entering the wrong type of value in response to an input request is very common. Good programming technique always accounts for this possibility.

while-loops

The while loop is particularly easy. Write a normal loop block, as you saw in the previous section, and put in front of the block the keyword while and a Boolean condition. A while loop repeatedly executes the statements in the loop as long as the while condition is true.
 with Ada.Text_IO, Ada.Integer_Text_IO;
 use Ada.Text_IO, Ada.Integer_Text_IO;
 procedure LoopDemo is
   Count : INTEGER;
 begin
   Count := 1;
   while Count < 5 loop
     Put("Count =");
     Put(Count, 5); New_Line;
     Count := Count + 1;
   end loop;
 end LoopDemo;
Ada while loops check their conditions before executing each loop. That means that the loop can conceivably execute "zero" times if the loop condition starts as false.

for-loops

The for loop is similar to the while loop, a basic construct starting with the keyword for. A for loop assigns a local loop parameter a lower value. It then repeatedly checks if the loop parameter is less than the higher value, and if so it executes a sequence of statements and then adds one to the loop parameter.
 with Ada.Text_IO, Ada.Integer_Text_IO;
 use Ada.Text_IO, Ada.Integer_Text_IO;
 procedure LoopDemo is
   Index, Count : INTEGER;
 begin
   -- This is the for loop
   for Index in 1..4 loop
      Put("Doubled index =");
      Put(2 * Index, 5); New_Line;
   end loop;

   -- This is the reverse for loop
   for Count in reverse 5..8 loop
      Put("Triple count =");
      Put(3 * Count, 5); New_Line;
   end loop;

   -- An empty loop
   for Index in 7..11 loop
      null;
   end loop;
 end LoopDemo;
There are some key points about for loops that need mentioning:
  1. The variable named in the for loop is a local variable visible only in the statements inside it, and it cannot be modified by the statements inside (you can exit a for loop, using the exit statement we have already seen).
  2. Normally a loop always adds one to the index variable (also called control variable or loop value). The reverse order can be requested by using the keyword reverse after the keyword in. In this case the loop value starts with the upper bound (given second) and decrements until it is less than the lower bound (given first). If you need to increment or decrement by more than one, use another kind of loop instead.
As with the while loop, for loops check their conditions before executing each loop. That means that the loop can conceivably execute "zero" times if the loop condition starts as false.

Arrays

An array allows us to declare a whole set of variables of the same type in a single declaration. Furthermore, it allows us to manipulate all of the data as a single entity, and access each variable (called an array element or component) within the array individually by means of an index (also called a subscript). This means that the time taken and complexity of writing programs that use many variables of the same type is reduced. In some ways, arrays are to data structures what loops are to control structures.

An array type in Ada can contain many components with the same subtype. An Ada array is quite similar to arrays in many other languages, but here are some important things to know about them:

  1. Ada array indices (also called subscripts) are not required to start at zero or one. Array indices can begin (and end) with any discrete value - whatever makes the most sense for the data. This means that you can start arrays at -5 (if that makes sense for your application), and you can use enumerated values as indices as well. Ada programs usually use a starting index of 1 if there is no particularly natural starting point; this reduces the probability of so-called "one-off" errors (people normally count from one, not zero, and can sometimes get confused when starting from zero).
  2. Like many other things in Ada, array accesses (both read and write) are normally checked at runtime. Thus, if the array index is out-of-bounds, instead of quietly doing the wrong thing, an exception will be raised. This catches a surprisingly large number of errors.
  3. Multi-dimensional arrays are handled in an intuitive way.
  4. A directive can be used to pack arrays, which requests the compiler to store the array in a memory-efficient way. This is particularly handy for arrays of Boolean and Character values.
  5. Using a value from an array intentionally looks like a function call. That way, if you change an array into a function, code that uses the values often needs relatively few changes.
  6. You can define array types without completely fixing their minimum and maximum size. These are called "unconstrained" arrays.
The program below illustrates the use of arrays in Ada 95. As you can see there are two types of arrays, one with fixed bounds, and one with variable bounds. In the variable bound case, each object has fixed bounds, but you can have several objects of the same type, all with different bounds.
 with Ada.Text_IO, Ada.Integer_Text_IO;
 use Ada.Text_IO, Ada.Integer_Text_IO;
 procedure ArrayMain is
   Index : integer := 0;

   -- fixed bounds
   type Arr is array
     (Integer range 1..10) of Integer;

   -- variable bounds  
   type Aru is array
     (Integer range <>) of Integer;

   -- bounds are 1..10
   M : Arr;

   -- bounds must be given in this case
   N : Aru (1..10);

 begin
   M(3) := 4;
   N(2) := M(1) * 3;

   -- print out the array elements
   for Index in 1..10 loop
     Put(M(Index));
     New_Line;
   end loop;
   New_Line(2);
   for Index in 1..10 loop
     Put(N(Index));
     New_Line;
   end loop;

 end ArrayMain;
Compile an run the above program. Take note of the values printed for each of the array elements, especially those that were not initialized.

One-dimensional arrays

To define an array, we use the reserved words array and of with the appropriate modifiers (as illustrated in the example below). We define a range which the array will cover, the type of the range variable (which must be composed of discrete type limits) and the type of each element of the array. Remember that a discrete type is any type of the integer class including enumerated types.
 with Ada.Text_IO, Ada.Integer_Text_IO;
 use Ada.Text_IO, Ada.Integer_Text_IO;
 procedure Array1 is
  N : INTEGER := 10;
  Dummy1:array(INTEGER range 1..7) of BOOLEAN;
  Dummy2:array(INTEGER range -21..N) of BOOLEAN;
  Dummy3:array(-21..N) of BOOLEAN;
  type MY_ARRAY is array(1..5) of INTEGER;
  Total        : MY_ARRAY;
  First        : MY_ARRAY;
  Second       : MY_ARRAY;
  Funny        : array(1..5) of INTEGER;
  X,Y          : array(12..27) of INTEGER;
  Fourth_Value : INTEGER renames First(4);
 begin
  First(1) := 12;
  First(2) := 16;
  First(3) := First(2) - First(1);
  Fourth_Value := -13;
  First(5) := 16 - 2 * First(2);

  for Index in 1..5 loop
   Second(Index) := 3 * Index + 77;
  end loop;

  Total := First;
  if Total = First then
   Put("Both arrays are the same size and contain ");
   Put_Line("the same values in all elements.");
  end if;

  for Index in 1..5 loop
   Total(Index) := Total(Index) + Second(Index);
   Funny(Index) := Total(Index) + First(6 - Index);
   Put("The array values are");
   Put(Total(Index), 6);
   Put(Funny(Index), 6);
   New_Line;
  end loop;
 end Array1;
Begin your analysis of the above program by considering the declaration of the array named Dummy1. This line says that the variable named Dummy1 will have 7 elements numbered from 1 through 7, and each element will have the ability to store the value of one BOOLEAN variable. The individual elements of Dummy1 are referred to by the array name (variable name) followed by a subscript in parentheses, i.e., Dummy1(1), Dummy1(2), ... to Dummy1(7). Keep in mind that each array element is a single BOOLEAN variable.

The following example illustrates a common programming need, sorting the elements of an array. This program uses a technique known as "shuffle Sort."

 with Ada.Text_IO, Ada.Integer_Text_IO;
 use  Ada.Text_IO, Ada.Integer_Text_IO;
 procedure Sort is
   type Array_Type is array 
             (Positive range <>) of Integer;
   A : Array_Type (1..10);

   procedure Shuffle_Sort(X : in out Array_Type) is
     Position : Positive;
     Value    : Integer;
   begin
     for I in X'First+1 .. X'Last loop
       if X(I) < X(I-1) then
         -- Misplaced item found: copy it
         Value := X(I);
         -- Scan backwards until correct
         -- position is found
         for J in reverse X'First .. I-1 loop
           exit when X(J) < Value;
           Position := J;
         end loop;

         -- Move intervening values along and slot
         -- in saved copy of item
         X(Position+1 .. I) := X(Position .. I-1);
         X(Position) := Value;
       end if;
     end loop;
   end Shuffle_Sort;

 begin   -- main program
   Put_Line ("Enter 10 integers:");
   for I in A'Range loop
     Put (I, Width=>1);
     Put (": ");
     Get (A(I));
   end loop;
   Shuffle_Sort (A);
   Put ("Sorted result: ");
   for I in A'Range loop
     Put (A(I));
   end loop;
 end Sort;
Using the above program as a guide, create a flowchart or pseudo code specification for the Shuffle Sort algorithm. Your result should be language independent, meaning that it can be coded in Ada, Pascal, C or any other programming language.

Array Initialization & Operations

The followiing program illustrates several methods for creating, initializing, and working with both one- and two-dimensional arrays.
 with Ada.Text_IO, Ada.Integer_Text_IO;
 use Ada.Text_IO, Ada.Integer_Text_IO;
 procedure ArrayInit is
  Index, Index2 : INTEGER := 0;
  type MY_ARRAY is array(1..5) of INTEGER;
  Total : MY_ARRAY := (12, 27, -13, 122, 44);
  First : MY_ARRAY := (1=>14, 2=>57, 3=>111,
                            5=>-27, 4=>21);
  Another : MY_ARRAY := (2=>13, 5=>57, others=>3);
  One_More : MY_ARRAY := (2..4=>13, 1 | 5=>27);
  type MATRIX is array(INTEGER range 1..3,
                 INTEGER range 1..4) of INTEGER;
  Square_Board : MATRIX := ((4, 7, 3, 5),
                            (3, 8, 2, 0),
                            (1, 5, 9, 9));
  Checker_Board : MATRIX := (2=>(3, 8, 2, 0),
                             3=>(1, 5, 9, 9),
                             1=>(4, 7, 3, 5));
  Chess_Board : MATRIX := (2=>(6, 16, 4, 1),
                      3=>(8, 7, 6, 5),
         1=>(4=>187, 2=>11, 1=>7, 3=>19));
 begin
  Put("   Total = ");
  for Index in 1..5 loop
    Put(Total(Index), WIDTH=>6);
  end loop;
  New_Line;

  Put("   First = ");
  for Index in 1..5 loop
    Put(First(Index), width=>6);
  end loop;
  New_Line;

  Put(" Another = ");
  for Index in 1..5 loop
    Put(Another(Index), width=>6);
  end loop;
  New_Line;

  Put("One_More = ");
  for Index in 1..5 loop
    Put(One_More(Index), width=>6);
  end loop;
  New_Line;

  Put("Square_Board = ");
  New_Line;
  for Index in 1..3 loop
    for Index2 in 1..4 loop
      Put(Square_Board(Index,Index2), width=>5);
    end loop;
    New_Line;
  end loop;

  New_Line;
  Put("Checker_Board = ");
  New_Line;
  for Index in 1..3 loop
    for Index2 in 1..4 loop
      Put(Checker_Board(Index,Index2), width=>5);
    end loop;
    New_Line;
  end loop;
  New_Line;

  Put("Chess_Board = ");
  New_Line;
  for Index in 1..3 loop
    for Index2 in 1..4 loop
      Put(Chess_Board(Index,Index2), width=>5);
    end loop;
    New_Line;
  end loop;
  New_Line;

  if Square_Board = Checker_Board then
    Put_Line("Square and Checker are equal.");
  else
    Put_Line("Square and Checker are NOT equal.");
  end if;

  if Chess_Board = Square_Board then
    Put_Line("Chess and Square are equal.");
  else
    Put_Line("Chess and Square are NOT equal.");
  end if;
 end ArrayInit;

Characters & Strings

The type CHARACTER is a predefined type in Ada and is defined as the printable set of ASCII characters (including a few that do not actually print). See Annex A.1 of the Ada 95 Reference Manual (ARM) for a complete list of the CHARACTER elements. All of the operations available with the enumerated type variable are available with the CHARACTER type variable. To illustrate their use, we declare two CHARACTER type variables in the program below, with the second being initialized to the letter D. Note the single quote marks which define the CHARACTER type literal to which the variable named Another is initialized. A different literal value is assigned to the variable My_Char (the letter 'A', and the two variables are compared in the if statement. Since 'A' is of a lesser ASCII value than 'D', the line of text My_Char is less than Another. is output to the display.

Note:

  • A = 10000012 = 6510, and
  • D = 10001002 = 6810

Here is the example program:

 with Ada.Text_IO;
 use Ada.Text_IO;
 procedure Chars is
  My_Char : CHARACTER;
  Another : CHARACTER := 'D';
 begin
  My_Char := 'A';
  if My_Char < Another then
    Put_Line("My_Char is less than Another.");
  end if;
  Put(My_Char);
  Put(Another);
  Put(My_Char);
  Put_Line(" character output.");
  My_Char := CHARACTER'SUCC(My_Char);
  My_Char := CHARACTER'FIRST;
  My_Char := CHARACTER'LAST;
 end Chars;
The program below illustrates some of the operations that can be done with the predefined type STRING. A string is an array of CHARACTER type variables which is of a fixed length and starts with element number 1 or higher. The index uses type POSITIVE. Note that this program is called instead of the more desirable name of string.adb because the word STRING is a reserved word in Ada and using it for the program name would make it unavailable for its correct use.

The declaration of the identifier Line declares an uninitialized string of 33 characters. The declaration of the identifier JOB declares a constant string of four elements initialized to the word John, and illustrates rather graphically that the string is composed of individual CHARACTER type elements. The declaration for Address is similar in that it declares another STRING constant that is then initialized (which all constants must be in order to be useful) to the value Anywhere, USA.

 with Ada.Text_IO;
 use Ada.Text_IO;
 procedure String1 is
  Line    :STRING(1..33);
  NAME    :constant STRING :=('J','o','h','n');
  JOB     :constant STRING :="Computer Programmer";
  Address :STRING(1..13) :="Anywhere, USA";
  Letter  :CHARACTER;
  EXAMPLE1:constant STRING :="A";
  EXAMPLE2:constant STRING :="";
 begin
  Line := "This is a test of STRINGS in Ada.";
  Put(Line);
  New_Line;
  Put(NAME);
  Put(" is a ");
  Put(JOB);
  Put(" and lives in ");
  Put(Address);
  New_Line(2);
  Address(3) := 'X';
  Address(4) := 'Y';
  Address(10..13) := NAME(1..4);
  Put(Address);
  New_Line;
 end String1;
It is important to note the different uses for 'J' and "A" as they apply to STRING and CHARACTER values. Single quote for CHARACTER and double quote for STRING.

Subprograms

Ada has two types of subprograms; functions and procedures. This section illustrates the use of both subprogram types.

A subprogram body defines the actual algorithm used by the subprogram. A subprogram body starts out with a subprogram specification (which is the subprogram declaration without the final semicolon) followed by the keyword is. This is followed by a declaration of local variables, the keyword begin, the statements to be executed, and then the keyword end.

Functions

The purpose of a function is to calculate a value. This is the conventional mathematical meaning of a function. Small functions to access complex data structures are an essential feature of structured programming: Not only do they hide irrelevant parts of the data structure but they provide a cleaner interface to the outside world.

Here is a declaration of a function which takes as input two values and returns a result:

 function Average_Two(A, B : in Integer)
                            return Integer;
Note the keywords in and out; this indicates the mode of the parameter(s). There are three possible modes:
  1. in - the parameter's value may be used but not changed.
  2. out - the parameter's value may be changed but not used.
  3. in out - the parameter's value may be used and/or changed.
The default mode is in, but it is good practice to always state the desired mode explicitly.

Functions return a value using the return statement. Here is an example of a simple function:

 function Sum(A, B : in Integer) return Integer is
  Total : Integer := A;
 begin
  Total := Total + B;
  return Total;
 end Sum;
The example below shows a function that computes the sum of the squares of two Integers. It works by creating a local function (in the same file) called Square:
 function Sum_Squares (A,B:in Integer)
                          return Integer is

  function Square(X : in Integer)
                          return Integer is
  begin -- this is the beginning of Square
    return X*X;
  end Square;

 begin -- this is the beginning of Sum_Squares
  return Square(A) + Square(B);
 end Sum_Squares;
As a stand-alone function, Square might appear as follows:
 function Square(Arg : Integer) return Integer is
 begin
   return Arg * Arg;
 end Square;
This function takes an Integer argument, which can only be read (there is no way for a function to modify its arguments), and returns a single result. There can be one or more return statements in the body of the function, execution must terminate by running into one of these return statements.

This function could be edited into its own file, square.adb as shown below. Remember, you cannot run this function on its own, since it is not suitable for use as a main program.

To use this function, we have a main program looking like:

 with Square;
 procedure Compute is
   A : Integer := 3;    -- initializing a variable
   B : Integer;
 begin
   B := Square (A + 1); -- result is 16
 end Compute;
The with Square statement announces the intention of using this function in this procedure, and as shown we just use it in the natural way, with the argument in parentheses.

It can be quite a nuisance to have every function in a separate file, especially if we use separate specifications for each function. Luckily we can avoid this, because Ada 95 provides the concept of a package to deal with collectiing together (packaging) functions into a single file. Typically we bundle a set of related functions. In this case, Cube and Square are related, so let's bundle them up into a package called Powers.

For packages, it is absolutely required to have two files, one for the specification and one for the body. The specification will looks like the one illustrated below:

 package Powers is
   function Square (Arg : Integer) return Integer;
   function Cube   (Arg : Integer) return Integer;
 end Powers;
The specification goes in a file called powers.ads. The body, which goes in the file called powers.adb, looks like:
 package body Powers is
   
   function Square (Arg : Integer) return Integer is
   begin
     return Arg * Arg;
   end Square;
   
   function Cube (Arg : Integer) return Integer is
   begin
     return Arg * Square (Arg);
   end Cube;
 end Powers;
The package specification is just a series of declarations, including function specs. Later we will see that it typically can contain type declarations and even variable declarations that are logically related.

The package body has the bodies of the functions. These look exactly like the bodies when we compiled them separately, with one interesting exception. The with Square on the body of Cube is gone. That's because with statements belong only at the start of the unit (if Powers needed to with anything then the with statements would go before the keyword package). But more importantly, you do not need to with things that are in the same package as you are. A package is a family of declarations which can all see one another without any fiddling.

To use the package, we make a main program that includes a with statement for the package. For example:

 with Powers;
 procedure Withp is
    -- defining a constant
    A : constant Integer := 10;
    B : Integer;
 begin
    -- result is 1000
    B := Powers.Cube (A); end Withp;
When we call the Cube function, we use object-oriented notation and precede the name of the function with the package name, Powers and a period to indicate which package the Cube function lives in, otherwise using a function inside a package is the same as using it in the standalone case. In practice, nearly all functions live in packages, typically only the main program lives on its own as a separate file.

The requirement to put the package name Powers and a period can be a bit tedious. However, it does have its advantages. In a big program, it can make it easier to see where things come from. On the other hand it clutters up the text. As always in Ada 95, we favor the reader of the code over the writer, so we are not very sensitive to the argument that it is a nuisance to *write* the extra junk, but we might worry about the cluttering causing it to be harder to read. If we feel that way, we can use the use clause:

 with Powers; use Powers;
 procedure Usep is
    A : constant Integer := 10;
    B : Integer;
 begin
    B := Cube (A);
 end Usep;
and then we do not need the prefix. The use clause makes all the items declared in the package visible without the prefix.

To "use" or not to "use", that is the question! This is a controversial subject. Some Ada 95 programmers regard the use clause as being in the same category as a goto statement. Other Ada 95 programmers find them useful. It is up to you to decide, but try to adopt a consistent style.

One additional point to note. It is perfectly valid to have the same names declared in multiple packages. In this case, you generally have to use the prefix notation anyway, so that the compiler knows which instance of a name you are talking about.

Procedures

Ada was designed to be very modular, and we have come to the point where we will study the first and simplest form of modularization, the procedure. Some languages call this kind of subprogram a subroutine, others a function, but Ada calls it a procedure.

The main difference between a procedure and function is that a function returns a value, while a procedure does not (though a procedure can change the values of parameters sent to it).

Here is a simple subprogram body that implements the procedure Average we declared in the last section. Note that after the word end we can add a word indicating what we are ending (the Ada compiler will check to make sure this is correct). Also note that the assignment statement in Ada is written as := (the same as Pascal):

 procedure Average(A, B : in Integer;
                 Result : out Integer) is
 begin
  Result := (A + B) / 2;
 end Average;
Local variables and local subprograms can be declared between the is and the begin. Local variables and local subprograms exist as long as their enclosing subprogram exists. Local variables are useful as "scratchpads" to hold intermediate results. Local variables are written the same as parameters are: the variable name(s), a colon, and their type.

Functions take parameters, which are read only, and compute a single result. Procedures can take parameters also, and in addition, can specify parameters as being writable as well as readable. This allows a procedure to return multiple results. Here is an example:

 procedure Procp
 (A  :Integer;        -- default is in
  Ai :in  Integer;    -- actual can be expression
  Ao :out Integer;    -- actual must be variable
  Aio:in out Integer) -- actual must be variable
 is
 begin
  Ao :=A+Ai;   -- read in params, write out params
  Aio:=Ai+Aio; -- read or write in out parames
  Aio:=Aio+Ao; -- read out params after setting them
 end Procp;
This procedure could live in its own file as procp.adb, or could be placed inside a package. In either case, it would probably have a separate specification:
 procedure Procp
   (A   : Integer;
    Ai  : in  Integer;
    Ao  : out Integer;
    Aio : in out Integer);
 -- Fascinating comments explaining what Procp does
To call a procedure, we use the keyword with it, and then we can call it using the technique illustrated below:
 with Procp;
 procedure Pcall is
   A,B,C : Integer;
 begin
   Procp (3,6,A,B); 
   -- named parameters 
   Procp (Aio => B, Ao => A, A => 3, Ai => 6);
 end Pcall;
As shown here, procedures can be called using positional notation, in which we have to remember the order of the parameters, or the more useful named notation, in which we can put the parameters in any order. Named notation should almost always be used except in a very simple case. For example:
  V := Square(Q);
seems OK. Although we could write a named parameter version:
  V := Square(Arg=>Q);
that illustrates how to include the name of the parameter in the call. However, because the name chosen is not descriptive, even this does not help much. The following example shows a better approach:
 Integrate(Func=>Cosine, From=>0.12, 
              To=>0.15, Result=>Area);
is preferable to the more cryptic version below:
 Integrate(Func,0.12,0.15,Area);
Note how the careful choice of parameter names makes the procedure call read well so that you can better understand what it does without needing to look at the specification. Carefully designing your functions and procedures (collectively called subprograms in Ada 95) to work this way will ease the job of anyone who has to read your code (including you!).
    Click on the button at left to return to the calling page.

 

There have been visitors since 11/26/2003

Added to the Web: October 24, 1999.
Last updated: October 27, 1999.
Web page design by Dan Solarek.

http://cset.sp.utoledo.edu/cset4250/