Procedures and Functions
When we compiled procedures Hello and Add, we made it possible for other units to with them and call them. We can also compile just a specification, supplying the body later. For example, we could compile
function English_Upper_Case(S : in String) return String;and then compile the calling program. When we later write the body, it must agree with the specification:
function English_Upper_Case(S : in String) return String is Answer : String(S'Range) := S; begin for I in S'Range loop if S(I) in 'a' .. 'z' then Answer(I) := Character'Val(Character'Pos(S(I)) - 32); end if; end loop; return Answer; end English_Upper_Case;
Functions and procedures may also be declared locally. In Ada 83, such declarations must follow any simple declarations like S : String(1 .. 5);.
with Ada.Text_IO; use Ada.Text_IO; procedure Greeting is S : String(1 .. 5) := "Hello"; function English_Upper_Case(S : in String) return String is Answer : String(S'Range) := S; begin for I in S'Range loop if S(I) in 'a' .. 'z' then Answer(I) := Character'Val(Character'Pos(S(I)) - 32); end if; end loop; return Answer; end English_Upper_Case; begin Put_Line(English_Upper_Case(S)); end Greeting;
It's usually better to declare local functions and procedures to be separate. These subprograms can in turn declare separate subprograms, to any depth:
procedure Main is function A return Float is separate; begin ... end Main; separate (Main) function A return Float is procedure B is separate; begin ... end A; separate (Main.A) procedure B is procedure C(I : in Integer) is separate; begin ... end B; separate (Main.A.B) procedure C(I : in Integer) is ... (etc.)
However, is separate may be used only at the outermost level. The example below is legal as it stands, but we may not make procedure B separate unless we also make function A separate, thus bringing function A to the outermost level.
procedure Main is F : Float; function A return Float is Answer : Float; procedure B is begin ... end B; begin ... return Answer; end A; begin ... end Main;
A program that begins separate (...) must be compiled after the program that says is separate, because a subprogram depends on its "parent."
In Ada 95, Hierarchial Libraries provide an alternative to separate subprograms. We'll learn about Hierarchial Libraries a little later. Also, the function To_Upper in the Ada 95 package Ada.Characters.Handling is better than our function English_Upper_Case, because it handles accented letters. We presented our function only to give a simple example that will work in both Ada 83 and Ada 95.
A procedure or function specification gives the name, mode, and type of each parameter. A function specification also gives the type of the result. The mode of the parameters can be in, out, or in out. The subprogram may read from, but not write to, in parameters, and it may write to, but not read from, out parameters. (In Ada 95, it may read out parameters.) If the mode is omitted, it's assumed to be in. Thus, these two lines have the same effect:
procedure Hanoi(N : in Natural; From, To, Spare : in Character); procedure Hanoi(N : Natural; From, To, Spare : Character);
The parameters of a function must always be of mode in, never out or in out.
Note that when several parameters have the same mode and type, they can be placed in one list, separated by commas. The lists themselves are separated by semicolons. The two specifications above are equivalent to:
procedure Hanoi(N : Natural; From: Character; To: Character; Spare: Character);
In any event, the parameters in a call to a procedure or function are always separated by commas:
Hanoi(5, 'A', 'B', 'C');
procedure P(A; B; C : in Integer);
No, in number 1 the items should be separated by commas, not semicolons. In a subprogram specification, the items in a list are separated by commas, and the lists are separated by semicolons. Here a "list" means a collection of parameters sharing the same mode and type (in Integer).
procedure P(A, B, C : Integer; D, E : in out Float) return Character;
No, number 2 is illegal because a return clause applies only to a function specification, not a procedure specification.
function F(A, B, C : Integer; D, E : in out Float) return Character;
No, number 3 is illegal because all parameters of a function must have mode in.
procedure P(A, B, C : Integer; D, E : in out Float);
You're right! In a subprogram specification, the items of a list are separated by commas, and the lists are separated by semicolons. In a list, the mode may be omitted; it's assumed to be in.
In number 1, the items should be separated by commas. Number 2 has a return clause in a procedure specification, and number 3 has a mode other than in in a function specification.
Recall that type conversion from Float to Integer in Ada rounds to the nearest integer. (The Ada 83 standard doesn't specify which way rounding occurs when the fractional part is exactly 0.5; the Ada 95 standard specifies rounding away from zero in such cases.) This function Floor computes the largest integer less than or equal to a given Float, always rounding toward negative infinity:
function Floor(X : in Float) return Integer is Answer : Integer := Integer(X); begin if Float(Answer) > X then Answer := Answer - 1; end if; return Answer; end Floor;Similarly, this function Truncate converts from Float to Integer, always truncating toward zero:
function Truncate(X : in Float) return Integer is Answer : Integer := Integer(X); begin if Answer > 0 and Float(Answer) > X then Answer := Answer - 1; elsif Answer < 0 and Float(Answer) < X then Answer := Answer + 1; end if; return Answer; end Truncate;
In Ada 95, the attributes 'Floor, 'Ceiling, and 'Truncation, available for any floating point type, are better than the above functions. If X is of type Float, then Float'Floor(X) is the largest integer <= X, Float'Ceiling(X) is the smallest integer >= X, and Float'Truncation always truncates toward zero.
The in parameters of a subprogram specification may be given default values. For example, here's a simplified version of the specification for the procedure Put in Ada.Integer_Text_IO (the actual specification can be found in Annex A.10.1 of the Ada 95 RM):
procedure Put(Item : in Integer; Width : in Integer := 6; Base : in Integer := 10);
This means that, in calls to Put, the Width and Base parameters are optional. If Width is omitted, it's assumed to be 6, and if Base is omitted, it's assumed to be 10. If either of these parameters is given in the call, the default value is overridden. (The default value for Width is shown here as 6. Actually, it depends on the implementation of Ada. Of course, the default value for Base is always 10.)
Default parameters let us make our Ada subprograms both flexible and easy to use. In other languages, we'd often have to choose between these two qualities. For example, suppose J is an integer. Here are some calls to Put:
Put(J); Put(J, Width => 4); Put(J, Base => 16); Put(J, Base => 16, Width => 4);
The first parameter in each call could have been given as Item => J, but everyone remembers that the first parameter of Put is the item, so named notation seems unnecessary for this parameter. However, Width and Base are used less frequently. We used named notation for these parameters so the reader of our code wouldn't have to remember which parameter comes second and which comes third. Note that if we omit the second parameter and specify the third, we must use named notation for the third parameter; we're not allowed to write Put(J, ,16); as in some languages.
If we were writing Put in another language, we'd have to choose either making the user specify the width and the base in every call (giving flexibility), or writing Put with only one parameter (giving ease of use). In Ada, default parameters give us both flexibility and ease of use!
Ada.Text_IO.New_Line has one parameter, Spacing, defaulted to 1. Thus, we can call New_Line; to get one CR-LF, or, for example, New_Line(3); to get three.
Default values may also be given in record definitions. If we write
type Month_Type is (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec); subtype Day_Subtype is Integer range 1 .. 31; type Date is record Day : Day_Subtype; Month : Month_Type; Year : Integer := 1776; end record; USA : Date;then USA.Year is set to 1776 when the line declaring USA is elaborated. Every time an object of type Date is declared, its Year field is set to 1776. However, there's a difference between default values in records and default parameters in subprograms. We can't write USA := (4, Jul); all fields of a record must be specified in an aggregate.
procedure Put(Item : in Integer; Width : in Integer := 11; Base : in Integer := 10);Which one of these is legal?
Put(Item => 23, 5, 10);
No, number 1 is illegal because positional notation may not follow named.
You're right! The third parameter may be omitted because it has a default value. In number 1, positional notation follows named, which isn't allowed, and in number 3, the separators should be commas.
Put(23; 5; 10);
No, number 3 is illegal because in a call, the parameters are always separated with commas, not semicolons.