OUR FIRST GENERIC PACKAGE
In the last chapter we studied the use of generic subprograms. This chapter will be devoted to the study of generic packages, and we will exhaust the topic because only procedures, functions, and packages can be used as generic units.
Example program ------> e_c31_p1.ada
Examine the file named e_c31_p1.ada for our first example of a generic package. This package is named EasyPkg, because it is so easy to understand, and is composed of one procedure and one function. The generic package begins with the reserved word generic, followed by two generic formal parameters, and the standard format for an Ada package specification in lines 5 through 9. The first generic formal parameter is a discrete type, and the second is a floating point type as we discussed in the last chapter.
The instantiating statements are given in lines 39 and 41 of the main program and are no different than those used in the last chapter except for the first word, the reserved word package, because we are declaring an instance of a package in this case. If we replace the formal parameter types with the types declared in each instantiation statement, we will have a normal package just as we studied previously. In the case of a package, we can add the use clause as we have in lines 40 and 42, eliminating the need for the extended naming notation, commonly called the dot notation. After declaring a few variables to work with, we are ready to exercise the two procedures and functions we have declared. It should be clear to you that we have a procedure and a function in the package named Funny_Stuff, and another procedure and function in the package named Usual_Stuff.
A fine point must be mentioned about the use clause when used with the generic package. It is not legal to use a use clause with the generic package itself. Every instantiation must explicitly give the extended name for the generic package. Of course this only occurs with nested generic packages which are the topic of the next example program. The use clause however, is legal for use with any and every instantiated copy of a generic package. The instantiated package's new name cannot be overloaded because it is not permitted to overload the name of any package.
USING THE INSTANTIATED PACKAGES
In line 53, we call the procedure named Trade_Values, but since there are two procedures with this name, Ada will pick the correct one by comparing the types. This is our old friend called overloading again. In line 54, because of the types used, the other procedure will be used as indicated in the comments. In lines 55 and 56, we explicitly tell the system which overloading to use, but it will still check the types to see if they are compatible. In line 57 we tell the system to use the wrong one which leads to a type mismatch and a compile error, and in line 58, we use two different types for the parameters, which is another type mismatch. (Lines 57 and 58 are commented out so that we will not actually get the error, but you should remove the comments to see that the compiler does report an error.)
Lines 60 through 64 give examples of proper usage of the two functions instantiated above and illustrate that the literals of type universal_real are compatible with both copies of the function as you would expect. The compiler uses the type of the assignment variable on the left hand side of the assignment statement to decide which overloading to use for each statement.
After you spend enough time to understand this program, compile and execute it even though it has no output.
NESTED GENERIC PACKAGES
Example program ------> e_c31_p2.ada
The example program named e_c31_p2.ada contains a generic package nested within another non-generic package. The outer package contains a single procedure, and the nested generic package contains a single formal generic parameter of a floating point type, and a single function. The procedure and function are the same as those in the last program, but they are organized differently here for illustration.
Note carefully that since the procedure is not in the generic part of the outer package, it is directly available in the same manner as if it were in a package without a generic part. The package name can be declared in a use clause which will make the non-generic portion of the package usable.
The executable part of the program is very simple and very similar to the last example program so it will be left to the student to study, compile, and execute this program.
OBJECTS AS GENERIC FORMAL PARAMETERS
Example program ------> e_c31_p3.ada
Examine the program named e_c31_p3.ada for an example of using objects for formal generic parameters instead of just types. In this case the number of ROWS and the number of COLUMNS will be part of the instantiation, and the resulting procedure and function will be ready to work with the desired size matrices. In addition, the constant named ROWS, and the constant named COLUMNS are initialized to the values given in lines 4 and 5. If a value is not given with the instantiation, these values will be used in much the same way that we use default values with a procedure or function.
In this case, the package body uses the standard Ada.Text_IO package and outputs a string to the monitor each time one of the subprograms is called. This is only done to illustrate to you that there is nothing magic about the package Ada.Text_IO, and that it can be used within another package.
USING THE OBJECTS
In line 58, the package is instantiated using the positional aggregate notation with values of 3 and 5 for the matrix size. Line 60 illustrates the use of the defaults declared in the generic part above, and line 61 illustrates the use of the named aggregate notation used during package instantiation.
AN EXPORTED TYPE CAN BE USED
Referring back to line 8, we have the type named LOCAL_MATRIX declared in the package specification and therefore available to any calling program. If you refer to the private part of the package specification, you will see that the type LOCAL_MATRIX is declared to be a function of the formal generic objects. After we instantiate a copy of the package, we have the exported type available for use in the calling program. We use the types exported types in lines 66 through 68 to declare a few variables, but even though two of the instantiations make use of the use clause, we must explicitly declare the desired type by using the extended naming notation. This is because the compiler has no way of knowing which exported type we are interested in using for each variable.
With the above descriptions of the new concepts in this program, you should be able to understand the details of it. Be sure to compile and execute this program.
A PROCEDURE AS A GENERIC PARAMETER
Example program ------> e_c31_p4.ada
Examine the program named e_c31_p4.ada for an example of a procedure being used as a generic formal parameter. The syntax uses the reserved word with to begin the formal parameter in line 4, and the complete header for the procedure is given with the list of formal parameters and their types. This procedure can then be called from within the body of the package and will refer to a procedure that is actually outside of the generic package. Now we must define the procedure that will be called in order to satisfy a call to this formal parameter. We will see where this procedure is defined shortly.
Refer to the main program where the procedure named R_Average is declared. It has exactly the same formal parameter structure as that declared in the generic formal parameter, so it can be used in the instantiating call in line 43. A call to the procedure in the generic instantiation actually results in a call back to the procedure in the calling program to do some calculations.
WHAT CAN THIS BE USED FOR?
This capability gives you the ability to write a generic package that can be used in several places but with a slight difference in each place, because each instantiation can use a different procedure for the reference back to the calling program. This is simply another one of the available entities that add to the flexibility of Ada. After you understand the logic here, you should compile and execute this program.
A FUNCTION AS A GENERIC PARAMETER
Example program ------> e_c31_p5.ada
Examine the program named e_c31_p5.ada for an example of using a function as a generic formal parameter. The logic is similar to that described in the last program except that a function is used for the reference back to the calling program. No further explanation will be given since it should not be needed. It should not come as much of a surprise to you that several procedures or functions, or a combination of them, can be used as generic formal parameters. Any of the isolated examples can be combined as needed to achieve the desired goal. Nesting of generic and normal packages can be done as needed. It will be up to you to decide how to modularize and package your program to best accomplish the stated goals. Ada gives you many options. Be sure to compile and execute this program.
Return to the Table of Contents