Code reuse has been one of the great programming hopes for many years. Although suitable in the area of mathematical routines (where the functions are well defined and stable) trying to develop libraries in other areas has met with very limited success, due to the inevitable intertwining of process and data types in procedures. Ada has attempted to free us from this problem by producing code that does not rely as much on the specific data types used, but on their more general algorithmic properties.
As described by Naiditch, generics are like form letters; mostly they are complete letters with a few blanks requiring substitution (eg name and address information). Form letters aren't sent out until this information is filled in. Similarly generics cannot be used directly, we create a new subprogram or a package by 'instantiating' a generic and then using the instantiated compilation unit. When a generic is instantiated we have to supply information to fill in the blanks, such as type information, values or even subprograms.
In Ada a program unit (either a subprogram or a package) can be a generic unit.
This generic unit is used to create instances of the code that work with actual data types. The data type required is passed in as a parameter when the generic unit is instantiated. Generics are usually presented in two parts, the generic specification and then the generic package.
Once they are compiled the generics are stored in the Ada library and can be with'ed (but never use'd) by other compilation units. These other units, whether they be subprograms, packages or generics, can be with'ed and instantiated (the process of creating a usable subprogram or package by supplying generic parameters). The instatiated subprogram or package can be stored in the Ada library for later use.
The following is the instantiation of the generic package integer_io. Int_io is now available to be with'ed (and use'd) by any program.
with text_io; use text_io; package int_io is new integer_io(integer);
The package can be instantiated with other types as well.
with text_io; use text_io; with accounts; use accounts; package account_no_io is new integer_io(account_no);
The following is a compilation unit. Once it is compiled it is available to be 'with'ed (but not 'use'd) from the Ada library. It includes the keyword generic, a list of generic parameters and the procedure specification.
type element is private; -- caution private here means
-- element is a parameter to
-- the generic sub program
procedure exchange(a,b :in out element);
The body of the generic procedure is presented as a seperate compilation unit. Note that it is identical to a non-generic version.
procedure exchange(a,b :in out element) is
The code above is simply a template for an actual procedure that can be created. It can't be called. It is equivalent to a type statement - it doesn't allocate any space, it just defines a template for the shape of things to come. To actually create a procedure:
procedure swap is new exchange(integer);
We now have a procedure swap that swaps integers. Here "integer" is called a generic actual parameter. "Element" is called a generic formal parameter.
procedure swap is new exchange(character); procedure swap is new exchange(element => account); -- named association
You can create as many of these as you like. In this case the procedure name is overloaded and as normal the compiler can tell which one you call by the parameter type.
It can be called (and behaves) just as though it had been defined as
procedure swap(a,b :in out integer) is
. . .
Packages can be generics as well.
The following generic package specification is fairly standard:
generic type element is private; -- note that this is a
-- parameter to the generic package stack is procedure push(e: in element); procedure pop(e: out element); function empty return boolean; end stack;
The accompanying package body would be
package body stack is
the_stack :array(1..200) of element;
top :integer range 0..200:=0;
procedure push(e:in element) is
procedure pop(e:out element) is
function empty return boolean is
Quite simply you replace any instance of the data type to be manipulated with that of the generic type name.
How would you create a generic unit and test it?
- Create it using a specific type and translate it to use generic parameters.
There are three types of parameters to generics
Value and object parameters
So far we have only seen type parameters.
Despite the alluring appeal of generics we are still restricted by the very nature of the tasks we are attempting to accomplish. In some generics we may wish to provide a facility to sum an array of numbers. Quite clearly this is only appropriate for numbers, we can't add records together. To provide protection from someone instantitating a generic with an inappropriate type, we can specify the category of types that can be used when instantiating.
The compiler can also check that we don't perform any inappropriate actions on a variable inside the code e.g. it wont allow us to find the 'pred of a record.
A list of the various type restrictions is given below.
type T is private -- very few restrictions type T is limited private -- fewer restrictions, type T is (<>) -- T has to be a discrete type type T is range <> -- T must be an integer type type T is digits <> -- T must be a floating point type type T is delta <> -- T must be a fixed point - not -- discussed type T is array(index_type) of element_type -- The component type of the actual array must match the -- formal array type. If it is other than scalar then -- they must both be either constrained or -- unconstrained. type T is access X -- T can point to a type X (X can be -- a previously defined generic parameter.
To understand the rule governing instantiation of array type parameters consider the following generic package
generic type item is private; type index is (<>); type vector is array (index range <>) of item; type table is array (index) of item; package P is . . .and the types:
type color is (red,green.blue); type Mix is array (color range <> ) of boolean; type Option is array (color) of boolean;
then Mix can match vector and Option can match table.
package R is new P( item => boolean, index => color, vector => mix, table => option);
Value parameters allow you to specify a value for a variable inside the generic:
generic type element is private; size: positive := 200; package stack is procedure push... procedure pop... function empty return boolean; end stack; package body stack is size:integer; theStack :array(1..size) of element; . . .
You would instantiate the package thus:
package fred is new stack(element => integer, size => 50);
package fred is new stack(integer,1000);
package fred is new stack(integer);
Note that if the value parameter does not have a default value then one has to be provided when the generic is instantiated.
Value parameters such as string can also be included.
generic type element is private; file_name :string; package ....
Note that the file_name parameter type (string) is not constrained. This is identical to string parameters to subprograms.
We can pass a subprogram as a parameter to a generic.
Why? If you have a limited private type you can pass tests for equality and assignment in as subprogram parameters.
generic type element is limited private; with function "="(e1,e2:element) return boolean; with procedure assign(e1,e2:element); package stuff is . . .
To instantiate the generic
package things is new stuff(person,text."=",text.assign);
Other forms allow for a default subprogram if none is given.
with procedure assign(e1,e2:element) is myAssign(e1,e2:person);
Or you can specify the computer makes a default selection of procedure based on the normal subprogram selection rules:
with function "="(e1,e2:element ) return boolean is <>;
If no function is supplied for the "=" function then the default equal function will be used according to the type of element ( i.e. if element is integer the normal integer "=" will be used).