Fall 98, CSE 520: Lecture 2 (Sep 1)


Expressiveness of the Lambda Calculus

This lecture will focus on the computational power of the Lambda Calculus. We will show that the Lambda Calculus is complete, in the sense that it is able to express any computable function. This result was proved by Kleene in 1936.

Church's numerals

In order to show the above result, we first need to encode the natutal numbers as lambda terms. The following is a possible representation:
[n] = \x y. xny
where, for generic lambda terms M and N, MnN represents the term M(...(M(M N))...), i.e. the application of M to N repeated n times. These terms [n] are called Church's numerals.

Lambda-definability

A partial function f : Nk -> N is lambda-definable if there exists a lambda term [f] such that, whenever f(n1,...,nk) is defined, we have
[f][n1]...[nk] = [f(n1,...,nk)]
The equality here is the "lambda-convertibility" (see previous lecture), i.e. the equality in the theory of Lambda Calculus. Note that [f] is curried, i.e. it takes its arguments one by one.

Recursive Functions

We recall here one of the possible caracterization of the class of recursive functions.

Initial functions

Composition

Given f : Nk -> N and g1,...,gk : Nh -> N, the function f o (g1,...,gk) : Nh -> N (composition of f and g1,...,gk) is the function defined as
(f o (g1,...,gk)) (n1,...,nh) = f(g1(n1,...,nh),..., gk(n1,...,nh))

Primitive recursion

Given g : Nk -> N and h : Nk+2 -> N, the function f : Nk+1 -> N is defined by primitive recursion from g and h if the following equalities hold:
f(0,n1,...,nk) = g(n1,...,nk)
f(n+1,n1,...,nk) = h(n, f(n,n1,...,nk), n1,...,nk)

Minimalization

Given g : Nk+1 -> N, the function f : Nk -> N is defined by minimalization from g if the following holds:
f(n1,...,nk) = mu n. (g(n,n1,...,nk) = 0)
where mu is the minimalization operator: mu n. P(n) returns the least natural number n such that P(n) holds.

Note that f could be defined by general iteration, i.e. using a while loop of the form

n := 0; while (g(n,n1,...,nk) <> 0) do n := n+1;

Definition The set of Recursive Functions is the smallest set which contains the initial functions and is closed w.r.t. composition, primitive recursion, and minimalization.

If we exclude minimalization from this construction, we get the set of Primitive Recursive Fuctions, which are all total. Minimalization is the only operator which introduces non-termination, i.e. partiality.

Lambda-definability of the recursive functions

Theorem (Kleene, 1936) Every recursive function is lambda-definable.

Proof

We give here a proof simpler (more tailored to computer scientists) than the original proof by Kleene.

Encoding of the initial functions

It is easy to see that these encodings satisfy the definition of lambda-definability. For instance:
[S][n] = (\z.\x y. x(z x y))[n] = \x y. x([n] x y) = \x y. x(xn y) = [n+1]

Encoding of composition

[f o (g1,...,gk)] = \x1...xh. [f]([g1]x1...xh) ... ([gk]x1...xh)

Encoding of primitive recursion

We consider here the simple case of k = 0. The equations defining f in this case are:
f(0) = g    (constant)
f(n+1) = h(n,f(n))
In ML we could define this function as:
fun f(n) = if n = 0 then g else h(n,f(n))
or equivalently (remember that the syntax for \n.E(n) in ML is fn n => E(n)) :
val rec f = fn n => if n = 0 then g else h(n-1,f(n-1))
The meaning of this declaration is that f is a fixpoint of the following functional F:
F = fn f' => fn n => if n = 0 then g else h(n-1,f'(n-1))
Namely, f satisfies the equation
f = F(f)

The fixpoint operator

We need a lambda term which is able to compute the fixpoint of a function, i.e. a term Y such that for every M it satisfies:
Y M = M (Y M)
One possible definition of such Y is
Y = \x. (\y. x(y y)) (\y. x(y y))
In fact, Y M = (\y. M(y y)) (\y. M(y y)) = M(\y. M(y y)) (\y. M(y y)) = M (Y M).

We can therefore define

f = Y [F]
It remains to define [F]. To this purpose, we need to define the booleans and the if-then-else operator, the test is_zero, and the predecessor.

Booleans and the if-then-else operator

Let us encode the booleans as follows:
[True] = \x y. x
[False] = \x y. y
Note that we have:
[True] M N = M
[False] M N = N
Therefore it is sufficient to define
[if_then_else] = \x y z . x y z
In fact we will have: [if_then_else] C M N = M if C = [True], and [if_then_else] C M N = N if C = [False].

Test is_zero

We can define
[is_zero] = \x. x ([True][False])[True]
In fact, [n] M N = MnN, hence [is_zero][n] = [n]([True][False])[True] = ([True][False])n[True] and the latter is equal (lambda-convertible) to [True] if n=0, and to [False] otherwise.

Predecessor

We leave the definition of predecessor as an exercise.

Encoding of minimalization

Given the definition of f as
f(n1,...,nk) = mu n. (g(n,n1,...,nk) = 0)
we can rewrite it in ML as follows:
val rec h = fn n => fn n1 => ... fn nk => if g(n,n1,...,nk) = 0 then n else h n n1...nk
val f(n1,...,nk) = h 0 n1...nk
We apply now the method seen for primitive recursion (which works for every kind of recursive definition): Define H as
H = fn h' => fn n => fn n1 => ... fn nk => if g(n,n1,...,nk) = 0 then n else h' n n1...nk
Then we can define
[h] = Y [H]
[f] = [h][0]

Note: In the classical proof of the Klenee's theorem, the encoding of primitive recursion does not use the fixpoint operator. The fixpoint operator in fact is necessary for expressing minimalization and general recursion, but not for primitive recursion. The classic encoding of primitive recursion is done by using "definite iteration" (i.e. iteration repeated a number of times known a-priori) and pairs to store the partial result of the computation. We give here the translation in ML and we leave the translation from ML to the Lambda Calculus as an exercise.

Let f be defined by primitive recursion as:

f(0) = g   
f(n+1) = h(n,f(n))
then f can be defined in pseudo-ML by the following declarations:
fun phi(m,n) = ( m+1 , h(m,n) )
fun f(n) = (phin (0,g)) Second
Where Second is the projection on the second element of a pair, i.e. (a,b) Second = b. To see that the above is a correct encoding, note that phi(m,f(m)) = (m+1,f(m+1)), hence phin (0,g) = (n,f(n)).

In order to translate from ML to the Lambda Calculus, we need to encode pairs and the projection operators. (We leave this as an exercise.) As for definite iteration, needed for representing phin, note that in the Lambda Calculus we have MnN = [n] M N.