Procedures and Functions
Procedures and Functions: Key Ideas
Key Ideas
- Procedures = void method
- Function = non-void method
- Function return value cannot be ignored
- Nested: Declare in declaration section (one top-level per file)
- Function return type is part of signature (ie used for disambiguation, unlike Java)
- Strong typing: Type of returned value must match declaration and use
- Scope: define before use, to matching end
- Local and global variables (don't use global)
- Parameters: formal, actual
- Parameters: positional, keyword, default
- Parameter mode: in, out, in out
- Unitialized variables
- Recursion and declaration without bodies
- Expression functions
- Pre and post conditions
- Pragma Assertion_Policy(check);
Procedures and Functions, and Scope
Nesting Procedures and Functions
- Procedures and functions are like Java methods
- The main procedure can declare nested procedures and functions
- Example - a procedure that prints a table of factorials:
procedure demo_subroutines is -- The main procedure, in demo_subroutines.adb
function factorial(n: Natural) return Natural is
ans: Natural;
begin
if n = 0 then
ans := 1;
else
ans := n * factorial(n - 1);
end if;
return ans;
end factorial;
procedure print_table(start, stop: Natural) is
begin
for i in start .. stop loop
put(i);
put(factorial(i));
new_line;
end loop;
end print_table;
first, last: Natural;
begin
get(first);
get(last);
print_table(first, last);
end demo_subroutines;
Procedure print_table
and function factorial
are nested within top-level procedure demo_subroutines
- Top-level = not nested in anything
- One top-level procedure per file
print_table
and factorial
are declared within the
declaration section of demo_subroutines
Structure of print_table
and demo_subroutines
are the same
What is the main program and where does execution start?
What's in a Name
- Java:
- Ada (and more traditional names):
- Generic terms:
- Subroutine
- Routine
- Subprogram
- In Java, everything is a method, in C/C++, everything is a method.
- Ada distiguishes between procedures and functions
- Informally, we use these terms interchangably
- Formally, they are not all the same, as we will see.
Java Methods
- Java has 2 kinds of methods
- What are they?
-
-
- How are they used?
Java Methods
Java has 2 kinds of methods. What are they and how are they used?
- void - return type is void (ie no return type )
void printSquare(int i)
{
Sop(i*i);
}
...
printSquare(3));
- non-void - return type is non-void (eg int)
int square(int i)
{
return i * i);
}
...
Sop(square(3));
Ada Equivalents
- Procedure - like void method
procedure printSquare(i: Integer) is
begin
put(i * i);
end printSquare;
...
printSquare(3);
- Function - like non-void method
function square(i: Integer) return Natural is
begin
return i * i;
end square;
...
put(square(3));
Statements and Values
-
Procedure calls are statements, functions calls are values
- Procedure call = a statement
procedure printSquare(i: Integer) is
begin
put(i * i);
end printSquare;
...
j := j + 1;
printSquare(j);
do_something_else();
- Function call = a value
function square(i: Integer) return Natural is
begin
return i * i;
end square;
...
int j = 5;
put(j);
put(square(j));
put(square(j) + 1);
Order of Declarations: Define Before Use
- print_table calls factorial: therefore Factorial must be declared above print_table
- Define before use required
- Different from Java
- All class members (ie methods and fields visible throughout class)
- Question: But what about mutual recursion (ie a calls b and b calls a): See below
Scope
- Scope: where name is visible:
- Java scope rule:
- Fields and methods: throughout class
- Local variables: from declaration to enclosing brace
- Ada scope rule:
- Procedures and functions: From declaration to corresponding end statement
- Variables: From declaration to corresponding end statement
- In example above, what are the scopes of:
- factorial, print_table
- n, start, stop
- first, last
- ans
- demo_subroutines
- Scope and declare blocks: Declare ... begin ... end;
Local and Global Variables
- Local variable: Declared in a routine
- What are the locals in the above?
- Global variables: Visible inside a routine, but not declared in it
- Any global variables in the above?
- How could we create some? Don't do it!
Global Variables
- A declaration is global to a procedure if it is declared
outside that procedure and visible inside that procedure
- Thus, a variable that is declared above a procedure is known as
global variable (since it is global to that procedure)
- Example global variable: Don't do this:
procedure demo_global is
g: Integer; -- What is the scope of g? Don't do this!
procedure put_answer(ans: Integer) is
begin
put("g = " & g'img); -- Don't use globals
put("ans = " & ans'img);
end mpg;
begin -- demo
get(g);
put_answer (g * g);
end demo_global;
In this course we don't use global variables
- Except, perhaps, in very rare cases
- See below for why
Java Locals and Globals
- Java locals - very similar
- Java globals - different
Functions and Strong Typing
- Type of returned value must match declaration and use
function half_it (n : Integer) return Float is
half : Integer := n / 2;
begin
return half; -- Compile error
Return type must match use:
function half_it (n : Natural) return Float is
begin
return float(n) / 2.0;
end half_it;
half: Integer;
begin
half := half_it(10); -- Compile error: Expected Int found Float
Function Calls as Arguments
- Question: Can you have a function call as an argument to a function call?
- Answer: Sure
- Example:
put(reverse(to_upper(name)));
- compare to java:
System.out.print(name.toUpper().reverse());
Expression Functions
- Expression functions provide a light weight syntax for
functions that simply evaluate an expression
- Example:
function cube(n: Integer) return Integer is
(n * n * n);
function fac(n: Natural) return Natural is
(if n = 0 then 1 else n * fac(n - 1));
x: Integer := 11;
begin
put( cube(x) );
put( fac(x) );
No begin, return, end
Conditional expression evaluates to eithew 1
or to n * fac(n-1)
Terminology Revisited
- Procedure - used as a statement
- Function - used as a value
- void Method - used as a statement
- non-void Method - used as a value (or a statement!)
- Subroutine - procedure or function
- Routine - procedure or function
- Subprogram - procedure or function
- Formal parameter - defined in routine declaration
- Actual Parameter - use in routine call
Parameter Modes
Question: How Does Get Work? Answer: Modes
- The parameter to get is an out mode parameter
procedure get(item: out Integer) is ...
Parameter Modes - Syntax
- Three possible modes: in, out, in ou
- Modes precede type:
procedure one(p1: in integer) is ...
procedure two(p2: out integer) is ...
procedure thr(p3: in out integer) is ...
Default mode: in
procedure one(p1: integer) is ...
Parameters Modes Specify Information Flow
- Information flow from called routine's perspective:
- In Mode:
- Information flows into called routiine
- Called routine receives information
- Out Mode:
- Information flows out from called routiine
- Called routine returns information
- In Out Mode:
- Information flows into and out from called routiine
- Called routine receives and returns information, in same variable
Example with In Mode and Out Mode
- Example: sum of 1 .. n and sum of squares of 1 .. n:
procedure demo1 is
procedure calcs(n: in Natural; s, ssq: out Natural) is
begin
s := 0;
ssq := 0;
for i in 1 .. n loop
s := s + n;
ssq := ssq + n ** 2;
end loop;
end calcs;
x, sumUpToX, sumSqUpToX: Natural;
begin
get(x);
calcs(x, sumUpToX, sumSqUpToX);
put(sumUpToX);
put(sumSqUpToX);
end demo1;
Example with In Out Mode Parameters
- In Out mode: caller both receives and returns information in same parameter
- Simple Example:
procedure doubleMyParam(x: in out Integer) is
begin
x := x * 2;
end doubleMyParam;
...
i := 3;
doubleMyParam(i);
put(i); -- 6
Example - keep a running sum of i and i**2:
procedure demo2 is
procedure calcs(n: Natural; s, ssq: in out Natural) is
begin
s := s + n;
ssq := ssq + n ** 2;
end calcs;
x, sumUpToX, sumSqUpToX: Natural;
begin
get(x);
sumUpToX := 0; -- Statement a
sumSqUpToX := 0; -- Statement b
for k in 1 .. x loop
calcs(k, sumUpToX, sumSqUpToX);
end loop;
put(sumUpToX);
put(sumSqUpToX);
end demo2;
What happens if we move initialization (ie statements a and b) to
calcs
?
Another example: use procedures and functions to sum and count positives, etc.:
sumcountprocs.adb
and
prettified
- [This example uses arrays, which we haven't covered yet, but
it should still be understandable]
Modes: Copy In, Copy Out
Conceptually, we can think of the modes as follows:
- In Mode: Copy values in from caller to calling routine
- Out Mode: Copy value back from called routine to calling routine
Actual implementation may or may not involve copying.
In Mode Parameters are Constants
- An in mode formal parameter is a constant and cannot be modified:
procedure foo1(i: in Integer) is
begin
i := 3; -- error: Assignment to in mode parameter not allowed
Why: Improves error detection
Modifying an in mode param is inconsistent.
Use in out mode if need to modify
Parameter Modes Improve Error Detection
- An in mode formal parameter is a constant and cannot be modified:
procedure foo1(i: in Integer) is
begin
i := 3; -- error: Assignment to in mode parameter not allowed
Out mode parameters are uninitialized on entering the procedure:
procedure foo2(o: out Integer) is
begin
put(o); -- Warning: Formal parameter "o" read but never assigned
-- warning: "o" is used uninitialized in this function
-- gnatmake -gnatwall foo2.adb: gives maximum warnings
Out mode parameters must have variables as actual parameters
procedure foo3(o: out Integer) is
begin
o := 3;
end foo4;
procedure foo4(i: in Integer) is
c: constant Integer := 5;
x: Integer;
begin
-- Which of these compile?
foo3(x);
foo3(2);
foo3(c);
foo3(i);
Out mode parameters must have variables as actual parameters
procedure foo3(o: out Integer) is
begin
o := 3;
end foo4;
procedure foo4(i: in Integer) is
c: constant Integer := 5;
x: Integer;
begin
foo3(x); -- Okay
foo3(2); -- error: Actual for "o" must be a variable
foo3(c); -- error
foo3(i); -- error
Why Parameter Modes
- Increased flexibility:
- Return multiple values from a procedure
- Example:
procedure sum(i: in Natural; sum, sumsq: out natural)
- Reliability
- in mode params cannot accidentally be modified
- Better error checking:
- Compiler verifies that mode and code are consistent
- Redundancy
- Aid design process:
- Specify information flow: who provides information
- Contract between caller and called
- Kind of pre/post condition
Parameter Modes and Java
- Nothing comparable in Java
- But to what mode are final parameters similar?
- In Java, parameters can only be used to send information into a method
- In Java, parameters can not be used to send information back from a method
- Although if a parameter is an object, that object can be changed
What Modes are Not: Implementation Mechanism
- Question from C/C++ programmers: Are modes the same as passing by value/reference?
- Or, What's the relation between mode and pass by value/reference
- Briefly:
- Pass by value and pass by reference are parameter passing mechanisms
- Modes specify information flow, not implementation mechanism
- Modes are a contract
- The modes can be implemented using any mechanism
- Mode is chosen by compiler to make code efficient
- Generally: small values are copied in and out
- Generally: large values are passed by reference
Procedure vs Function
Functions are Values; Procedures are Statements
- Procedures calls are statements
-
put_line("Hello"); -- this is a statement
- Function calls represent values
-
y := 3.0 * sqrt(x);
-
sqrt(x)
returns a value used in an expression
- Functions cannot be used as procedures!
procedure foo is
function notSoGood(n) return integer is
begin
put(n);
return -n;
end notSoGood;
begin
notSoGood; -- Compile error!
This is not true in Java and C.
Java and C Can Ignore Expression Results
public class Func {
public static int calcPrintCube(int n){ // Non-void return type
System.out.println(n * n * n); // Side effect: change state of output
return n * n * n;
}
public static void main(String[] args) {
calcPrintCube(10); // function used as statement
int cube = calcPrintCube(20); // return value ignored
}
}
This is legal C:
int calcPrintCube(int n){ // Non-void return type
printf("%d", n * n * n); // Side effect: change state of output
return n * n * n;
}
void main(String args[]) {
calcPrintCube(10); // function used as statement
int cube = calcPrintCube(20); // return value ignored
}
Even this is legal C:
x = 0; // Do you
y - 0; // See the error
z = 0; // Error in this code?
Even this is legal C:
0;
In C, results of expressions can be freely ignored
Procedures or Functions: Which to use
- Question: is it better to use
p(x, y);
or y := p(x);
- If returning a single value:
- if value is non-structured (eg a number): probably better to use a function
- if value is structured (eg an array) probably better to use a procedure:
- it can be modified in place rather being copied on assignment
- individual elements can be modified
- If returning multiple values (eg
p(x, y, z);
to calculate y and z) from x:
- use a procedure
- or put y and z into a structured type (like an array or record - which we cover later)
Recursive Routines
Recursive Routines
- Main routine can call a recursive routine:
procedure call_recur is
function fac(n: Natural) is
begin
if n = 1 then
return 1;
else
return n * fac(n - 1);
end if;
end fac;
begin
put(fac(5));
end call_recur;
Mutually Recursive Routines
- Mutual recursion causes some problems:
- Mutual recursion example: A calls B and B calls A
procedure mutual_recursion is
procedure A is
begin
B;
end A;
procedure B is
begin
A;
end B;
begin
B; -- Infinite recursion!
end mutual_recursion;
This simple example causes infinite recursion
It also won't compile: why??
Mutually Recursive Routines - Problem solved
- Mutual recursion example: A calls B and B calls A
procedure mutual_recursion is
-- Procedure specification
-- Declare that B is a procedure with no parameters
procedure B;
procedure A is
begin
B;
end A;
procedure B is
begin
A;
end B;
begin
B; -- Infinite recursion!
end mutual_recursion;
This simple example causes infinite recursion
Some people like to declare the specifications of all routines at the beginning
Mutually Recursive Routines - Realistic Example
- A more realistic example: a solution to a compilation problem:
- This code evaluates arithmetic expressions, such as 2 * 3 and 2 * (3 + 4)
procedure handle_expressions is
-- Procedure specifications - all of them
procedure handle_parens;
procedure handle_expressions;
procedure handle_expressions is
begin
while has_more_symbols(expr) loop
if is_number(next_symbol) then
handle_number;
elsif is_operator(next_symbol) then
handle_operator;
elsif is_left_paren(next_symbol) then
handle_parens;
end if;
end loop;
end A;
procedure handle_parens is
begin
skip_left_paren;
handle_expressions;
skip_right_paren;
end B;
-- declare other routines here
begin
handle_expressions;
end infinite_recursion;
More on Globals
Global Variables - Why Not?
Problems with globals (Yannick Moy, Adacore)
- Invisible data flows between program parts
- Break encapsulation, introdue complex remote dependencies
- Lead to concurrency hazards
David Parnas - Module design: Maximize cohesion, minimize coupling
- Globals increase coupling between modules/routines
Phillip Koopman, CMU: "Global Variables Are Evil!"
Global Variables - Why Not?
- If no globals, routines can modify only parameters of mode
out
and in out
- Can ignore other routines
procedure demo is
miles, gals, mpg: Float; !!Don't use globals!!
function calc_mpg(m, g: Float) return Float is
mpg: Float := m / g;
begin
miles := m / g; -- A blunder!
return mpg;
end calc_mpg;
begin -- demo
get(miles);
get(gals);
mpg := calc_mpg(miles, gals);
put(miles);
put(gals);
put(mpg);
end demo;
What if no globals?
Global types and constants are not a problem
- Because they don't lead to the same kind of debugging problems
Modes Help Debugging, if no Global Variables
- Use modes to help find error
procedure p1(a: in Integer);
procedure p2(a: in out Integer);
procedure p3(a: in Integer);
...
x: Integer;
begin -- main
put(x); -- Assume value of x is correct here
p1(x);
p2(x);
p3(x);
put(x); -- What if value of x is not correct here?
...
Error must be in procedure p2
- Only true if no global variables
Uninitialized Variables
WARNING: Unitialized Variables
- Uninitialized variables will compile [but not a good idea, of
course]
procedure warning is
procedure p is
someLocal: Natural;
begin
put(someLocal);
end p;
var_for_main: Natural; -- Always put variables for main here
begin -- warning
p;
put(var_for_main);
end warning;
GNAT compiles and gives a warning
- What value is in someLocal?
Java would not allow this - it enforces definite assignment
Double piTimesDiam(double diam){
Double pi; // Forgot the value!
return pi * diam; // Error!
}
- One of the few places where Java is more reliable than Ada
- But there may be reasons to not initialize a variable