SUBPROGRAMS
LET'S LOOK AT A PROCEDURE
Example program ------> e_c08_p1.ada
Ada was designed to be very modular, and we come to the point where we will study the first and simplest form of modularization, the procedure. If you examine the program named e_c08_p1.ada, you will have your first example of a procedure. Some languages call this kind of subprogram a subroutine, others a function, but Ada calls it a procedure.
THE PROCEDURE IS LIKE THE MAIN PROGRAM
Beginning with the executable part of the main program, we have two executable statements in lines 14 and 15, each calling a procedure to write a line, if the name has any meaning. As always, the two statements are executed in succession, and each statement is a procedure call to which we would like to transfer control. The procedure itself is defined in lines 7 through 11, and a close inspection will reveal that the structure of the procedure is very similar to the structure for the main program. It is in fact, identical to the main program, and just as we begin executing the main program by mentioning its name, we begin executing the procedure by mentioning its name. The procedure has a declarative part, which is empty in this case, and an executable part which says to display a line of text and return the cursor to the beginning of the next line.
When execution reaches the end statement of the procedure, the procedure is complete and control returns to the next successive statement in the calling program.
Everything we have said about the main program, which is actually a procedure, is true for the procedure. Thus we can define new types, declare variables and constants, and even define additional procedures in the declarative part of this procedure. Likewise, we can call other procedures, and do assignments and compares in the executable part of the procedure.
ORDER OF DECLARATIONS
The original Ada specification, Ada 83, required that all procedures must come after all type, constant, and variable declarations. This was to force you to arrange your program such that all of the smaller declarations must come first, followed by the larger ones, so that the smaller declarations would not get lost by being buried between the larger declarations. This has been removed from the requirement for Ada 95, and you are permitted to list the various entities in any order you desire.
The embedded procedure has all of the flexibility and requirements as those defined for the main procedure. We will give further examples of each of these points in the next few example programs. At this time, compile and run this program, and make sure you understand its operation completely.
THREE PROCEDURES
Example program ------> e_c08_p2.ada
Examine the program named e_c08_p2.ada, and you will see three procedures in the declarative part of this program. Jumping ahead to the main program, beginning in line 30, you will see that the program will write a header, write and increment something 7 times, then write an ending statement. Notice how the use of descriptive procedure names resulted in our understanding of what the program will do without looking at the procedures themselves. The names are long, and a bit tedious to type in, but since Ada was designed to be a language that would be written once and read many times, the extra time will pay off when the source code is studied by several persons in the future.
WHAT IS A GLOBAL VARIABLE?
The variable named Counter, defined in line 7, is a global variable because it is defined prior to any procedure. It is therefore available for use by any of these procedures and it is, in fact, used by two of them. In line 11, the variable Counter is assigned the value of 1 when the procedure named Write_A_Header is called. Each time Write_And_Increment is called, the current value of Counter is written to the display, along with a message, and the value is incremented. Note carefully that the procedures are not executed in the order they are because of their relative order in the declaration part of the program, but because the main program calls them in that order. The program would work exactly the same way if you moved the first procedure, in its entirety of course, after the procedure Write_An_Ending_Statement. The order of execution is controlled by the order of calls, not the physical order of the procedures.
WHY DO PROCEDURES GO IN THE DECLARATIVE PART?
The declarative part of the program is where we define entities for use within the executable part. The procedures are actually definitions of how to do something, so they are right where they belong. Compile and run this program and be sure you understand the output.
A PROCEDURE WITH PARAMETERS
Example program ------> e_c08_p3.ada
Examine the file named e_c08_p3.ada and you will find a procedure that requires some data to be supplied to it each time it is called. Three variables are defined as type INTEGER and used in the procedure call in line 25. We will ignore the strange looking constructs in lines 9 through 12 for a couple of paragraphs. The procedure header, beginning in line 15, states that it is expecting three parameters, and each must be of type INTEGER, so we are compatible so far. When the procedure is called, the value of the first variable, which is named Dogs in this case, is taken to the procedure, where the procedure prefers to refer to it by the name Variety1. In like manner, the value of Cats is given to the procedure, and is called Variety2. The variable named Animals is referred to by the name Total in the procedure. The procedure is now ready to do some meaningful work with these variables, but is somewhat limited in what it can do to them because of the mode field of the procedure header.
THE MODE OF A PARAMETER
The formal parameter, as it is called in the procedure header, named Variety1, is of mode in which means that it is an input parameter, and therefore cannot be changed within the procedure. Variety1, along with Variety2 for the same reason, is a constant within the procedure, and any attempt to assign a value to it will result in a compile error. The formal parameter named Total, however, is of mode out and can therefore have a new value assigned to it within the procedure. Ada 83 defined that the out mode variable could not be read, but Ada 95 has relaxed this requirement and permits you to read the out mode variable within the procedure. Extra care must be taken that you do not attempt to read the value of an out mode parameter that has not yet been initialized to some meaningful value. Parameters that are of the modes in or in out do not have this problem since they are initialized, by definition, when the procedure is called.
If a parameter is defined as being of mode in out, it can be both read from and written to. If no mode is given, the system will use mode in as a default.
PARAMETER MODE SELECTION
All variables could be defined as mode in out and there would be no problems, since there would be maximum flexibility, or so it would seem. There is another rule that must be considered, and that rule says that every parameter that is of mode out or in out must be called with a variable as the actual parameter in the calling program. This permits the value to be returned and assigned to the variable. A variable of mode in can use a constant or a variable for the actual parameter in the calling program. We have been using the New_Line procedure with a constant in it, such as New_Line(2), and if it had been defined with the formal parameter of mode in out, we would have had to define a variable, assign the value 2 to it, and use the variable in the call. This would have made the procedure a bit more difficult to use, and in fact, somewhat awkward. For this reason, the formal parameter in New_Line was defined using mode in. You should choose the mode of the formal parameters very carefully.
The three formal parameters are available for use in the procedure, once they are defined as illustrated, just as if they had been defined in the declarative portion of the procedure. They are not available to any other procedure or the main program, because they are defined locally for the procedure. When used however, their use must be consistent with the defined mode for each.
SOME GLOBAL VARIABLES
The three variables declared in line 7 can be referred to in the procedure as well as in the main program. Because it is possible to refer to them in the procedure, they can be changed directly within the procedure. The variable Animals can also be modified when control returns to the main program because it is declared as out mode in the procedure header. This possibility can lead to some rather unusual results. You should spend some time thinking about what this really means.
THE PROCEDURE SPECIFICATION
Lines 10, 11, and 12, give an example of a procedure specification. This is an incomplete procedure declaration that you will find useful when you begin writing larger programs. The procedure specification can be included for any procedure, if desired, and it describes the external interface to the procedure without declaring what the procedure actually does. The Pascal programmer will recognize this as being very similar to the forward declaration. More will be said about this topic later. Note that the procedure specification is not required in this case, it is only included as an illustration.
Compile and run this program after you understand the simple addition and assignment that is done for purposes of illustration.
PROCEDURES CALLING OTHER PROCEDURES
Example program ------> e_c08_p4.ada
The example program e_c08_p4.ada contains examples of a procedure calling another procedure, which is perfectly legal if the called procedure is within the scope of the calling procedure. Much more will be said about scope later in this tutorial. The only rule that will be mentioned here is that the procedure must be defined prior to a call to it. You should have no trouble understanding this program, and when you do, you should compile and execute it. Be sure you understand where each line in the output comes from and why it is listed in the order that it is.
HOW DO WE NEST PROCEDURES?
Example program ------> e_c08_p5.ada
Examine the program named e_c08_p5.ada for examples of nested procedures. We mentioned earlier that it was possible to embed a procedure within the declarative part of any other procedure. This is illustrated in lines 9 through 20 where the procedure Second_Layer is embedded within the procedure Triple. In addition, the procedure Second_Layer has the procedure Bottom_Layer embedded within its declarative part in lines 11 through 14. Such nesting can continue indefinitely, because there is no limit to the depth of nesting allowed in Ada.
VISIBILITY OF PROCEDURES
There is a limit on visibility of procedures. Any procedure is visible, and can therefore be called, if it is within the top level of the declarative part of the calling procedure. Any procedure is also visible if it is prior to, on the same level, and within the same declarative part as the calling point. Finally, any procedure can be called if it is prior to, on the same level, and within the same declarative part as any subprogram within which the calling point is nested. In simpler words, a procedure is visible in three cases. First, if it is within the declarative part of the calling procedure. The second case is if it is a peer (on the same level within a parent subprogram) or thirdly, if it is a peer of any parent.
The procedure named Triple can therefore call Second_Layer, but not Bottom_Layer, since it is at a lower level and is not visible. The main program, according to these rules, is only allowed to call Triple, because the other two procedures are nested too deeply for a direct call. Be sure to compile and run this program and study the results.
ADA FUNCTIONS
Example program ------> e_c08_p6.ada
The program named e_c08_p6.ada has two examples of Ada functions. A function differs from a procedure in only two ways. A function returns a single value which is used in the place of its call, and all formal parameters of a function must be of type in, with no other mode permitted. In the program under consideration, two functions are illustrated, one beginning in line 13, and the other beginning in line 18. Note that each begins with the reserved word function.
A FUNCTION SPECIFICATION
In a manner similar to that defined for a procedure we can define a function specification that gives the interface of the function to any potential caller. You will find the function specification useful later in your Ada programming efforts. It is similar to the Pascal forward declaration. Note once again, that the function specification is not required in this case, it is only given here as an illustration.
The function named Square requires one argument which it prefers to call Val, and which must be of type INTEGER. It returns a value to the main program which will be of type INTEGER because that is the type given between the reserved words return and is in the function header in line 13.
A function must return a value, and the value is returned by following the reserved word return with the value to be returned. This return must be done in the executable part of the program, as illustrated in line 15. It is an error to fail to execute a return statement and fall through the end of a function. Such a runtime error will be reported by raising the exception Program_Error, which will be explained later in this tutorial.
CAN YOU RETURN FROM A PROCEDURE?
It would be well to point out that you can return from a procedure by using the return statement also, but no value can be given since a procedure does not return a value in the same manner as a function. The return statement can be anyplace in the procedure or function and there can be multiple returns if the logic dictates the possibility of returning from several different places.
A VALUE IS SUBSTITUTED FOR THE FUNCTION CALL
Examining the executable part of the program, we find that the variable Twelve has been initialized to the value of 12, and is used in line 25 as the argument for the function Square. This causes Square to be called, where the value of 12 is squared and the result is returned as 144. It is as if the resulting value of 144 replaces the function call Square(Twelve) in the Put procedure call, and the value of 144 is displayed. Continuing on to line 27, the variable Twelve, which still contains the value of 12, and the constant 12, are given to the function Sum_Of_Numbers which returns the sum of 24. This value is assigned to the variable named Sum where it is stored for use in line 29.
A function can be defined with no input parameters, in which case, the function is called with no parameters. Such a case would be useful for a random number generator where the call could be X := Random; assuming a new random number is returned each time the function is called. Compile and execute this program and study the output generated.
A FULLER EXAMPLE
Example program ------> e_c08_p7.ada
Examine the program named e_c08_p7.ada which is a rather odd program that computes the square of an integer type variable, but maintains the sign of the variable. In this program, the odd square of 3 is 9, and the odd square of -3 is -9. Its real purpose is to illustrate several procedures and a function interacting.
The main program named OddSqre has a function and a procedure nested within its declarative part, both of which have parameters passed. The nested procedure named Square_And_Keep_Sign has another procedure nested within its declarative part, named Do_A_Negative_Number which calls the function declared at the next higher level.
This program is a terrible example of how to solve the problem at hand but is an excellent example of several interacting subprograms, and it would be profitable for you to spend enough time with it to thoroughly understand what it does.
COMMENTS ON e_c08_p7.ada
This program illustrates some of the options that are purely programming taste. The first option is illustrated in line 21, where we could have chosen to use the construct Number_To_Square**2 instead of the simple multiplication. Either form is correct and the one to be used should reflect the nature of the problem at hand. The second option is the fact that three returns were included in lines 36, 39, and 42, when a single return could have been used following the end of the if statement. This was done to illustrate multiple returns in use. In some cases, the logic of the program is much clearer to use several returns instead of only one. More than anything else, it is a matter of personal taste. Be sure to compile and execute this program.
OVERLOADING
Example program ------> e_c08_p8.ada
We have casually mentioned overloading earlier in this tutorial and it is now time to get a good example of what overloading is by examining the program named e_c08_p8.ada. This program includes two functions and two procedures and all four of these subprograms have the same name. The Ada system has the ability to discern which subprogram you wish to use by the types included in the actual parameter list and the type of the return. In line 45, we make a call to a function with a 2, which is an integer type constant, and we assign the returned value to an INTEGER type variable. The system will look for a function named Raise_To_Power with a single integer class formal parameter and an INTEGER type return which it finds in lines 11 through 15, so it executes this function. The actual searching will be done at compile time so the efficiency is not degraded in any way by the overloaded names.
If you continue studying this program you will see how the system can find the correct subprogram by comparing types used as formal parameters, and the type returned. Using the same name for several uses is referred to as overloading the subprogram names and is an important concept in the Ada language.
OVERLOADING CAN CAUSE YOU PROBLEMS
If we made an error in this example program, by inadvertently omitting the decimal point in line 47, and assigning the result to an INTEGER type variable, the system would simply use the wrong function and generate invalid data for us. An even worse problem could be found if we had a function that used an INTEGER for input and a FLOAT for output, because only one small error could cause erroneous results. Because of this, it would be to your advantage to use different subprogram names for different operations, unless using the same name results in clear code.
In the case of the text output procedures which we have been using, it makes a lot of sense to overload the output subprograms to avoid confusion. The name Put is used for outputting strings, integers, enumerated types, etc, and we are not confused. Overloading can be an advantage in certain cases but should not be abused just because it is available. Be sure to compile and execute this program.
HOW ARE PARAMETERS PASSED TO A SUBPROGRAM?
If you understand the method of passing parameters to and from a subprogram, it may occasionally be possible to improve the efficiency by selecting the type carefully. For that reason the following, admittedly sketchy, descriptions are given;
PROGRAMMING EXERCISES
Return to the Table of Contents