G. Generics - JulTob/Ada GitHub Wiki

Generics

Just like C++ templates, Generics allow doing Generic Packages that define algorithms for undefined types. This is, for types yet to be defined in run-time.

In Ada, generic is like saying "Here's a recipe for ice cream." This recipe is not for a specific flavor yet, because we haven’t specified some key ingredients. It's a template from which many specific flavors (or procedures) can be made.

In Ada, when defining a generic package, procedure, or function, the syntax requires that the generic keyword is followed directly by the definitions of the generic formal parameters, without any specific line-ending or delimiter after the word "generic." The definitions for the types and operations that form the generic formal parameters continue until the start of the actual package, procedure, or function definition. Here's how it's structured:

package.ads

generic 
      type Item_Type is private;
      with function "<" (Left, Right : Item_Type) return Boolean is <>;
   package Some_Generic_Package is
      -- Package specification here
      end Some_Generic_Package;

procedure.ads

generic
      type Element_Type is private;
      with function "=/" (Left, Right : Element_Type) return Boolean is <>;
   procedure Sort_Array(Arr : in out Array_Type);
  • Generic Formal Parameters
    • Element_Type needs to support a specific operation (=), specified using the box notation.
  • Procedure Definition
    • The Sort_Array procedure, which uses these parameters, starts right after the generic formal parameters are defined.

This grouping clarifies that the generic formal parameters are prerequisites for the functionality of the subsequent unit, ensuring that all necessary types and operations are defined before the unit is used.


Let us examine one simple yet powerful case:

Generic_Display.ads

generic
   -- Define a generic floating-point type with customizable precision.
   type Measurement_Type is delta <>;

-- Procedure to display the name and value of a measurement.
procedure Display_Measurement (Measurement_Name : in String;
                               Measurement_Value : in Measurement_Type);

Think of this code as a template for creating a tool that shows us information about different kinds of measurements. First, we decide what kind of measurement we're dealing with —whether it’s a temperature, a length, or a weight— and how precise the measurement needs to be. Then, we use our tool (Display_Measurement) to show these measurements clearly. For example, we might use it to display 'Room Temperature: 72.50 degrees' on a screen."

To use this generic recipe, you'd specify what kind of floating-point number you’re dealing with (how precise it should be) and then you'd "call" the procedure to display information about specific numbers.

Generic_Display.adb

procedure Display_Measurement (Measurement_Name : in String;
                               Measurement_Value : in Measurement_Type) is
begin
   Ada.Text_IO.Put_Line (Measurement_Name & ": " & Measurement_Type'Image(Measurement_Value));
   end Display_Measurement;

Now, let’s create an Ada program that uses this generic procedure. We will instantiate the generic procedure for specific types. Say, a precision for temperature measurements and one for humidity. Due to real-life factors, say, we have a sensitivity in our instruments of $0.01$ degrees and $0.5$ percentiles of relative humidity.

Main_Program.adb

with Ada.Text_IO; use Ada.Text_IO;
with Generic_Display;

procedure Main_Program is
   -- Define a type for temperature measurements with a precision of two decimals.
   type Temperature is delta 0.01 range -50.0 .. 50.0;

   -- Define a type for humidity measurements with a precision of 0.5.
   type Humidity is delta 0.5 range 0.0 .. 100.0;

   -- Instantiate the generic procedure for temperature.
   procedure Display_Temperature is new Generic_Display (Measurement_Type => Temperature);

   -- Instantiate the generic procedure for humidity.
   procedure Display_Humidity is new Generic_Display (Measurement_Type => Humidity);

   -- Example measurements
   Current_Temperature : Temperature := 23.45;
   Current_Humidity : Humidity := 52.5;
   begin
      -- Displaying the temperature and humidity using the instantiated procedures.
      Display_Temperature ("Room Temperature", Current_Temperature);
      Display_Humidity ("Room Humidity", Current_Humidity);
   end Main_Program;

Homononyms: Generic Overloading

This is one of those things you absolutely have to love Ada for.

Thanks to its strong types, Ada can overload functions and procedures. This is, when calling a function such as 'Add(x,y)' it can work for both Integers and floats if defined for each. You probably already know that. But did you know you can absolutely overload functions created through a generic? Well, now you know. And now think about it. Let's say you have been working on a procedure that controls a set of machines. One is a new model with high precision, the other is an old one that remains useful, but is less fine-tuned. You don't have to make a procedure for each. You can use the same libraries for both! 'Well, duh. That's what I'm programming this for.'. Well, not just that, you can control both objects within the same mainframe program! And you can use the same functions for as many types as you need, making scalability easy and reliable. You can add levels of control that would be a nightmare in python (C++ too, but everything real-life oriented is a nightmare in C++).

Let us see it work with an example: a swap procedure.

Universal Swap

Universal_Swap.ads

-- Generic specification for a Swap procedure
generic
   type Item_Type is private;
   procedure Swap(X : in out Item_Type; Y : in out Item_Type);

The generic Swap is defined with a placeholder Item_Type that is private. The keyword private means that no assumptions are made about the type, except that it supports assignment, which is necessary for swapping.

Universal_Swap.adb

-- Implementation of the generic Swap procedure
procedure Swap(X : in out Item_Type; Y : in out Item_Type) is
   Temp : Item_Type;
   begin
      Temp := X;
      X := Y;
      Y := Temp;
   end Swap;

Using_Swap.adb

with Ada.Text_IO; use Ada.Text_IO;
with Universal_Swap;  

procedure Main is
   -- Instantiate Swap for Integer
   procedure Swap is new Swap(Item_Type => Integer);
   -- Instantiate Swap for String
   procedure Swap is new Swap(Item_Type => String);

   A : Integer := 10;
   B : Integer := 20;

   S1 : String := "Hello";
   S2 : String := "World";
begin
   Ada.Text_IO.Put_Line("A: " & Integer'Image(A) & " B: " & Integer'Image(B));
   Ada.Text_IO.Put_Line("S1: " & S1 & " S2: " & S2);

   Swap(A, B);
   Swap(S1, S2);

   Ada.Text_IO.Put_Line("Swapped Integers: A = " & Integer'Image(A) & ", B = " & Integer'Image(B));
   Ada.Text_IO.Put_Line("Swapped Strings: S1 = " & S1 & ", S2 = " & S2);
   end Main;

This example demonstrates how Ada's support for overloading combined with generics enhances code readability and reuse. You can maintain a consistent interface (like the Swap procedure) across different data types, improving the maintainability of your code and reducing the likelihood of errors. Ada's strong type checking at compile time ensures that the correct procedure version is called based on the argument types, thereby leveraging the language's safety features.


Examples of use

Generic_Swap.ads

Generic Type Object (<>) is Private;  -- Object can be anything
Package Generic_Swap Is
   Procedure Swap (Left, Right: in out Object);
   End Generic_Swap;

Generic_Swap.adb

Package Body Generic_Swap Is
   Procedure Swap (Left, Right : IN OUT Object) is
     Temp: Object := Left;
     Begin
       Left:= Right; Right:= Temp;
       End Swap;
  End Generic_Swap;

Test_Swap.adb

with Generic_Swap;
procedure Test_Swap is
   type R is record N: Integer; end record;
   procedure SwapR is new Swap(R);
   A, B: R;
   begin
     SwapR(A,B);
   End Test_Swap;
generic
   type Element_Type is private;
   with function “<”(Left,Right : Element_Type) 
      return Boolean is <>;
package Sorters is
   type Element_Array is array (Natural range <>) of Element_Type;
   procedure Quick_Sort(Sequence: in out Element_Array);
   procedure Heap_Sort(Sequence: in out Element_Array);
   procedure Bubble_Sort(Sequence: in out Element_Array);
end Sorters;
with Sorters;
Procedure Example is
   Package integer_Sorters is new Sorters(Element_Type =>Integer, “<” => Standard.”<”);
   Data:Integer_Sorters.Element_Array;
begin 
   ... 
   Integer_Sorters.Quick_Sort(Data);
end Example;

Here Element_Type and “<” must be provided. <> indicates the compiler it should fill the required function if one is available.

Se utiliza la palabra with precediendo al protocolo del subprograma que se espera. Se puede especificar un nombre por defecto para el subprograma pasado por parámetro, utilizando "is ". Si se quiere que el nombre por defecto sea el mismo que el del parámetro formal, se pondrá como nombre "<>". En caso de especificar un nombre por defecto se podrá omitir el parámetro al instanciar la unidad.

generic
   type Elemento is private; 
   --  Un procedimiento con un parámetro de tipo Elemento
   with procedure Acción(X : in Elemento);
   --  Un procedimiento con un parámetro de tipo Elemento cuyo
   --  nombre por omisión es Escribir 
   with procedure Acción (X : in Elemento) is Escribir;

   --  Una función booleana con un parámetro de tipo Elemento cuyo
   --  nombre por omisión es Acción_2 
   with function Acción_2 (X : Elemento) return Boolean is <>;

Universal Array Sort

generic type Element is private;

type Index is (<>);
     with Function "<" (Left, Right : ELement) return Boolean;

type Array_Type is array (Index range <>) of Component;

procedure Sort( A: in out Array_Type);


Measurement_by_digits.ads

generic
   -- Define a generic floating-point type with customizable precision.
   type Measurement_Type is digits <>;

-- Procedure to display the name and value of a measurement.
procedure Display_Measurement (Measurement_Name : in String;
                               Measurement_Value : in Measurement_Type);
⚠️ **GitHub.com Fallback** ⚠️