In Ada the subprograms are classified into two categories:procedures andfunctions. A procedures call is a statement and does not return any value, whereas a function returns a value and must therefore be a part of an expression.
Subprogram parameters may have three modes.
ininoutoutout parameters were write-only.)A parameter of any mode may also be explicitlyaliased.
accessNote that parameter modes do not specify the parameter passing method. Their purpose is to document the data flow.
The parameter passing method depends on the type of the parameter. A rule of thumb is that parameters fitting into a register are passed by copy, others are passed by reference. For certain types, there are special rules, for others the parameter passing mode is left to the compiler (which you can assume to do what is most sensible). Tagged types are always passed by reference.
Explicitlyaliased parameters andaccess parameters specify pass by reference.
Unlike in the C class of programming languages, Ada subprogram calls cannot have empty parameters parentheses() when there are no parameters.
A procedure call in Ada constitutes a statement by itself.
For example:
procedureA_Test(A, B:inInteger; C:outInteger)isbeginC := A + B;endA_Test;
When the procedure is called with the statement
A_Test (5 + P, 48, Q);
the expressions 5 + P and 48 are evaluated (expressions are only allowed for in parameters), and then assigned to the formal parameters A and B, which behave like constants. Then, the value A + B is assigned to formal variable C, whose value will be assigned to the actual parameter Q when the procedure finishes.
C, being anout parameter, is an uninitialized variable before the first assignment. (Therefore in Ada 83, there existed the restriction thatout parameters are write-only. If you wanted to read the value written, you had to declare a local variable, do all calculations with it, and finally assign it to C before return. This was awkward and error prone so the restriction was removed in Ada 95.)
Within a procedure, the return statement can be used without arguments to exit the procedure and return the control to the caller.
For example, to solve an equation of the kind:
withAda.Numerics.Elementary_Functions;useAda.Numerics.Elementary_Functions;procedureQuadratic_Equation(A, B, C : Float;-- By default it is "in". R1, R2 :outFloat; Valid :outBoolean)isZ : Float;beginZ := B**2 - 4.0 * A * C;ifZ < 0.0orA = 0.0thenValid := False;-- Being out parameter, it should be modified at least once. R1 := 0.0; R2 := 0.0;elseValid := True; R1 := (-B + Sqrt (Z)) / (2.0 * A); R2 := (-B - Sqrt (Z)) / (2.0 * A);endif;endQuadratic_Equation;
The function SQRT calculates the square root of non-negative values. If the roots are real, they are given back in R1 and R2, but if they are complex or the equation degenerates (A = 0), the execution of the procedure finishes after assigning to the Valid variable the False value, so that it is controlled after the call to the procedure. Notice that theout parameters should be modified at least once, and that if a mode is not specified, it is impliedin.
A function is a subprogram that can be invoked as part of an expression. Until Ada 2005, functions can only takein (the default) oraccess parameters; the latter can be used as a work-around for the restriction that functions may not haveout parameters. Ada 2012 has removed this restriction.
Here is an example of a function body:
functionMinimum (A, B: Integer)returnIntegerisbeginifA <= BthenreturnA;elsereturnB;endif;endMinimum;
(There is, by the way, also the attributeInteger'Min, see RM 3.5.) Or in Ada2012:
functionMinimum (A, B: Integer)returnIntegerisbeginreturn(ifA <= BthenAelseB);endMinimum;
or even shorter as anexpression function
functionMinimum (A, B: Integer)returnIntegeris(ifA <= BthenAelseB);
The formal parameters with modein behave as local constants whose values are provided by the corresponding actual parameters. The statementreturn is used to indicate the value returned by the function call and to give back the control to the expression that called the function. The expression of thereturn statement may be of arbitrary complexity and must be of the same type declared in the specification. If an incompatible type is used, the compiler gives an error. If the restrictions of a subtype are not fulfilled, e.g. a range, it raises a Constraint_Error exception.
The body of the function can contain severalreturn statements and the execution of any of them will finish the function, returning control to the caller. If the flow of control within the function branches in several ways, it is necessary to make sure that each one of them is finished with areturnstatement. If at run time the end of a function is reached without encountering areturn statement, the exception Program_Error is raised. Therefore, the body of a function must have at least one suchreturnstatement.
Every call to a function produces a new copy of any object declared within it. When the function finalizes, its objects disappear. Therefore, it is possible to call the function recursively. For example, consider this implementation of the factorial function:
functionFactorial (N : Positive)returnPositiveisbeginifN = 1thenreturn1;elsereturn(N * Factorial (N - 1));endif;endFactorial;
When evaluating the expressionFactorial (4); the functionwill be called with parameter 4 and within the function it willtry to evaluate the expressionFactorial (3), calling itself as a function, but in this case parameter N would be 3 (each call copies the parameters) and so on until N = 1 is evaluated which will finalize the recursion and then the expression will begin to be completed in the reverse order.
A formal parameter of a function can be of any type, including vectors orrecords. Nevertheless, it cannot be an anonymous type, that is, itstype must be declared before, for example:
typeFloat_Vectorisarray(Positiverange<>)ofFloat;functionAdd_Components (V: Float_Vector)returnFloatisResult : Float := 0.0;beginforIinV'RangeloopResult := Result + V(I);endloop;returnResult;endAdd_Components;
In this example, the function can be used on a vector of arbitrary dimension.Therefore, there are no static bounds in the parameters passed to thefunctions. For example, it is possible to be used in the followingway:
V4 : Float_Vector (1 .. 4) := (1.2, 3.4, 5.6, 7.8);Sum : Float;Sum := Add_Components (V4);
In the same way, a function can also return a type whose bounds are not known a priori. For example:
functionInvert_Components (V : Float_Vector)returnFloat_VectorisResult : Float_Vector(V'Range);-- Fix the bounds of the vector to be returned.beginforIinV'RangeloopResult(I) := V (V'First + V'Last - I);endloop;returnResult;endInvert_Components;
The variable Result has the same bounds as V, so the returned vector will always have the same dimension as the one passed as parameter.
A value returned by a function can be used without assigning it to a variable, it can be referenced as an expression. For example,Invert_Components (V4) (1), where the first element of the vector returned by the function would be obtained (in this case, the last element of V4, i.e. 7.8).
In subprogram calls, named parameter notation (i.e. the name of the formalparameter followed of the symbol=> and then the actual parameter) allows the rearrangement of the parameters in the call. For example:
Quadratic_Equation (Valid => OK, A => 1.0, B => 2.0, C => 3.0, R1 => P, R2 => Q);F := Factorial (N => (3 + I));
This is especially useful to make clear which parameter is which.
Phi := Arctan (A, B);Phi := Arctan (Y => A, X => B);
The first call (from Ada.Numerics.Elementary_Functions) is not very clear. One might easily confuse the parameters. The second call makes the meaning clear without any ambiguity.
Another use is for calls with numeric literals:
Ada.Float_Text_IO.Put_Line (X, 3, 2, 0); -- ?Ada.Float_Text_IO.Put_Line (X, Fore => 3, Aft => 2, Exp => 0); -- OK
On the other hand, formal parameters may have default values. They can, therefore, be omitted in the subprogram call. For example:
procedureBy_Default_Example (A, B:inInteger := 0);
can be called in these ways:
By_Default_Example (5, 7);-- A = 5, B = 7By_Default_Example (5);-- A = 5, B = 0By_Default_Example;-- A = 0, B = 0By_Default_Example (B => 3);-- A = 0, B = 3By_Default_Example (1, B => 2);-- A = 1, B = 2
In the first statement, a "regular call" is used (with positional association); the second also uses positional association but omits the second parameter to use the default; in the third statement, all parameters are by default; the fourth statement uses named association to omit the first parameter; finally, the fifth statement uses mixed association, here the positional parameters have to precede the named ones.
Note that the default expression is evaluated once for each formal parameter that has no actual parameter. Thus, if in the above example a function would be used as defaults for A and B, the function would be evaluated once in case 2 and 4; twice in case 3, so A and B could have different values; in the others cases, it would not be evaluated.
Subprograms may be renamed. The parameter and result profile for a renaming-as-declaration must be mode conformant.
procedureSolve (A, B, C:inFloat; R1, R2 :outFloat; Valid :outBoolean)renamesQuadratic_Equation;
This may be especially comfortable for tagged types.
packageSome_PackageistypeMessage_Typeistaggednullrecord;procedurePrint (Message:inMessage_Type);endSome_Package;
withSome_Package;procedureMainisMessage: Some_Package.Message_Type;procedurePrintrenamesMessage.Print;-- this has convention intrinsic, see RM 6.3.1(10.1/2)Method_Ref:-- thus taking 'Access should be illegal; GNAT GPL 2012 allows thisaccessprocedure:= Print'Access;begin-- All these calls are equivalent: Some_Package.Print (Message);-- traditional call without use clause Message.Print;-- Ada 2005 method.object call - note: no use clause necessary Print;-- Message.Print is a parameterless procedure and can be renamed as suchMethod_Ref.-- GNAT GPL 2012 allows illegal call via an access to the renamed procedure Print-- This has been corrected in the current version (as of Nov 22, 2012)all;endMain;
But note thatMessage.Print' is illegal, you have to use a renaming declaration as above.Access;
Since only mode conformance is required (and not full conformance as between specification and body), parameter names and default values may be changed with renamings:
procedureP (X:inInteger := 0);procedureR (A:inInteger := -1)renamesP;