In tail recursion, each recursive call is a tail call: its result is returned directly, with no suspended addition or other work. A compiler can then reuse an activation record, giving constant stack use. This is a possible optimization, not a guarantee made by the source syntax. The direct Fibonacci number procedure in part (a) must retain information while waiting to add two recursive results.
Extend the Fibonacci numbers with and use the suggested quantity . For ,while . This gives the Oberon procedures
PROCEDURE G(n, a, b: INTEGER): INTEGER;
VAR result: INTEGER;
BEGIN
IF n = 1 THEN
result := a
ELSE
result := G(n-1, a+b, a)
END;
RETURN result
END G;
PROCEDURE FibTail(n: INTEGER): INTEGER;
BEGIN
RETURN G(n, 1, 0)
END FibTail;The assignment of the recursive result does not perform arithmetic after the call; it merely passes that result to
RETURN, so the call is in tail recursion. A dialect permitting an immediate RETURN G(...) in the branch expresses the same control flow. The decreasing argument proves algorithm termination, and the displayed identity proves correctness by mathematical induction. ThusThere are recursive steps, giving arithmetic operations. Stack use is with tail call elimination and without it. With unbounded integers, the growing Fibonacci numbers also make arithmetic costs grow; the linear bound counts arithmetic operations.Replace the recursive transfer in part (b) by simultaneous state updates, using a temporary variable to preserve the old
a:PROCEDURE FibIter(n: INTEGER): INTEGER;
VAR k, a, b, oldA: INTEGER;
BEGIN
k := n; a := 1; b := 0;
WHILE k > 1 DO
oldA := a;
a := a + b;
b := oldA;
k := k - 1
END;
RETURN a
END FibIter;For original input , a loop invariant isIt holds initially because . Each iteration preserves it by the identity from part (b). The loop variant is , a nonnegative integer that decreases by one whenever the guard holds. On exit , so the loop invariant gives , exactly the returned answer. The procedure uses arithmetic operations and stored integer variables, without depending on compiler treatment of tail calls.
A directly self-recursive procedure can be replaced by a simple loop when every recursive call is a tail call. This is tail recursion: no operation, including an update or cleanup action, remains to be performed after the call returns. The loop tests the same cases, performs the same nonrecursive work, and replaces the current parameters by the next call's arguments. Argument values must be computed before assignments if their expressions depend on old parameter values; by-reference bindings also need preserving.
Each recursive call is a tail call: nothing remains to be computed after it returns. Replace it by an update of the retained bounds:
function Select(i; var a; N): T
require 1 <= i <= N
target := i-1
l := 0
r := N
while true do
Partition(a,l,r,m,n)
if target < m then
r := m
else if target >= n then
l := n
else
return a[target]The loop invariant retains the target order statistic in with . Each nonreturning iteration strictly decreases , because is nonempty. This proves termination and replaces recursive stack space by auxiliary space, apart from the pivot routine's own storage.