Ada's system of types and procedures requires that the type of a procedure's
actual parameter always match that of the formal parameter. This means that a
procedure or function that needs to do the same thing to values of two
different types must be written twice--once for each type. Consider the
procedure Exchange
:
PROCEDURE Exchange(Value1, Value2: IN OUT Natural) IS TempValue: Natural; BEGIN TempValue := Value1; Value1 := Value2; Value2 := TempValue; END Exchange;
A
procedure to exchange two Float
values would have the same
sequence of statements, but the type references would be different:
PROCEDURE Exchange(Value1, Value2: IN OUT Float) IS TempValue: Float; BEGIN TempValue := Value1; Value1 := Value2; Value2 := TempValue; END Exchange;Obviously, we could modify the first version to give the second version by using an editor. Because we are likely to need the
Natural
version again,
we modify a copy of it. This gives two versions of a procedure which are almost
the same; because of overloading, the two can both be called
Exchange
. Carrying this to its extreme, we could build up a large
library of Exchange
programs with our editor and be ready for any
eventuality. Exchange
could even be made to work with array or
record structures, because Ada allows assignment for any type.
There is a problem with this approach: It clutters our file system with a
large number of similar programs. Worse still, suppose that a bug turns up in
the statements for Exchange
or in another program with more
complexity. The bug will have turned up in one of the versions; the same
bug will probably be present in all of them, but we would probably forget to
fix all the others! This is, in miniature, a problem long faced by industry:
multiple versions of a program, all similar but not exactly alike, all
requiring debugging and other maintenance.
Returning to our simple example, it would be nice if we could create
one version of Exchange
, test it, then put it in the
library. When we needed a version to work with a particular type, we could just
tell the compiler to use our pretested Exchange
but to change the
type it accepts. The compiler would make the change automatically, and we would
still be left with only a single copy of the procedure to maintain.
It happens that Ada allows us to do exactly this. The solution to this problem is generics. A generic unit is a recipe or template for a procedure, function, or package. Such a unit is declared with formal parameters that are types and sometimes that are procedure or function names. An analogy can be drawn with an unusual recipe for a layer cake: all the elements are there except that the following items are left as variables to be plugged in by the baker:
This recipe was pretested by the cookbook author, but before we can use it for a three-layer yellow cake with marshmallow filling and chocolate icing, we need to (at least mentally) make all the changes necessary to the ingredients list. Only after this instance of the recipe has been created does it make sense to try to make a cake using it.
Example 11.2
Program
11.3 is a specification for a generic exchange program. This specification
indicates to the compiler that we wish ValueType
to be a formal
parameter. The formal parameters are listed between the word
GENERIC
and the procedure heading. Writing
TYPE ValueType IS PRIVATE;
tells the compiler that any type, including a private one, can be plugged in as the kind of element to exchange. We will introduce more examples of type parameters below.
Program 11.3
Specification for Generic Exchange Procedure
GENERIC TYPE ValueType IS PRIVATE; -- any type OK except LIMITED PRIVATE PROCEDURE Swap_Generic(Value1, Value2: IN OUT ValueType); ------------------------------------------------------------------------ --| Specification for generic exchange procedure --| Author: Michael B. Feldman, The George Washington University --| Last Modified: September 1995 ------------------------------------------------------------------------
The
body of Swap_Generic
appears as
Program
11.4. Notice that SwapGeneric
looks essentially the same as
the Integer
and Float
versions, except for the use of
ValueType
wherever a type is required. ValueType
is a
formal type parameter.
Program 11.4
Body of Generic Exchange Procedure
PROCEDURE Swap_Generic(Value1, Value2: IN OUT ValueType) IS ------------------------------------------------------------------------ --| Body of generic exchange procedure --| Author: Michael B. Feldman, The George Washington University --| Last Modified: September 1995 ------------------------------------------------------------------------ TempValue: ValueType; BEGIN -- Swap_Generic TempValue := Value1; Value1 := Value2; Value2 := TempValue; END Swap_Generic;Compiling the specification and the body creates a version of the generic that is ready to be instantiated, or tailored by plugging in the desired type. Here are two instances:
PROCEDURE IntegerSwap IS NEW Swap_Generic (ValueType => Integer); PROCEDURE CharSwap IS NEW Swap_Generic (ValueType => Character);
The
notation is familiar; we have used it in creating instances of
Text_IO.Enumeration_IO
and other generics.
Program
11.5 shows how Swap_Generic
could be tested and used. The two
instantiations above appear in the program.
Program 11.5
WITH Swap_Generic; WITH Ada.Text_IO; WITH Ada.Integer_Text_IO; PROCEDURE Test_Swap_Generic IS ------------------------------------------------------------------------ --| Test program for Swap_Generic --| Author: Michael B. Feldman, The George Washington University --| Last Modified: September 1995 ------------------------------------------------------------------------ X : Integer; Y : Integer; A : Character; B : Character; PROCEDURE IntegerSwap IS NEW Swap_Generic (ValueType => Integer); PROCEDURE CharSwap IS NEW Swap_Generic (ValueType => Character); BEGIN -- Test_Swap_Generic X := 3; Y := -5; A := 'x'; B := 'q'; Ada.Text_IO.Put("Before swapping, X and Y are, respectively "); Ada.Integer_Text_IO.Put(Item => X, Width => 4); Ada.Integer_Text_IO.Put(Item => Y, Width => 4); Ada.Text_IO.New_Line; IntegerSwap(Value1 => X,Value2 => Y); Ada.Text_IO.Put("After swapping, X and Y are, respectively "); Ada.Integer_Text_IO.Put(Item => X, Width => 4); Ada.Integer_Text_IO.Put(Item => Y, Width => 4); Ada.Text_IO.New_Line; Ada.Text_IO.New_Line; Ada.Text_IO.Put("Before swapping, A and B are, respectively "); Ada.Text_IO.Put(Item => A); Ada.Text_IO.Put(Item => B); Ada.Text_IO.New_Line; CharSwap(Value1 => A,Value2 => B); Ada.Text_IO.Put("After swapping, A and B are, respectively "); Ada.Text_IO.Put(Item => A); Ada.Text_IO.Put(Item => B); Ada.Text_IO.New_Line; END Test_Swap_Generic;Sample Run
Before swapping, X and Y are, respectively 3 -5 After swapping, X and Y are, respectively -5 3 Before swapping, A and B are, respectively xq After swapping, A and B are, respectively qx
Example 12.3
Consider the function Maximum
from Program 4.7, which
returns the larger of its two Integer
operands:
FUNCTION Maximum (Value1, Value2: Integer) RETURN Integer IS Result: Integer; BEGIN IF Value1 > Value2 THEN Result := Value1; ELSE Result := Value2; END IF; RETURN Result; END Maximum;
We
would like to make a function that returns the larger of its two operands,
regardless of the types of these operands. As in the case of
Generic_Swap
, we can use a generic type parameter to indicate that
an instance can be created for any type. This is not enough, however. The
IF
statement compares the two input values: Suppose the type we
use to instantiate does not have an obvious, predefined, "greater than"
operation? Suppose the type is a user-defined record with a key field, for
example? "Greater than" is not predefined for records! We can surely write such
an operation, but we need to inform the compiler to use it; when writing a
generic, we need to reassure the compiler that all the operations used in the
body of the generic will exist at instantiation time. Let us indicate in the
generic specification that a comparison function will exist.
Program
11.6
is the desired generic specification. The WITH
syntax here takes
getting used to, but it works.
Program 11.6
GENERIC TYPE ValueType IS PRIVATE; WITH FUNCTION Compare(L, R : ValueType) RETURN Boolean; FUNCTION Maximum_Generic(L, R : ValueType) RETURN ValueType; ------------------------------------------------------------------------ --| Specification for generic maximum function --| Author: Michael B. Feldman, The George Washington University --| Last Modified: September 1995 ------------------------------------------------------------------------The body of the generic function, shown as Program 11.7, looks similar to the one just given for
Maximum
.
Program 11.7
FUNCTION Maximum_Generic(L, R : ValueType) RETURN ValueType IS ------------------------------------------------------------------------ --| Body of generic maximum function --| Author: Michael B. Feldman, The George Washington University --| Last Modified: September 1995 ------------------------------------------------------------------------ BEGIN -- Maximum_Generic IF Compare(L, R) THEN RETURN L; ELSE RETURN R; END IF; END Maximum_Generic;An instantiation for
Float
values might be
FUNCTION FloatMax IS NEW Maximum_Generic (ValueType=>Float, Compare=> ">");
Notice
how the "greater than" operator is supplied. It makes no difference that the
generic expected a function and we gave it an operator; after all, an operator
is a function. What is important is that the structure of the
actual parameter matches the structure of the formal parameter. As long as
there is a ">"
available for Float
(of course
there is, in Standard
), the instantiation will succeed.
The Ada compiler has no idea what the function Compare
will do
when the generic is instantiated. It turns out, then, that if we just supply
"<"
as an actual parameter for Compare
, the
instantiation finds the minimum instead of the maximum!
Program
11.8 shows a total of six instantiations, giving minimum and maximum
functions for Integer
, Float
, and
Currency
values. All the minimums are called Minimum
;
all the maximums are called Maximum
; this is just the normal Ada
overloading principle in action.
Program 11.8
WITH Ada.Text_IO; WITH Ada.Float_Text_IO; WITH Ada.Integer_Text_IO; WITH Currency; USE Currency; WITH Currency.IO; WITH Maximum_Generic; PROCEDURE Test_Maximum_Generic IS ------------------------------------------------------------------------ --| Test program for Generic Maximum, using six instances --| Author: Michael B. Feldman, The George Washington University --| Last Modified: September 1995 ------------------------------------------------------------------------ FUNCTION Maximum IS NEW Maximum_Generic (ValueType=>Float, Compare=> ">"); FUNCTION Minimum IS NEW Maximum_Generic (ValueType=>Float, Compare=> "<"); FUNCTION Maximum IS NEW Maximum_Generic (ValueType=>Integer, Compare=> ">"); FUNCTION Minimum IS NEW Maximum_Generic (ValueType=>Integer, Compare=> "<"); FUNCTION Maximum IS NEW Maximum_Generic (ValueType=>Quantity, Compare=> ">"); FUNCTION Minimum IS NEW Maximum_Generic (ValueType=>Quantity, Compare=> "<"); BEGIN -- Test_Maximum_Generic Ada.Text_IO.Put("Maximum of -3 and 7 is "); Ada.Integer_Text_IO.Put(Item => Maximum(-3, 7), Width=>1); Ada.Text_IO.New_Line; Ada.Text_IO.Put("Minimum of -3 and 7 is "); Ada.Integer_Text_IO.Put(Item => Minimum(-3, 7), Width=>1); Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put("Maximum of -3.29 and 7.84 is "); Ada.Float_Text_IO.Put (Item => Maximum(-3.29, 7.84), Fore=>1, Aft=>2, Exp=>0); Ada.Text_IO.New_Line; Ada.Text_IO.Put("Minimum of -3.29 and 7.84 is "); Ada.Float_Text_IO.Put (Item => Minimum(-3.29, 7.84), Fore=>1, Aft=>2, Exp=>0); Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put("Maximum of 23.65 and 37.49 is "); Currency.IO.Put (Item => Maximum(MakeCurrency(23.65), MakeCurrency(37.49))); Ada.Text_IO.New_Line; Ada.Text_IO.Put("Minimum of 23.65 and 37.49 is "); Currency.IO.Put (Item => Minimum(MakeCurrency(23.65), MakeCurrency(37.49))); Ada.Text_IO.New_Line(Spacing => 2); END Test_Maximum_Generic;Sample Run
Maximum of -3 and 7 is 7 Minimum of -3 and 7 is -3 Maximum of -3.29 and 7.84 is 7.84 Minimum of -3.29 and 7.84 is -3.29 Maximum of 23.65 and 37.49 is 37.49 Minimum of 23.65 and 37.49 is 23.65
Example 12.4
Program
11.9 is a specification for a function ArrayMaximumGeneric
that returns the "largest" of all the elements in an array, regardless of the
index or element type. "Largest" is in quotes because we know already that we
can make it work as a minimum-finder as well.
Program 11.9
Specification for Generic Array Maximum Function
GENERIC TYPE ValueType IS PRIVATE; -- any nonlimited type TYPE IndexType IS (<>); -- any discrete type TYPE ArrayType IS ARRAY(IndexType RANGE <>) OF ValueType; WITH FUNCTION Compare(L, R : ValueType) RETURN Boolean; FUNCTION Maximum_Array_Generic(List: ArrayType) RETURN ValueType; ------------------------------------------------------------------------ --| Specification for generic version of array maximum finder --| Author: Michael B. Feldman, The George Washington University --| Last Modified: September 1995 ------------------------------------------------------------------------The syntax of the specification for
IndexType
means "any discrete type
is OK as an actual parameter." Recalling that discrete types are the integer
and enumeration types and subtypes, this is exactly what we need for the index
type of the array. The specification for ArrayType
looks like a
type declaration, but it is not. Rather, it is a description to the
compiler of the kind of array type acceptable as an actual parameter. In
this case, the array type must be indexed by IndexType
(or a
subtype thereof) and have elements of type Valuetype
(or a subtype
thereof).
The body of ArrayMaximumGeneric
can be seen in
Program
11.10. You can write a test program for it as an exercise. As a hint,
consider the following declarations:
TYPE FloatVector IS ARRAY(Integer RANGE <>) OF Float; TYPE RationalVector IS ARRAY (Positive RANGE <>) OF Rational;
and instantiate the generic as follows:
FUNCTION Maximum IS NEW Maximum_Array_Generic(ValueType=>Float, IndexType=>Integer, ArrayType=>FloatVector, Compare=>">"); FUNCTION Minimum IS NEW Maximum_Array_Generic(ValueType=>Rational, IndexType=>Positive, ArrayType=>RationalVector, Compare=>"<");
Program 11.10
FUNCTION Maximum_Array_Generic(List: ArrayType) RETURN ValueType IS ------------------------------------------------------------------------ --| Body of generic array maximum finder --| Author: Michael B. Feldman, The George Washington University --| Last Modified: September 1995 ------------------------------------------------------------------------ Result: ValueType; BEGIN -- Maximum_Array_Generic FOR WhichElement IN List'Range LOOP IF Compare(List(WhichElement), Result) THEN Result := List(WhichElement); END IF; END LOOP; RETURN Result; END Maximum_Array_Generic;
Swap_Generic
not be
instantiated? How about Maximum_Generic
?Swap_Generic
(
Program
11.5) to instantiate for some other types.
Maximum_Generic
(
Program
11.8).
Maximum_Array_Generic
as suggested
in the section.
Copyright © 1996 by Addison-Wesley Publishing Company, Inc.