Generics are used for metaprogramming in Ada. They are useful for
abstract algorithms that share common properties with each other.

Either a subprogram or a package can be generic. A generic is declared
by using the keyword generic. For example:

procedure Show_Simple_Generic is
generic
type T is private;
-- Declaration of formal types and objects
procedure Operator (X : in out T);
-- This could be one of the following:
-- <procedure | function | package>
procedure Operator (X : in out T) is null;
begin
null;
end Show_Simple_Generic;

Formal types are abstractions of a specific type. For example, we may
want to create an algorithm that works on any integer type, or even on
any type at all, whether a numeric type or not. The following example
declares a formal type T for the Set procedure.

procedure Show_Formal_Type_Declaration is
generic
type T is private;
-- T is a formal type that indicates that any type can be used,
-- possibly a numeric type or possibly even a record type.
procedure Set (E : T);
procedure Set (E : T) is null;
begin
null;
end Show_Formal_Type_Declaration;

The declaration of T as private indicates that you can map
any type to it. But you can also restrict the declaration to allow
only some types to be mapped to that formal type. Here are some
examples:

Formal objects are similar to subprogram parameters. They can reference
formal types declared in the formal specification. For example:

procedure Show_Formal_Object_Declaration is
generic
type T is private;
X : in out T;
-- X can be used in the Set procedure
procedure Set (E : T);
procedure Set (E : T) is null;
begin
null;
end Show_Formal_Object_Declaration;

Formal objects can be either input parameters or specified using the
inout mode.

We don't repeat the generic keyword for the body declaration of a
generic subprogram or package. Instead, we start with the actual
declaration and use the generic types and objects we declared. For example:

Generic subprograms or packages can't be used directly. Instead, they
need to be instantiated, which we do using the new keyword, as
shown in the following example:

with Ada.Text_IO; use Ada.Text_IO;
procedure Show_Generic_Instantiation is
generic
type T is private;
X : in out T;
-- X can be used in the Set procedure
procedure Set (E : T);
procedure Set (E : T) is
begin
X := E;
end Set;
Main : Integer := 0;
Current : Integer;
procedure Set_Main is new Set (T => Integer,
X => Main);
-- Here, we map the formal parameters to actual types and objects.
--
-- The same approach can be used to instantiate functions or
-- packages, e.g.:
-- function Get_Main is new ...
-- package Integer_Queue is new ...
begin
Current := 10;
Set_Main (Current);
Put_Line ("Value of Main is " & Integer'Image (Main));
end Show_Generic_Instantiation;

In the example above, we instantiate the procedure Set by mapping the
formal parameters T and X to actual existing elements, in this case
the Integer type and the Main variable.

The previous examples focused on generic subprograms. In this section,
we look at generic packages. The syntax is similar to that used for
generic subprograms: we start with the generic keyword and
continue with formal declarations. The only difference is that
package is specified instead of a subprogram keyword.

In this example, Swap_Colors can only be used for the Color
type. However, this algorithm can theoretically be used for any type,
whether an enumeration type or a complex record type with many
elements. The algorithm itself is the same: it's only the type that
differs. If, for example, we want to swap variables of Integer
type, we don't want to duplicate the implementation. Therefore, such
an algorithm is a perfect candidate for abstraction using generics.

In the example below, we create a generic version of Swap_Colors
and name it Generic_Swap. This generic version can operate on any
type due to the declaration of formal type T.

As we can see in the example, we can create the same Swap_Colors
procedure as we had in the non-generic version of the algorithm by
declaring it as an instance of the generic Generic_Swap procedure. We
specify that the generic T type will be mapped to the Color type by
passing it as an argument to the Generic_Swap instantiation,

The previous example, with an algorithm to swap two values, is one of the
simplest examples of using generics. Next we study an algorithm for
reversing elements of an array. First, let's start with a non-generic
version of the algorithm, one that works specifically for the Color
type:

The procedure Reverse_Color_Array takes an array of colors, starts by
swapping the first and last elements of the array, and continues doing that
with successive elements until it reaches the middle of array. At that
point, the entire array has been reversed, as we see from the output of the
test program.

To abstract this procedure, we declare formal types for three components of
the algorithm:

In the previous example we've focused only on abstracting the reversing
algorithm itself. However, we could have decided to also abstract our small
test application. This could be useful if we, for example, decide to test
other procedures that change elements of an array.

In order to do this, we again have to choose the elements to abstract. We
therefore declare the following formal parameters:

S: the string containing the array name

a function Image that converts an element of type T to a
string

a procedure Test that performs some operation on the array

Note that Image and Test are examples of formal subprograms and
S is an example of a formal object.

Here is a version of the test application making use of the generic
Perform_Test procedure: