Ada: Subprograms

Ada: Subprograms

Procedures and functions in Ada programming.

In Ada, subprograms are equivalent to procedures and functions.

More specifically:

  • Procedures in Ada are subprograms that do not return a value. They are called procedure_name.

  • Functions in Ada are subprograms that return a value. They are called function_name and have a return type.

So subprograms in Ada refer collectively to both procedures and functions. They are the basic units of decomposition in Ada that help organize the program logic.

For example:

procedure print_message is  
begin
    Put_Line("Hello from a procedure!");
end print_message;

function add(a, b: integer) return integer is    
begin
    return a + b;
end add;

Here:

  • print_message is a procedure subprogram

  • add is a function subprogram

Both are valid Ada subprograms - one returning a value (function) and the other not (procedure).

So in summary:

  • Subprograms in Ada refer collectively to procedures and functions

  • Procedures are subprograms that do not return a value

  • Functions are subprograms that return a value

  • Both procedures and functions help organize your program logic

So subprograms in Ada are the same as procedures and functions. They just have different return types - procedures return no value while functions return a value.


Parameters

In Ada, subprograms can have three types of parameters:

  1. Input parameters:
  • These are the parameters passed to the subprogram when it is called.

  • The subprogram uses these parameters as read-only.

  • Any changes made to input parameters within the subprogram do not reflect outside.

Example:

procedure print_sum(a, b: in integer) is
begin
    Put(a + b);
end print_sum;

Here a and b are input parameters.

  1. Output parameters:
  • These are parameters passed by reference to the subprogram.

  • The subprogram can modify the value of output parameters and the changes reflect outside the subprogram.

Example:

procedure increment(a: out integer) is 
begin
    a := a + 1;  
end increment;

Here a is an output parameter. After calling increment, the passed variable will be incremented.

  1. In out parameters:
  • These parameters allow both input and output.

  • The subprogram can read from and modify the parameter, and the changes reflect outside.

Example:

procedure double(a: in out integer) is
begin
    a := a * 2; 
end double;

Here a is an in out parameter. The subprogram can read the initial value of a and then double its value.

So in summary:

  • Input parameters are read-only parameters passed to a subprogram.

  • Output parameters are passed by reference and allow the subprogram to modify them.

  • In out parameters allow both input (reading) and output (modification).


Recursive Subprograms

Recursive subprograms in Ada are subprograms that call themselves. This allows solving problems that are naturally recursive in a recursive manner.

Some characteristics of recursive subprograms in Ada:

  1. A recursive subprogram calls itself, either directly or indirectly. This results in multiple activations of the same subprogram.

  2. Each activation of a recursive subprogram has its own set of local variables. So the local variables are not shared between activations.

  3. Recursive subprograms require a base case - a condition to stop the recursion. Otherwise it will result in infinite recursion.

  4. Recursive subprograms are generally used to solve problems that are naturally recursive, like calculating factorial, traversing trees, etc.

An example of a recursive procedure in Ada:

procedure factorial(n: in integer; result: out integer) is
begin
    if n = 1 then
        result := 1;
    else 
        factorial(n - 1, result);
        result := n * result;
    end if;
end factorial;

Here:

  • The procedure calls itself recursively to calculate the factorial

  • n = 1 is the base case that stops the recursion

  • Each activation of factorial has its own copy of n and result

An example of a recursive function in Ada:

function fibonacci(n: integer) return integer is
begin
    if n < 2 then
        return n;
    else
        return fibonacci(n-1) + fibonacci(n-2);
    end if; 
end fibonacci;

Here the fibonacci number is calculated recursively by calling the function itself.

So in summary, recursive subprograms in Ada are subprograms that call themselves to solve problems that are naturally recursive. They require a base case to stop the infinite recursion.


Static Variables

Static variables in Ada are variables that are initialized only once and retain their value between subprogram calls. They are declared using the keyword 'static' like this:

static <variable_name> : <type>;

For example:

static Counter : integer := 0;

Here Counter is a static variable of type integer. It will be initialized only once to 0, and will retain its value between subprogram calls.

Static variables are useful for:

  • Maintaining state between subprogram calls

  • Generating consecutive numbers

  • Keeping a running total

The generators of consecutive numbers example in Ada uses a static variable to generate a consecutive number each time a subprogram is called:

procedure Next_Number (Number : out Integer) is
begin
    Number := Counter;
    Counter := Counter + 1;
end Next_Number;

static Counter : Integer := 0;

Here:

  • Counter is a static variable initialized to 0

  • Next_Number is a procedure that:

    • Assigns the current value of Counter to the output parameter Number

    • Increments Counter by 1

So each time Next_Number is called, it will:

  • Return the current value of Counter in Number

  • Increment Counter by 1

This allows Next_Number to generate consecutive numbers each time it is called.

For example:

Next_Number(N1); -- N1 gets 0 
Next_Number(N2); -- N2 gets 1
Next_Number(N3); -- N3 gets 2

And so on, since Counter is a static variable that retains its value between calls.


Deffered Variables

Yes, variables in Ada can be declared later, after the subprogram where they are used. This is known as deferred variable declaration.

For example, you can have:

procedure Some_Procedure is
begin
    Count := Count + 1;  
end Some_Procedure;

Here Count is used before it is declared. This is valid in Ada.

The actual declaration of Count can be done later, after the subprogram:

procedure Some_Procedure is 
begin
    Count := Count + 1;  
end Some_Procedure;

Count : Integer := 0;

Here Count is declared after its use in Some_Procedure.

This is known as deferred variable declaration in Ada, and is allowed. The variable declaration can come after its use.

The benefits of deferred variable declaration are:

  • It allows separating interface from implementation. The subprogram interface can be defined without declaring variables.

  • It makes the code more modular and readable, by declaring variables close to where they are used.

However, deferred variable declaration can make the code harder to understand, since the variable is used before it is declared. So it should be used judiciously.

In summary, yes - variables in Ada can be declared later, after they are used in a subprogram. This is known as deferred variable declaration and is allowed, though it should be used carefully to keep the code readable.


Subprogram call

Subprograms (procedures and functions) in Ada can be called in a few ways:

  1. Direct call - By specifying the subprogram name and passing arguments:
procedure Do_Something (X : Integer);

procedure Call_Subprogram is
begin
    Do_Something (1);  -- Direct call  
end Call_Subprogram;

Here Do_Something is called directly by specifying its name and passing the argument 1.

  1. Parameterless call - By just specifying the subprogram name:
procedure Do_Something;

procedure Call_Subprogram is
begin
    Do_Something;  -- Parameterless call  
end Call_Subprogram;

Here Do_Something is called without any arguments.

  1. Call by reference - By passing the address of an object:
procedure Do_Something (X : in out Integer);

procedure Call_Subprogram is
    Y : Integer := 1;
begin
    Do_Something (Y); -- Call by reference, Y is an lvalue
end Call_Subprogram;

Here Y is passed by reference, so Do_Something can modify the actual variable Y.

  1. Call by result - By passing an expression:
procedure Do_Something (X : Integer);

procedure Call_Subprogram is
begin
    Do_Something (1 + 2); -- Call by result, expression is evaluated    
end Call_Subprogram;

Here the expression 1 + 2 is passed, and is evaluated before the call.

Arguments can be of any mode (in, out, in out) and any type (scalar, array, record, access, etc.). The argument types are checked at compile time.

The calling conventions depend on the type of arguments:

  • For scalar and access arguments, the value is copied.

  • For in mode arguments, the value is copied into the callee.

  • For out and in out arguments, the address is passed, and the callee modifies the actual object.


Call-back functions

Yes, functions in Ada can receive other functions as arguments. This allows functions to do callbacks by calling the passed function.

For example, you can have:

procedure Callback (Function : access procedure);

Here Callback receives a procedure access (pointer to a procedure) as an argument. It can then call that procedure to do a callback:

procedure Callback (Function : access procedure) is
begin
    Function.all; -- Call the passed procedure  
end Callback;

Then you can define a procedure and pass it to Callback:

procedure Do_Something is 
begin
    ... 
end Do_Something;

procedure Call_Callback is
begin
    Callback (Do_Something'Access); -- Pass procedure as argument
end Call_Callback;

This will call the Do_Something procedure from within Callback, achieving a callback.

You can also pass functions as arguments. For example:

function Sum (X, Y : Integer) return Integer;

procedure Callback (Function : access function (Integer, Integer) return Integer) is
begin
    Function.all (1, 2);  -- Call the passed function  
end Callback;

procedure Call_Callback is
begin
    Callback (Sum'Access);  -- Pass function as argument  
end Call_Callback;

Here Callback receives a function that accepts 2 Integer arguments and returns an Integer. It then calls that passed function.

So in summary, functions in Ada can receive other functions as arguments using access types. This allows functions to do callbacks by calling the passed functions.


Optional Parameters

Ada supports optional parameters. Optional parameters allow a subprogram to be called with either the full set of formal parameters or a subset of them.

Optional parameters in Ada are defined using default expressions or default parameter names.

For example:

procedure Foo (X : Integer;  
               Y : Integer := 0;   --  Optional with default value
               Z : Integer);

Here Y is an optional parameter with a default value of 0. So we can call Foo as:

Foo (1, 2, 3);  -- All parameters specified

Foo (1);        -- Only X specified, Y and Z take default values

Foo (1, Z => 3); -- X and Z specified, Y takes default value

We can also define optional parameters using default parameter names:

procedure Bar (X : Integer;
               Y : Integer := <> ;   -- Optional with default name 
               Z : Integer);

Bar (1, Z => 3);  -- Only X and Z specified, Y takes default name

Here Y is optional and takes a default parameter name <>.

Optional parameters allow for more flexible subprogram calls as the caller can specify as many or as few parameters as needed.

Another useful aspect is that optional parameters always come after non-optional parameters in the parameter list. This makes the calls self-documenting as the required parameters are always specified first.

So in summary, Ada supports optional parameters using default expressions or default parameter names. This allows for more flexible subprogram calls and self-documenting interfaces.


Rest (Vararg) Parameters

Ada supports vararg (variable argument) parameters. Vararg parameters allow a subprogram to be called with a variable number of arguments of a specified type.

Vararg parameters in Ada are defined using the ... syntax. For example:

procedure Foo (X : Integer;  
               Y : Integer;  
               ... Rest : Integer);

Here Rest is a vararg parameter that can accept 0 or more Integer arguments.

We can call Foo as:

Foo (1, 2);  

Foo (1, 2, 3);

Foo (1, 2, 3, 4, 5);

The vararg parameter Rest will contain all the additional Integer arguments passed.

Within the subprogram, the vararg parameter can be accessed using the Ada.Arguments.Argument_List package. For example:

procedure Foo (... Rest : Integer) is
   Arg_List : Ada.Argument_List.Argument_List (1 .. Rest'Length);
begin
   Ada.Argument_List.To_Argument_List (Rest, Arg_List);

   -- Access individual arguments
   for I in 1 .. Arg_List.Last loop
      ...  -- Use Arg_List (I)
   end loop;
end Foo;

Here we use Ada.Argument_List.To_Argument_List to convert the vararg parameter Rest into an Argument_List which we can then iterate over to access the individual arguments.

So in summary, Ada supports vararg parameters using the ... syntax. This allows subprograms to be called with a variable number of arguments of a specified type. The vararg parameter can then be accessed within the subprogram using the Ada.Arguments package.


Hidden Features

Here are some other useful and hidden features about subprograms in Ada that you should know:

  1. Named Notation for Parameters - You can specify parameter names when calling subprograms. This makes calls self-documenting and less error prone.
procedure Swap (X, Y : in out Integer);

Swap (X => A, Y => B);

Here we specify parameter names X and Y when calling Swap.

  1. Default Expression for Return Values - You can specify a default expression for return values.
function Foo return Integer := 0;
  1. Abstract Subprograms - You can define subprograms that only specify the interface and not the implementation. Child units must implement them. Useful for defining contracts.
procedure Abs_Proc is abstract;
  1. Limited Subtypes - You can define limited subtypes that can only be used as parameters or return values. This prevents global access.
type Limited is limited private;
  1. Overloading - You can overload subprograms based on parameter types. This allows for more intuitive interfaces.
procedure Foo (X : Integer);

procedure Foo (X : Float);
  1. Generic Subprograms - You can define generic subprograms to operate on any type that meets certain requirements.
generic
   type Item is private;
procedure Sort (List : in out Item);
  1. Pragmas - Useful pragmas like Export, Import, Convention, Inline_Always etc. to optimize and interface subprograms.

Disclaim: This article was created by Rix (AI)