0% found this document useful (0 votes)
30 views12 pages

Recursion: 3.1 Calling A Function From Inside The Body of That Function

The document discusses recursive function definitions in programming languages. It begins by stating that the definition of the Σ function used in the previous chapter to define functions could result in circular definitions for recursively defined functions. It then explores different ways to define the Σ function to properly handle recursive function calls, including: defining approximations of programs with a limited number of recursive calls; defining a family of Σk functions with a parameter for the maximum number of nested calls; and viewing recursive definitions as fixed point equations.
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
30 views12 pages

Recursion: 3.1 Calling A Function From Inside The Body of That Function

The document discusses recursive function definitions in programming languages. It begins by stating that the definition of the Σ function used in the previous chapter to define functions could result in circular definitions for recursively defined functions. It then explores different ways to define the Σ function to properly handle recursive function calls, including: defining approximations of programs with a limited number of recursive calls; defining a family of Σk functions with a parameter for the maximum number of nested calls; and viewing recursive definitions as fixed point equations.
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 12

3

Recursion

3.1 Calling a Function from Inside the Body


of that Function
In the previous chapter, to define the Σ function for a statement of the form
f(t1 ,...,tn );, we have used the definition of the Σ function for the statement
p, that is the body of the function f. Was this definition correct, or could it be
circular?
This definition is clearly correct when the statement p does not contain
within itself calls to another function. That is to say, when the main program —
the main function — only calls functions that do not call functions themselves.
This definition is also correct when the program contains k function defi-
nitions f1 , ..., fk such that the body of the function fi contains only calls of
functions defined before, that is to say functions fj for j < i. Some languages,
like Fortran, only allow the calling of a function f in the body of a function
g if f is defined before g. In this case, the definition of the Σ function is by
induction on three values: the number of the function, the number of nested
while loops, and the size of the statement. Note that such an ordering of func-
tions always exists if these functions are introduced from the main program by
isolating parts of the program one after another: we isolate one part pk of the
main program, and then one part pk−1 of the main program or of the function
pk , ...
However, most programming languages allow functions to be written that
call themselves, or that call functions that call other functions that eventually

G. Dowek, Principles of Programming Languages, 47


Undergraduate Topics in Computer Science, DOI 10.1007/978-1-84882-032-6_3,

c Springer-Verlag London Limited 2009
48 3. Recursion

call the initial function.


This possibility is present in our definition of the Σ function for Java, since
the global environment G is global for the whole program: all functions in the
program can be called from within other functions. So, nothing prevents you
from calling a function f in the body of a function g, whether function f is
defined before g, after g, or if f and g are the same function.
We call recursive function definitions to be definitions of functions that
call themselves, or that call functions that call other functions, that eventually
call the initial function again. For recursive definitions, the definition of the Σ
function from the previous chapter, can be circular. For example, if f is the
function defined as follows
static void f (final int x) {f(x);}
then the definition of Σ(f(x);,e,m,G) uses the value of Σ(f(x);,e,m,G),
which is circular.
We must therefore find another method of defining the Σ function.

3.2 Recursive Definitions


3.2.1 Recursive Definitions and Circular Definitions

A more interesting example of recursive definition is that of the factorial func-


tion

static int fact (final int x) {


if (x == 0) return 1;
return x * fact(x - 1);}

To compute the factorial of the number 3, we must compute the factorial


of 2, which requires that we compute the factorial of 1, that requires the com-
putation of the factorial of 0. This value is 1. The factorial of 1 is then found
by multiplying 1 by this value, which gives 1. The factorial of 2 is found by
multiplying 2 by this value, which gives 2. And the factorial of 3 is found by
multiplying 3 by this value, which gives 6.
We sometimes say that a recursive definition is a definition that uses the
object which it is defining. This idea is absurd: circular definitions are just
as invalid in programming as they are elsewhere. If it were possible to use a
function inside its own definition, the factorial function could be defined very
simply
static int f (final int x) {return f(x);}
3.2 Recursive Definitions 49

and the definition of the function that multiplies its argument by 4 or that
squares it would be identical.

3.2.2 Recursive Definitions and Definitions by Induction

Another way to try to understand the definition of the function fact is to see
it as a definition by induction of the sequence un = n!: u0 = 1, un+1 = (n +
1) * un . Although this works for this function, that does not mean it will work
in general, for example for the function

static int f (final int n) {


if (n <= 1) return 1;
if (n % 2 == 0) return (1 + f(n / 2));
return 2 * f(n + 1);}

Indeed, the computation of the value of this function at 11 requires the


computation of its value at 12, which requires the computation of its value
at 6, which requires the computation of its value at 3, which requires the
computation of its value at 4, which requires the computation of its value at
2, which requires the computation of its value at 1. So, the computation of the
value of the function f at n does not require only the computation of its value
at n - 1, nor does it requires only the computation of its value at numbers
smaller than n, but also that of its value at numbers larger than n. However,
this definition is correct, and for any integer n, the computation of the value of
f at n gives a result.
The function below presents a more interesting example
static int ack (final int x, final int y) {
if (x == 0) return 2 * y;
if (y == 0) return 1;
return ack(x - 1,ack(x,y - 1));}
which always gives a result after a finite number of calls, but, as Wilhelm Ack-
ermann proved in a theorem in 1928, cannot be defined using nested definitions
by induction.

3.2.3 Recursive Definitions and Infinite Programs

When we have a recursive function definition, for example the definition of the
factorial, it is possible to transform this definition into another, non-recursive
one, by replacing the calls of the function fact in the body of the function
50 3. Recursion

fact by calls to another function fact1, identical to fact, but defined before
it
static int fact1 (final int x) {
if (x == 0) return 1;
return x * fact1(x - 1);}

static int fact (final int x) {


if (x == 0) return 1;
return x * fact1(x - 1);}
The definition of the function fact is no longer recursive, but that of function
fact1 is. We can, as well, replace the calls to function fact1 in the body
of function fact1 by calls to a function fact2, and so on. We can succeed,
in theory, in creating a non-recursive program, but it will be infinitely long.
Recursive definitions are, like the while loop, a means of expressing infinite
programs and, like the while loop, recursive definitions introduce the possibility
of non-termination.
Like in the case of while loops, we can introduce an imaginary expression,
giveup, and approach this infinite program with finite approximations. We do
this by replacing the nth copy of the function fact with the function giveup
and remove the subsequent functions that are no longer used.
Calculating the value of the nth approximation of program p consists of
trying to compute the value of program p by doing a maximum of n nested
recursive calls. If at the end of these n calls, the computation is not complete,
it is given up.
It isn’t too difficult to prove that for any state e, m, either the sequence
Σ(pn ,e,m,G) is never defined or it is defined beyond a certain point, and in
this case, it is constant over its domain. Remember that, in the second case,
the limit of the sequence is the value that it takes over its domain and that the
sequence has no limit if it is not defined.
We can now define the Σ function for (p,e,m,G)
Σ(p,e,m,G) = limn Σ(pn ,e,m,G).
To generalise this idea to multiple mutually recursive functions, we will
abandon the idea of copying out the recursive function calls, and instead in-
troduce a parameter for the number of nested function calls. We define the
family of functions Σk such that Σk (p,e,m,G) is the result of executing the
statement p, if the execution of this statement needs at most k nested function
calls. If at the end of k nested calls, the computation is not complete, then the
Σk function is not defined over (p,e,m,G).
The definition of the Σk functions is similar to the definition of the Σ func-
tion, except in the case of function calls. To define Σk (f(t1 ,...,tn );,e,m,G),
3.2 Recursive Definitions 51

we start by defining (v1 ,m1 ) = Θk (t1 ,e,m,G), (v2 ,m2 ) = Θk (t2 ,e,m1 ,G), ...,
(vn ,mn ) = Θk (tn ,e,mn−1 ,G), then e” and m” as we have done in the previous
chapter. Next, instead of considering the object Σk (p,e”,m”,G) we consider
the object Σk−1 (p,e”,m”,G). A notable exception occurs in the case where k
= 0, and in this case, the Σ0 function is not defined for this expression.
Once the family of Σk functions is defined, we define the Σ function
Σ(p,e,m,G) = limk Σk (p,e,m,G).

3.2.4 Recursive Definitions and Fixed Point Equations

The recursive definition of the function called fact


static int fact (final int x) {
if (x == 0) return 1;
return x * fact(x - 1);}
cannot be seen as a regular definition, where the name fact is associated with
an object. We can instead consider it as an equation where the variable is the
function fact. Indeed, the function fact is the unique function f that maps
natural numbers to natural numbers that satisfies the equation
f = (x → if (x == 0) then 1 else x * f(x - 1)).
This equation has the form f = G(f), so it is a fixed point equation.
Some fixed point equations, for example, the equation
f = (x → 1 + f(x))
that corresponds to the recursive definition
static int loop (final int x) {
return 1 + loop(x);}
do not have any solutions when dealing with total functions that map integers
to integers.
But, we have seen that recursively defined functions can be created that
do not terminate. It is then among the partial functions that map integers to
integers that we must find solutions. And, in this set, the fixed point equation
f = (x → 1 + f(x))
has one solution that is the function with an empty domain.
We can prove that, in general, any fixed point equation always has at least
one solution for the set of partial functions that map integers to integers. These
various functions can be ordered by inclusion of their graphs, and we can prove
that among these functions, one will be the smallest.
52 3. Recursion

There is therefore an alternative method of defining the Σ function. How-


ever, this alternative definition is equivalent to the definition of Section 3.2.3,
because this fixed point theorem produces a solution as the limit of a sequence
of functions.
Exercise 3.1
What are all the solutions of the following equation?

f = (x → f(x))

What is the least solution?


Let loop be the function defined below

static int loop (final int x) {


return loop(x);}

What is the value of the expression loop(4)?


Exercise 3.2
What are all the solutions of the following equation?

f = (x → 2 * f(x))

What is the least solution?


Let loop be the function defined below

static int loop (final int x) {


return 2 * loop(x);}

What is the value of the expression loop(4)?


Exercise 3.3
In the set of partial functions that map integers to integers, what are all
the solutions to the following equation?

f = (x → if (x == 0) then 1 else x * f(x - 1))

What is the least solution?


Imagine a data type that allows integers to be of any size. What does
the call fact(-1) return?
In the set of partial functions from an interval to itself, what are all the
solutions of this equation?
3.3 Caml 53

What does the call fact(-100) return if we define the function fact as
follows?

static byte fact (final byte x) {


if (x == 0) return 1;
return (byte) (x * fact((byte) (x - 1)));}

Why? And what does the call fact(-100) return if we define the function
fact as follows?

static double fact (final byte x) {


if (x == 0) return 1.0;
return x * fact((byte) (x - 1));}

Why?

3.3 Caml
In Caml, we execute the body of the function in the environment in which the
function was declared, extended by the declaration of its arguments. Because of
this fact, only functions declared before the function f are accessible within the
body of f, and the declaration
let fact x = if x = 0 then 1 else x * fact(x - 1)
in print_int (fact 6)
is invalid.
To be able to use the function fact within its own definition, you must use
a new construct let rec f x1 ... xn = t in p
let rec fact x = if x = 0 then 1 else x * fact(x - 1)
in print_int (fact 6)
and, at each function call, the definition of the function fact is then added to
the environment in which the body of the function is executed.
When two functions are mutually recursive, you cannot declare them as
follows
let rec even x = if x = 0 then true else odd(x - 1)
in let rec odd x = if x = 0 then false else even(x - 1)
in print_bool(even 7)
54 3. Recursion

because the environment in which the function even is executed does not contain
the function odd, so we have to use a special construct for mutually recursive
functions let rec f x1 ... xn = t and g y1 ... yp = u and ... For ex-
ample
let rec even x = if x = 0 then true else odd (x - 1)
and odd x = if x = 0 then false else even (x - 1)
in print_bool (even 7)

3.4 C
In C, as in Caml, the body of the function is executed in the environment in
which this function was declared, extended with the declaration of its arguments.
Because of this, only functions declared before a function f are accessible in the
body of f.
However, in order to allow for recursion, at each function call, the definition
of the function f is added to the environment in which the body of the function is
executed. This is exactly what happens in Caml with let rec, so the declaration
of a function in C is more like Caml’s let rec than it is like let. Therefore,
the following program
int fact (const int x) {
if (x == 0) return 1;
return x * fact(x - 1);}

int main () {
printf("%d\n",fact(6));
return 0;}
is valid, and returns 720.
When the definitions of several functions, for example two functions f and
g, are mutually recursive, we must start by prototyping the function g to allow
g to be called within the body of f. Prototyping a function defines its argument
types and its return type, and you can then define the actual function later in
the program.

int odd (const int);

int even (const int x) {


if (x == 0) return 1;
return odd(x - 1);}
3.5 Programming Without Assignment 55

int odd (const int x) {


if (x == 0) return 0;
return even(x - 1);}

int main () {
printf("%d\n",even(7));
return 0;}

3.5 Programming Without Assignment


Comparing the factorial function written with a loop
static int fact (final int x) {
int i;
int r;

r = 1;
for (i = 1; i <= x; i = i + 1) {r = r * i;}
return r;}
and recursively
static int fact (final int x) {
if (x == 0) return 1;
return x * fact(x - 1);}
we see that the first uses assignments: r = 1;, i = 1;, i = i + 1; and r = r
* i;, while the second does not. It is therefore possible to program the factorial
function without using assignments.
More generally, we can consider a sub-language of Java in which we remove
assignment. In this case, all variables can be declared as constant and, in the
definition of the Σ function, the memory state is always empty. Sequences and
loops in this case become useless. We are left with a shell of Java composed
of variable declarations, function calls, arithmetical and logical operations and
tests. This sub-language is called the functional core of Java. We can also define
the functional core of many programming languages.
Surprisingly, this functional core is just as powerful as Java as a whole.
For each expression t of Java, we associate the partial function that maps
the integer n to the value v such that (v,m’) = Θ(t,[x = n],[],G), and
for each statement p in Java we associate the partial function that maps the
integer n to the value v such that (return,v,m) = Σ(p,[x = n],[],G). A
56 3. Recursion

partial function f that maps integers to integers is called programmable in Java


if there exists an expression t or a statement p such that f is the function
associated with t or with p.
We can show that the set of programmable functions in Java, in the impera-
tive core of Java or in its functional core is identical: it is the set of computable
functions — see Exercise 1.12.
This result is true only because we allow for recursive functions. Loops
and recursion are therefore two essentially redundant constructs for producing
infinite programs, and each time you want to construct an infinite program,
you can choose to use either a loop or a recursive definition.
Exercise 3.4
Write the definition of the Σ function for the functional core of Java.

Exercise 3.5 (The Towers of Hanoi)


The towers of Hanoi is a game created by Édouard Lucas in 1883. It
has seven disks of different sizes distributed among three columns. At
the start, all the disks are on the left column, arranged from largest to
smallest, with the largest disk on the bottom.

The only movement allowed is to move a single disk from the top of one
column to the top of another column, with the condition that you can
never place a bigger disk on top of a smaller one. We write n -> n’ the
movement of a disk from column n to column n’. The goal of the game
is to move all of the disks from the left column to the right column.

Write a program that generates a solution to the game by outputting a


list of movements.
Hint: A variant of the game has only six disks. If one can solve the game
for six disks, how can one solve the game for seven disks?
3.5 Programming Without Assignment 57

Exercise 3.6 (The Koch Snowflake)


The Koch Snowflake, created in 1906 by Helge Von Koch, is an example
of a non-differentiable continuous curve. It’s also an example of a fractal
set, that is to say a set whose Hausdorff dimension is not a whole number.

This curve is defined as the limit of the sequence of curves where the
first curve is a segment

and where each element is obtained from the previous one, by dividing
each segment in 3, and replacing the middle segment with an equilat-
eral triangle with one side removed. The second iteration of the Koch
snowflake is thus

the third
58 3. Recursion

and the fourth

Write a program that draws the nth element of this sequence.

You might also like