recursive-functions
recursive-functions
Recursive Functions
rec.1 Introduction
cmp:rec:int: In order to develop a mathematical theory of computability, one has to, first of
sec
all, develop a model of computability. We now think of computability as the
kind of thing that computers do, and computers work with symbols. But at the
beginning of the development of theories of computability, the paradigmatic
example of computation was numerical computation. Mathematicians were
always interested in number-theoretic functions, i.e., functions f : Nn → N that
can be computed. So it is not surprising that at the beginning of the theory
of computability, it was such functions that were studied. The most familiar
examples of computable numerical functions, such as addition, multiplication,
exponentiation (of natural numbers) share an interesting feature: they can be
defined recursively. It is thus quite natural to attempt a general definition of
computable function on the basis of recursive definitions. Among the many
possible ways to define number-theoretic functions recursively, one particularly
simple pattern of definition here becomes central: so-called primitive recursion.
In addition to computable functions, we might be interested in computable
sets and relations. A set is computable if we can compute the answer to
whether or not a given number is an element of the set, and a relation is
computable iff we can compute whether or not a tuple ⟨n1 , . . . , nk ⟩ is an element
of the relation. By considering the characteristic function of a set or relation,
discussion of computable sets and relations can be subsumed under that of
computable functions. Thus we can define primitive recursive relations as well,
e.g., the relation “n evenly divides m” is a primitive recursive relation.
1
Primitive recursive functions—those that can be defined using just primitive
recursion—are not, however, the only computable number-theoretic functions.
Many generalizations of primitive recursion have been considered, but the most
powerful and widely-accepted additional way of computing functions is by un-
bounded search. This leads to the definition of partial recursive functions, and
a related definition to general recursive functions. General recursive functions
are computable and total, and the definition characterizes exactly the partial
recursive functions that happen to be total. Recursive functions can simulate
every other model of computation (Turing machines, lambda calculus, etc.)
and so represent one of the many accepted models of computation.
h(0) = 1
h(x + 1) = 2 · h(x)
If we already know how to multiply, then these equations give us the infor-
mation required for (a) and (b) above. By successively applying the second
equation, we get that
h(1) = 2 · h(0) = 2,
h(2) = 2 · h(1) = 2 · 2,
h(3) = 2 · h(2) = 2 · 2 · 2,
..
.
add(x, 0) = x
add(x, y + 1) = add(x, y) + 1
These equations specify the value of add for all x and y. To find add(2, 3),
for instance, we apply the defining equations for x = 2, using the first to find
add(2, 0) = 2, then using the second to successively find add(2, 1) = 2 + 1 = 3,
add(2, 2) = 3 + 1 = 4, add(2, 3) = 4 + 1 = 5.
In the definition of add we used + on the right-hand-side of the second
equation, but only to add 1. In other words, we used the successor function
succ(z) = z+1 and applied it to the previous value add(x, y) to define add(x, y+
1). So we can think of the recursive definition as given in terms of a single
function which we apply to the previous value. However, it doesn’t hurt—
and sometimes is necessary—to allow the function to depend not just on the
previous value but also on x and y. Consider:
mult(x, 0) = 0
mult(x, y + 1) = add(mult(x, y), x)
mult(2, 0) = 0
mult(2, 1) = mult(2, 0 + 1) = add(mult(2, 0), 2) = add(0, 2) = 2
mult(2, 2) = mult(2, 1 + 1) = add(mult(2, 1), 2) = add(2, 2) = 4
mult(2, 3) = mult(2, 2 + 1) = add(mult(2, 2), 2) = add(4, 2) = 6
In the case of add, we have k = 1 and f (x0 ) = x0 (the identity function), and
g(x0 , y, z) = z + 1 (the 3-place function that returns the successor of its third
argument):
add(x0 , 0) = f (x0 ) = x0
add(x0 , y + 1) = g(x0 , y, add(x0 , y)) = succ(add(x0 , y))
In the case of mult, we have f (x0 ) = 0 (the constant function always return-
ing 0) and g(x0 , y, z) = add(z, x0 ) (the 3-place function that returns the sum
of its last and first argument):
mult(x0 , 0) = f (x0 ) = 0
mult(x0 , y + 1) = g(x0 , y, mult(x0 , y)) = add(mult(x0 , y), x0 )
rec.3 Composition
If f and g are two one-place functions of natural numbers, we can compose cmp:rec:com:
sec
them: h(x) = g(f (x)). The new function h(x) is then defined by composition
from the functions f and g. We’d like to generalize this to functions of more
than one argument.
Here’s one way of doing this: suppose f is a k-place function, and g0 , . . . ,
gk−1 are k functions which are all n-place. Then we can define a new n-place
function h as follows:
The functions Pik are called projection functions: Pin is an n-place function.
Then g can be defined by
h(x, y) = f (P02 (x, y), g(P02 (x, y), P02 (x, y), P12 (x, y)), P12 (x, y)).
l(x, y) = g(P02 (x, y), P02 (x, y), P12 (x, y)),
for each natural number n and i < n, we will include among the primitive
recursive functions the function zero(x) = 0.
Definition rec.3. The set of primitive recursive functions is the set of func-
tions from Nn to N, defined inductively by the following clauses:
explanation Put more concisely, the set of primitive recursive functions is the smallest
set containing zero, succ, and the projection functions Pjn , and which is closed
under composition and primitive recursion.
Another way of describing the set of primitive recursive functions is by
defining it in terms of “stages.” Let S0 denote the set of starting functions:
zero, succ, and the projections. These are the primitive recursive functions of
stage 0. Once a stage Si has been defined, let Si+1 be the set of all functions
you get by applying a single instance of composition or primitive recursion to
functions already in Si . Then
[
S= Si
i∈N
add(x0 , 0) = f (x0 ) = x0
add(x0 , y + 1) = g(x0 , y, add(x0 , y)) = succ(add(x0 , y))
So add is primitive recursive provided f and g are as well. f (x0 ) = x0 = P01 (x0 ),
and the projection functions count as primitive recursive, so f is primitive
recursive. The function g is the three-place function g(x0 , y, z) defined by
g(x0 , y, z) = succ(z).
This does not yet tell us that g is primitive recursive, since g and succ are not
quite the same function: succ is one-place, and g has to be three-place. But
we can define g “officially” by composition as
Since succ and P23 count as primitive recursive functions, g does as well, since
it can be defined by composition from primitive recursive functions.
Proof. Exercise.
Problem rec.1. Prove Proposition rec.5 by showing that the primitive recur-
sive definition of mult can be put into the form required by Definition rec.1
and showing that the corresponding functions f and g are primitive recursive.
Example rec.6. Here’s our very first example of a primitive recursive defini-
tion:
h(0) = 1
h(y + 1) = 2 · h(y).
This function cannot fit into the form required by Definition rec.1, since k = 0.
The definition also involves the constants 1 and 2. To get around the first
problem, let’s introduce a dummy argument and define the function h′ :
h′ (x0 , 0) = f (x0 ) = 1
h′ (x0 , y + 1) = g(x0 , y, h′ (x0 , y)) = 2 · h′ (x0 , y).
The function f (x0 ) = 1 can be defined from succ and zero by composition:
f (x0 ) = succ(zero(x0 )). The function g can be defined by composition from
g ′ (z) = 2 · z and projections:
and
Here the role of f is played by P01 , and the role of g is played by succ(P23 (x0 , y, z)),
which is assigned the notation Comp1,3 [succ, P23 ] as it is the result of defining a
function by composition from the 1-ary function succ and the 3-ary function P23 .
With this setup, we can denote the addition function by
Having these notations sometimes proves useful, e.g., when enumerating prim-
itive recursive functions.
Problem rec.2. Give the complete primitive recursive notation for mult.
h(⃗x, 0) = f (⃗x)
h(⃗x, y + 1) = g(⃗x, y, h(⃗x, y))
and suppose the functions f and g are computable. (We use ⃗x to abbreviate x0 ,
. . . , xk−1 .) Then h(⃗x, 0) can obviously be computed, since it is just f (⃗x) which
we assume is computable. h(⃗x, 1) can then also be computed, since 1 = 0 + 1
and so h(⃗x, 1) is just
Thus, to compute h(⃗x, y) in general, successively compute h(⃗x, 0), h(⃗x, 1), . . . ,
until we reach h(⃗x, y).
Thus, a primitive recursive definition yields a new computable function if
the functions f and g are computable. Composition of functions also results in
a computable function if the functions f and gi are computable.
Since the basic functions zero, succ, and Pin are computable, and com-
position and primitive recursion yield computable functions from computable
functions, this means that every primitive recursive function is computable.
exp(x, 0) = 1
exp(x, y + 1) = mult(x, exp(x, y)).
exp(x, 0) = f (x)
exp(x, y + 1) = g(x, y, exp(x, y)).
where
f (x) = succ(zero(x)) = 1
g(x, y, z) = mult(P03 (x, y, z), P23 (x, y, z)) = x · z
is primitive recursive.
pred(0) = 0 and
pred(y + 1) = y.
This is almost a primitive recursive definition. It does not, strictly speaking, fit
into the pattern of definition by primitive recursion, since that pattern requires
at least one extra argument x. It is also odd in that it does not actually use
pred(y) in the definition of pred(y + 1). But we can first define pred′ (x, y) by
and then define pred from it by composition, e.g., as pred(x) = pred′ (zero(x), P01 (x)).
fac(0) = 1
fac(y + 1) = fac(y) · (y + 1).
where g(x, y, z) = mult(P23 (x, y, z), succ(P13 (x, y, z))) and then let
From now on we’ll be a bit more laissez-faire and not give the official definitions
by composition and primitive recursion.
is primitive recursive.
Proof. We have:
x −̇ 0 = x
x −̇ (y + 1) = pred(x −̇ y)
max(x, y) = x + (y −̇ x).
cmp:rec:exa: Proposition rec.13. The minimum of x and y, min(x, y), is primitive re-
prop:min-pr
cursive.
Proof. Exercise.
is primitive recursive.
Problem rec.5. Show that integer division d(x, y) = ⌊x/y⌋ (i.e., division,
where you disregard everything after the decimal point) is primitive recursive.
When y = 0, we stipulate d(x, y) = 0. Give an explicit definition of d using
primitive recursion and composition.
Proof. For example, finite sums are defined recursively by the equations
g(⃗x, 0) = f (⃗x, 0)
g(⃗x, y + 1) = g(⃗x, y) + f (⃗x, y + 1).
is primitive recursive.
Proof. Suppose P (⃗x) and Q(⃗x) are primitive recursive, i.e., their characteristic
functions χP and χQ are. We have to show that the characteristic functions of
¬P (⃗x), etc., are also primitive recursive.
(
0 if χP (⃗x) = 1
χ¬P (⃗x) =
1 otherwise
Problem rec.6. Show that the three place relation x ≡ y mod n (congruence
modulo n) is primitive recursive.
cond(0, y, z) = y,
cond(x + 1, y, z) = z.
One can use this to justify definitions of primitive recursive functions by cases
from primitive recursive relations:
Proposition rec.18. If g0 (⃗x), . . . , gm (⃗x) are primitive recursive functions,
and R0 (⃗x), . . . , Rm−1 (⃗x) are primitive recursive relations, then the function f
defined by
g0 (⃗x) if R0 (⃗x)
g (⃗
x ) if R1 (⃗x) and not R0 (⃗x)
1
..
f (⃗x) = .
g
m−1 (⃗x) if Rm−1 (⃗x) and none of the previous hold
gm (⃗x) otherwise
Proof. Note than there can be no z < 0 such that R(⃗x, z) since there is no
z < 0 at all. So mR (⃗x, 0) = 0.
In case the bound is of the form y + 1 we have three cases:
1. There is a z < y such that R(⃗x, z), in which case mR (⃗x, y+1) = mR (⃗x, y).
mR (⃗x, 0) = 0
mR (⃗x, y) if mR (⃗x, y) ̸= y
mR (⃗x, y + 1) = y if mR (⃗x, y) = y and R(⃗x, y)
y+1 otherwise.
rec.10 Primes
cmp:rec:pri: Bounded quantification and bounded minimization provide us with a good
sec
deal of machinery to show that natural functions and relations are primitive
recursive. For example, consider the relation “x divides y”, written x | y. The
x | y ⇔ (∃z ≤ y) (x · z) = y.
Prime(x) ⇔ x ≥ 2 ∧ (∀y ≤ x) (y | x → y = 1 ∨ y = x)
p(0) = 2
p(x + 1) = nextPrime(p(x))
Since nextPrime(x) is the least y such that y > x and y is prime, it can be
easily computed by unbounded search. But it can also be defined by bounded
minimization, thanks to a result due to Euclid: there is always a prime number
between x and x ! + 1.
This shows, that nextPrime(x) and hence p(x) are (not just computable but)
primitive recursive.
(If you’re curious, here’s a quick proof of Euclid’s theorem. Suppose pn
is the largest prime ≤ x and consider the product p = p0 · p1 · · · · · pn of all
primes ≤ x. Either p + 1 is prime or there is a prime between x and p + 1.
Why? Suppose p + 1 is not prime. Then some prime number q | p + 1 where
q < p + 1. None of the primes ≤ x divide p + 1. (By definition of p, each of the
primes pi ≤ x divides p, i.e., with remainder 0. So, each of the primes pi ≤ x
divides p + 1 with remainder 1, and so pi ∤ p + 1.) Hence, q is a prime > x and
< p + 1. And p ≤ x !, so there is a prime > x and ≤ x ! + 1.)
Proposition rec.20. The function len(s), which returns the length of the se-
quence s, is primitive recursive.
We can use bounded minimization, since there is only one i that satisfies R(s, i)
when s is a code of a sequence, and if i exists it is less than s itself.
Proposition rec.21. The function append(s, a), which returns the result of
appending a to the sequence s, is primitive recursive.
Proposition rec.22. The function element(s, i), which returns the ith ele-
ment of s (where the initial element is called the 0th), or 0 if i is greater than
or equal to the length of s, is primitive recursive.
Proof. Note that a is the ith element of s iff pa+1 i is the largest power of pi
that divides s, i.e., pa+1
i | s but p a+2
i ∤ s. So:
(
0 if i ≥ len(s)
element(s, i) = a+2
(min a < s) (pi ∤ s) otherwise.
Instead of using the official names for the functions defined above, we intro-
duce a more compact notation. We will use (s)i instead of element(s, i), and
⟨s0 , . . . , sk ⟩ to abbreviate
append(append(. . . append(Λ, s0 ) . . . ), sk ).
Proposition rec.23. The function concat(s, t), which concatenates two se-
quences, is primitive recursive.
hconcat(s, t, 0) = s
hconcat(s, t, n + 1) = append(hconcat(s, t, n), (t)n )
then the numeric code of the sequence s described above is at most sequenceBound(x, k).
Having such a bound on sequences gives us a way of defining new functions
using bounded search. For example, we can define concat using bounded search.
All we need to do is write down a primitive recursive specification of the object
(number of the concatenated sequence) we are looking for, and a bound on how
far to look. The following works:
sconcat(⟨s0 , . . . , sk ⟩) = s0 ⌢ . . . ⌢ sk .
Problem rec.10. Show that there is a primitive recursive function tail(s) with
the property that
tail(Λ) = 0 and
tail(⟨s0 , . . . , sk ⟩) = ⟨s1 , . . . , sk ⟩.
cmp:rec:seq: Proposition rec.24. The function subseq(s, i, n) which returns the subse-
prop:subseq
quence of s of length n beginning at the ith element, is primitive recursive.
Proof. Exercise.
rec.12 Trees
cmp:rec:tre: Sometimes it is useful to represent trees as natural numbers, just like we can
sec
represent sequences by numbers and properties of and operations on them by
primitive recursive relations and functions on their codes. We’ll use sequences
and their codes to do this. A tree can be either a single node (possibly with a
label) or else a node (possibly with a label) connected to a number of subtrees.
The node is called the root of the tree, and the subtrees it is connected to its
immediate subtrees.
We code trees recursively as a sequence ⟨k, d1 , . . . , dk ⟩, where k is the num-
ber of immediate subtrees and d1 , . . . , dk the codes of the immediate subtrees.
h(⃗x, 0) = f (⃗x)
h(⃗x, y + 1) = g(⃗x, y, ⟨h(⃗x, 0), . . . , h(⃗x, y)⟩).
with the understanding that the last argument to g is just the empty sequence
when y is 0. In either formulation, the idea is that in computing the “successor
step,” the function h can make use of the entire sequence of values computed
so far. This is known as a course-of-values recursion. For a particular example,
it can be used to justify the following type of definition:
(
g(⃗x, y, h(⃗x, k(⃗x, y))) if k(⃗x, y) < y
h(⃗x, y) =
f (⃗x) otherwise
You should think about how to obtain these functions using ordinary prim-
itive recursion. One final version of primitive recursion is more flexible in that
one is allowed to change the parameters (side values) along the way:
h(⃗x, 0) = f (⃗x)
h(⃗x, y + 1) = g(⃗x, y, h(k(⃗x), y))
h(x) = g(x, x) + 1
= fx (x) + 1.
g0 (x) = x+1
gn+1 (x) = gnx (x)
You can confirm that each function gn is primitive recursive. Each successive
function grows much faster than the one before; g1 (x) is equal to 2x, g2 (x) is
equal to 2x · x, and g3 (x) grows roughly like an exponential stack of x 2’s. The
Ackermann–Péter function is essentially the function G(x) = gx (x), and one
can show that this grows faster than any primitive recursive function.
Let us return to the issue of enumerating the primitive recursive functions.
Remember that we have assigned symbolic notations to each primitive recursive
function; so it suffices to enumerate notations. We can assign a natural number
#(F ) to each notation F , recursively, as follows:
#(0) = ⟨0⟩
#(S) = ⟨1⟩
#(Pin ) = ⟨2, n, i⟩
#(Compk,l [H, G0 , . . . , Gk−1 ]) = ⟨3, k, l, #(H), #(G0 ), . . . , #(Gk−1 )⟩
#(Recl [G, H]) = ⟨4, l, #(G), #(H)⟩
Here we are using the fact that every sequence of numbers can be viewed as
a natural number, using the codes from the last section. The upshot is that
every code is assigned a natural number. Of course, some sequences (and
hence some numbers) do not correspond to notations; but we can let fi be the
unary primitive recursive function with notation coded as i, if i codes such a
notation; and the constant 0 function otherwise. The net result is that we have
an explicit way of enumerating the unary primitive recursive functions.
(In fact, some functions, like the constant zero function, will appear more
than once on the list. This is not just an artifact of our coding, but also a result
of the fact that the constant zero function has more than one notation. We
will later see that one can not computably avoid these repetitions; for example,
there is no computable function that decides whether or not a given notation
represents the constant zero function.)
We can now take the function g(x, y) to be given by fx (y), where fx refers
to the enumeration we have just described. How do we know that g(x, y) is
program (say, in Java or C++) that does this; and now we can appeal to the
Church–Turing thesis, which says that anything that, intuitively, is computable
can be computed by a Turing machine.
Of course, a more direct way to show that g(x, y) is computable is to de-
scribe a Turing machine that computes it, explicitly. This would, in particular,
avoid the Church–Turing thesis and appeals to intuition. Soon we will have
built up enough machinery to show that g(x, y) is computable, appealing to a
model of computation that can be simulated on a Turing machine: namely, the
recursive functions.
2. Add something to the definition, so that some new partial functions are
included.
The first is easy. As before, we will start with zero, successor, and projec-
tions, and close under composition and primitive recursion. The only difference
is that we have to modify the definitions of composition and primitive recur-
sion to allow for the possibility that some of the terms in the definition are not
defined. If f and g are partial functions, we will write f (x) ↓ to mean that f
is defined at x, i.e., x is in the domain of f ; and f (x) ↑ to mean the opposite,
for every x.
The proof of the normal form theorem is involved, but the basic idea is explanation
simple. Every partial recursive function has an index e, intuitively, a number
coding its program or definition. If f (x) ↓, the computation can be recorded
systematically and coded by some number s, and the fact that s codes the
computation of f on input x can be checked primitive recursively using only x
and the definition e. Consequently, the relation T , “the function with index e
has a computation for input x, and s codes this computation,” is primitive
recursive. Given the full record of the computation s, the “upshot” of s is the
value of f (x), and it can be obtained from s primitive recursively as well.
The normal form theorem shows that only a single unbounded search is
required for the definition of any partial recursive function. Basically, we can
search through all numbers until we find one that codes a computation of the
function with index e for input x. We can use the numbers e as “names”
of partial recursive functions, and write φe for the function f defined by the
equation in the theorem. Note that any partial recursive function can have
more than one index—in fact, every partial recursive function has infinitely
many indices.
is not computable.
In the context of partial recursive functions, the role of the specification of a
program may be played by the index e given in Kleene’s normal form theorem.
If f is a partial recursive function, any e for which the equation in the normal
form theorem holds, is an index of f . Given a number e, the normal form
theorem states that
φe (x) ≃ U (µs T (e, x, s))
Note that h(e, x) = 0 if φe (x) ↑, but also when e is not the index of a partial
recursive function at all.
Theorem rec.29. The halting function h is not partial recursive. cmp:rec:hlt:
thm:halting-problem
Photo Credits
27
Bibliography
28