“learn.adacore.com" (original) (raw)

Subprograms

Subprograms

So far, we have used procedures, mostly to have a main body of code to execute. Procedures are one kind of subprogram.

There are two kinds of subprograms in Ada, functions and procedures. The distinction between the two is that a function returns a value, and a procedure does not.

This example shows the declaration and definition of a function:

function Increment (I : Integer) return Integer;

-- We declare (but don't define) a function with -- one parameter, returning an integer value

function Increment (I : Integer) return Integer is -- We define the Increment function begin return I + 1; end Increment;

                Use tabbed editor view
            
                
                Use the dark theme

                    
                      -g

                    
                      -O0

                    
                      -gnata

                    
                      -gnatW8

                    
                      -gnatwa

                    
                      -gnatyg0-s

                    
                      -gnatyM50

                    
                      -gnatyM80

                    
                      -gnato

                    
                      -gnato0

                    
                      -gnato11

                    
                      -gnato21

                    
                      -gnato22

                    
                      -gnato23

                    
                      -gnateE

                    
                      -gnat2022

                    
                      -gnatX

Subprograms in Ada can, of course, have parameters. One syntactically important note is that a subprogram which has no parameters does not have a parameter section at all, for example:

procedure Proc;

function Func return Integer;

Here's another variation on the previous example:

function Increment_By (I : Integer := 0; Incr : Integer := 1) return Integer; -- ^ Default value for parameters

                Use tabbed editor view
            
                
                Use the dark theme

                    
                      -g

                    
                      -O0

                    
                      -gnata

                    
                      -gnatW8

                    
                      -gnatwa

                    
                      -gnatyg0-s

                    
                      -gnatyM50

                    
                      -gnatyM80

                    
                      -gnato

                    
                      -gnato0

                    
                      -gnato11

                    
                      -gnato21

                    
                      -gnato22

                    
                      -gnato23

                    
                      -gnateE

                    
                      -gnat2022

                    
                      -gnatX

In this example, we see that parameters can have default values. When calling the subprogram, you can then omit parameters if they have a default value. Unlike C/C++, a call to a subprogram without parameters does not include parentheses.

This is the implementation of the function above:

function Increment_By (I : Integer := 0; Incr : Integer := 1) return Integer is begin return I + Incr; end Increment_By;

                Use tabbed editor view
            
                
                Use the dark theme

                    
                      -g

                    
                      -O0

                    
                      -gnata

                    
                      -gnatW8

                    
                      -gnatwa

                    
                      -gnatyg0-s

                    
                      -gnatyM50

                    
                      -gnatyM80

                    
                      -gnato

                    
                      -gnato0

                    
                      -gnato11

                    
                      -gnato21

                    
                      -gnato22

                    
                      -gnato23

                    
                      -gnateE

                    
                      -gnat2022

                    
                      -gnatX

Subprogram calls

We can then call our subprogram this way:

with Ada.Text_IO; use Ada.Text_IO; with Increment_By;

procedure Show_Increment is A, B, C : Integer; begin C := Increment_By; -- ^ Parameterless call, -- value of I is 0 -- and Incr is 1

Put_Line ("Using defaults for Increment_By is " & Integer'Image (C));

A := 10; B := 3; C := Increment_By (A, B); -- ^ Regular parameter passing

Put_Line ("Increment of " & Integer'Image (A) & " with " & Integer'Image (B) & " is " & Integer'Image (C));

A := 20; B := 5; C := Increment_By (I => A, Incr => B); -- ^ Named parameter passing

Put_Line ("Increment of " & Integer'Image (A) & " with " & Integer'Image (B) & " is " & Integer'Image (C)); end Show_Increment;

                Use tabbed editor view
            
                
                Use the dark theme

                    
                      -g

                    
                      -O0

                    
                      -gnata

                    
                      -gnatW8

                    
                      -gnatwa

                    
                      -gnatyg0-s

                    
                      -gnatyM50

                    
                      -gnatyM80

                    
                      -gnato

                    
                      -gnato0

                    
                      -gnato11

                    
                      -gnato21

                    
                      -gnato22

                    
                      -gnato23

                    
                      -gnateE

                    
                      -gnat2022

                    
                      -gnatX

Ada allows you to name the parameters when you pass them, whether they have a default or not. There are some rules:

As a convention, people usually name parameters at the call site if the function's corresponding parameters has a default value. However, it is also perfectly acceptable to name every parameter if it makes the code clearer.

Nested subprograms

As briefly mentioned earlier, Ada allows you to declare one subprogram inside another.

This is useful for two reasons:

For the previous example, we can move the duplicated code (call toPut_Line) to a separate procedure. This is a shortened version with the nested Display_Result procedure.

with Ada.Text_IO; use Ada.Text_IO; with Increment_By;

procedure Show_Increment is A, B, C : Integer;

procedure Display_Result is begin Put_Line ("Increment of " & Integer'Image (A) & " with " & Integer'Image (B) & " is " & Integer'Image (C)); end Display_Result;

begin A := 10; B := 3; C := Increment_By (A, B); Display_Result; A := 20; B := 5; C := Increment_By (A, B); Display_Result; end Show_Increment;

                Use tabbed editor view
            
                
                Use the dark theme

                    
                      -g

                    
                      -O0

                    
                      -gnata

                    
                      -gnatW8

                    
                      -gnatwa

                    
                      -gnatyg0-s

                    
                      -gnatyM50

                    
                      -gnatyM80

                    
                      -gnato

                    
                      -gnato0

                    
                      -gnato11

                    
                      -gnato21

                    
                      -gnato22

                    
                      -gnato23

                    
                      -gnateE

                    
                      -gnat2022

                    
                      -gnatX

Function calls

An important feature of function calls in Ada is that the return value at a call cannot be ignored; that is, a function call cannot be used as a statement.

If you want to call a function and do not need its result, you will still need to explicitly store it in a local variable.

function Quadruple (I : Integer) return Integer is

function Double (I : Integer)
                 return Integer is
begin
   return I * 2;
end Double;

Res : Integer := Double (Double (I)); -- ^ Calling the Double -- function begin Double (I); -- ERROR: cannot use call to function -- "Double" as a statement

return Res; end Quadruple;

                Use tabbed editor view
            
                
                Use the dark theme

                    
                      -g

                    
                      -O0

                    
                      -gnata

                    
                      -gnatW8

                    
                      -gnatwa

                    
                      -gnatyg0-s

                    
                      -gnatyM50

                    
                      -gnatyM80

                    
                      -gnato

                    
                      -gnato0

                    
                      -gnato11

                    
                      -gnato21

                    
                      -gnato22

                    
                      -gnato23

                    
                      -gnateE

                    
                      -gnat2022

                    
                      -gnatX

Parameter modes

So far we have seen that Ada is a safety-focused language. There are many ways this is realized, but two important points are:

Parameter modes are a feature that helps achieve the two design goals above. A subprogram parameter can be specified with a mode, which is one of the following:

in Parameter can only be read, not written
out Parameter can be written to, then read
in out Parameter can be both read and written

The default mode for parameters is in; so far, most of the examples have been using in parameters.

Historically

Functions and procedures were originally more different in philosophy. Before Ada 2012, functions could only take in parameters.

Subprogram calls

In parameters

The first mode for parameters is the one we have been implicitly using so far. Parameters passed using this mode cannot be modified, so that the following program will cause an error:

procedure Swap (A, B : Integer) is Tmp : Integer; begin Tmp := A;

-- Error: assignment to "in" mode -- parameter not allowed A := B;

-- Error: assignment to "in" mode -- parameter not allowed B := Tmp; end Swap;

                Use tabbed editor view
            
                
                Use the dark theme

                    
                      -g

                    
                      -O0

                    
                      -gnata

                    
                      -gnatW8

                    
                      -gnatwa

                    
                      -gnatyg0-s

                    
                      -gnatyM50

                    
                      -gnatyM80

                    
                      -gnato

                    
                      -gnato0

                    
                      -gnato11

                    
                      -gnato21

                    
                      -gnato22

                    
                      -gnato23

                    
                      -gnateE

                    
                      -gnat2022

                    
                      -gnatX

The fact that in is the default mode is very important. It means that a parameter will not be modified unless you explicitly specify a mode in which modification is allowed.

In out parameters

To correct our code above, we can use an in out parameter.

with Ada.Text_IO; use Ada.Text_IO;

procedure In_Out_Params is procedure Swap (A, B : in out Integer) is Tmp : Integer; begin Tmp := A; A := B; B := Tmp; end Swap;

A : Integer := 12; B : Integer := 44; begin Swap (A, B);

--  Prints 44
Put_Line (Integer'Image (A));

end In_Out_Params;

                Use tabbed editor view
            
                
                Use the dark theme

                    
                      -g

                    
                      -O0

                    
                      -gnata

                    
                      -gnatW8

                    
                      -gnatwa

                    
                      -gnatyg0-s

                    
                      -gnatyM50

                    
                      -gnatyM80

                    
                      -gnato

                    
                      -gnato0

                    
                      -gnato11

                    
                      -gnato21

                    
                      -gnato22

                    
                      -gnato23

                    
                      -gnateE

                    
                      -gnat2022

                    
                      -gnatX

An in out parameter will allow read and write access to the object passed as parameter, so in the example above, we can see that A is modified after the call to Swap.

Attention

While in out parameters look a bit like references in C++, or regular parameters in Java that are passed by-reference, the Ada language standard does not mandate "by reference" passing for in out parameters except for certain categories of types as will be explained later.

In general, it is better to think of modes as higher level than by-value versus by-reference semantics. For the compiler, it means that an array passed as an in parameter might be passed by reference, because it is more efficient (which does not change anything for the user since the parameter is not assignable). However, a parameter of a discrete type will always be passed by copy, regardless of its mode (which is more efficient on most architectures).

Out parameters

The out mode applies when the subprogram needs to write to a parameter that might be uninitialized at the point of call. Reading the value of anout parameter is permitted, but it should only be done after the subprogram has assigned a value to the parameter. Out parameters behave a bit like return values for functions. When the subprogram returns, the actual parameter (a variable) will have the value of the out parameter at the point of return.

In other languages

Ada doesn't have a tuple construct and does not allow returning multiple values from a subprogram (except by declaring a full-fledged record type). Hence, a way to return multiple values from a subprogram is to use outparameters.

For example, a procedure reading integers from the network could have one of the following specifications:

procedure Read_Int (Stream : Network_Stream; Success : out Boolean; Result : out Integer);

function Read_Int (Stream : Network_Stream; Result : out Integer) return Boolean;

While reading an out variable before writing to it should, ideally, trigger an error, imposing that as a rule would cause either inefficient run-time checks or complex compile-time rules. So from the user's perspective an out parameter acts like an uninitialized variable when the subprogram is invoked.

Forward declaration of subprograms

As we saw earlier, a subprogram can be declared without being fully defined, This is possible in general, and can be useful if you need subprograms to be mutually recursive, as in the example below:

procedure Mutually_Recursive_Subprograms is procedure Compute_A (V : Natural); -- Forward declaration of Compute_A

procedure Compute_B (V : Natural) is
begin
   if V > 5 then
      Compute_A (V - 1);
      --  Call to Compute_A
   end if;
end Compute_B;

procedure Compute_A (V : Natural) is
begin
   if V > 2 then
      Compute_B (V - 1);
      --  Call to Compute_B
   end if;
end Compute_A;

begin Compute_A (15); end Mutually_Recursive_Subprograms;

                Use tabbed editor view
            
                
                Use the dark theme

                    
                      -g

                    
                      -O0

                    
                      -gnata

                    
                      -gnatW8

                    
                      -gnatwa

                    
                      -gnatyg0-s

                    
                      -gnatyM50

                    
                      -gnatyM80

                    
                      -gnato

                    
                      -gnato0

                    
                      -gnato11

                    
                      -gnato21

                    
                      -gnato22

                    
                      -gnato23

                    
                      -gnateE

                    
                      -gnat2022

                    
                      -gnatX

Renaming

Subprograms can be renamed by using the renames keyword and declaring a new name for a subprogram:

procedure New_Proc renames Original_Proc;

This can be useful, for example, to improve the readability of your application when you're using code from external sources that cannot be changed in your system. Let's look at an example:

procedure A_Procedure_With_Very_Long_Name_That_Cannot_Be_Changed (A_Message : String);

with Ada.Text_IO; use Ada.Text_IO;

procedure A_Procedure_With_Very_Long_Name_That_Cannot_Be_Changed (A_Message : String) is begin Put_Line (A_Message); end A_Procedure_With_Very_Long_Name_That_Cannot_Be_Changed;

                Use tabbed editor view
            
                
                Use the dark theme

                    
                      -g

                    
                      -O0

                    
                      -gnata

                    
                      -gnatW8

                    
                      -gnatwa

                    
                      -gnatyg0-s

                    
                      -gnatyM50

                    
                      -gnatyM80

                    
                      -gnato

                    
                      -gnato0

                    
                      -gnato11

                    
                      -gnato21

                    
                      -gnato22

                    
                      -gnato23

                    
                      -gnateE

                    
                      -gnat2022

                    
                      -gnatX

As the wording in the name of procedure above implies, we cannot change its name. We can, however, rename it to something like Show in our test application and use this shorter name. Note that we also have to declare all parameters of the original subprogram — we may rename them, too, in the declaration. For example:

with A_Procedure_With_Very_Long_Name_That_Cannot_Be_Changed;

procedure Show_Renaming is

procedure Show (S : String) renames
  A_Procedure_With_Very_Long_Name_That_Cannot_Be_Changed;

begin Show ("Hello World!"); end Show_Renaming;

                Use tabbed editor view
            
                
                Use the dark theme

                    
                      -g

                    
                      -O0

                    
                      -gnata

                    
                      -gnatW8

                    
                      -gnatwa

                    
                      -gnatyg0-s

                    
                      -gnatyM50

                    
                      -gnatyM80

                    
                      -gnato

                    
                      -gnato0

                    
                      -gnato11

                    
                      -gnato21

                    
                      -gnato22

                    
                      -gnato23

                    
                      -gnateE

                    
                      -gnat2022

                    
                      -gnatX

Note that the original name (A_Procedure_With_Very_Long_Name_That_Cannot_Be_Changed) is still visible after the declaration of the Show procedure.

We may also rename subprograms from the standard library. For example, we may rename Integer'Image to Img:

with Ada.Text_IO; use Ada.Text_IO;

procedure Show_Image_Renaming is

function Img (I : Integer) return String renames Integer'Image;

begin Put_Line (Img (2)); Put_Line (Img (3)); end Show_Image_Renaming;

                Use tabbed editor view
            
                
                Use the dark theme

                    
                      -g

                    
                      -O0

                    
                      -gnata

                    
                      -gnatW8

                    
                      -gnatwa

                    
                      -gnatyg0-s

                    
                      -gnatyM50

                    
                      -gnatyM80

                    
                      -gnato

                    
                      -gnato0

                    
                      -gnato11

                    
                      -gnato21

                    
                      -gnato22

                    
                      -gnato23

                    
                      -gnateE

                    
                      -gnat2022

                    
                      -gnatX

Renaming also allows us to introduce default expressions that were not available in the original declaration. For example, we may specify "Hello World!"as the default for the String parameter of the Show procedure:

with A_Procedure_With_Very_Long_Name_That_Cannot_Be_Changed;

procedure Show_Renaming_Defaults is

procedure Show (S : String := "Hello World!") renames A_Procedure_With_Very_Long_Name_That_Cannot_Be_Changed;

begin Show; end Show_Renaming_Defaults;