Typed Lambda Calculus Top More lambda calculus for foundations of computing

More lambda calculus for foundations of computing

Church and Kleene came up with the above functions pretty quickly, but predecessor was a real sticking point. Kleene worked on it for a long time with no success, but then one day at the dentists, while having his teeth worked on, he figured out a clever way of solving the problem.

The difficulty is that one cannot remove occurrences of s from the representation of n. Thus somehow, one must figure out a way to compute n-1 from n. The way Kleene came up with was to build up a series of pairs of the form <n-1,n> so that n-1 could be removed from the pair at the end.

Start by defining PZero = <0,0> = Pair 0 0. Now define PSucc = λ n. Pair (snd n) (Succ (snd n)). Thus PSucc ignores the first item of the pair, moves the second to the first position and then puts the successor of the second element into the second position. It is easy to see that PSucc PZero = <0,1>, and, in general, n PSucc PZero = <n-1,n> for n > 0.

It is now easy to define the predecessor function:

Pred = λ n. fst (n PSucc PZero).

Thus Pred n = fst (n PSucc PZero) = fst <n-1,n> = n-1 for n > 0. Note that Pred n = n. This is OK, because there is no predecessor for 0 in the natural numbers.

Once Kleene figured out predecessor, he had no trouble going on from there. For example, one handy function is isZero, which determines whether its argument is 0:

isZero = λ n. n (λ x. false) true.

Notice that if n is zero, the function λ x. false is never applied to true, so the final result is true, but if n is greater than 0, then the function will be applied to true at least once, so the final result will be false.

It is worth noting here that not every computation in the untyped lambda calculus terminates. In particular, let Ω = λ x. (x x). Then (Ω Ω) represents an infinite loop, as

(Ω Ω) = (λ x. (x x)) Ω
= (Ω Ω)
= ....

Now many more functions could be defined directly in the lambda calculus, but it would be much more useful if we could define functions recursively, as in ML, as it is easier to design programs that way. However, there is a big problem with defining functions recursively in the lambda calculus: names!

Let's see how it works in ML. To define factorial we could write (I'm avoiding pattern matching here):

    val fact = fn n => if n = 0 then 1 else n * fact (n-1)
I've used the expanded definition of functions here to make it closer to the lambda calculus. Trying to emulate this in the lambda calculus would look like
λ n. cond (isZero n) 1 (Mult n (fact (Pred n)))

That looks promising except for one thing: the occurrence of fact as a free variable in the formula. Of course, what we'd like is to name the entire function fact and write:

fact = λ n. cond (isZero n) 1 (Mult n (fact (Pred n)))

but the lambda calculus has no naming facility, so we must find another way to accomplish the same thing.

We can see how to do this if we represent the function above in a slightly different way. Because we don't know what fact is, let's abstract it away:

λ fact. λ n. cond (isZero n) 1 (Mult n (fact (Pred n)))

For simplicity, let's let F stand for this function. (I'm not really giving it a name as part of the lambda calculus, I just want a short way to refer to it - we can expand it out whenever we like.) Now notice that up above, the definition of fact in lambda calculus was such that

fact = F(fact)

When we have a function f and value x such that f(x) = x, we call x a fixed point of the function. Examples from mathematics are that 0 and 1 are fixed points of the squaring function, -1, 0, and 1 are fixed points of the cubing function, etc.

So, if we can find a fixed point for F in the lambda calculus, then we will have a definition of the factorial function. Remarkably, there is a fixed point operator in the lambda calculus, which, when applied to any function f in the lambda calculus, returns a fixed point of that function.

There are actually several fixed point functions, but we'll use one of the simplest, written Y:

Y = λf. (λx. f (x x)) (λx. f (x x))

The claim is that for all functions g, Y g will be a fixed point of g. Let's compute and see:

Y g = λf. (λx. f (x x)) (λx. f (x x)) g
= (λx. g (x x)) (λx. g (x x))
= g ((λx. g (x x)) (λx. g (x x)))
= g (Y g)

Somewhat surprisingly, if we let x0 = Y g, then we have shown that x0 = g(x0), so x0 is a fixed point of g (even though the computation seems to go backwards!). We have thus shown that any function definable in the lambda calculus has a fixed point, also definable in the lambda calculus.

Let's go back to our original problem involving recursive definitions. In order to define the factorial function, we defined the (higher-order function)

F = λ fact. λ n. cond (isZero n) 1 (Mult n (fact (Pred n)))

and noted that the factorial function that we wanted was defined such that

fact = F(fact)

Now, however, we know how to compute that fixed point. We will let fact = Y F.

This may seem like a real stretch to you, so let's actually try computing some values of fact and make sure that we get the values we are expecting:

fact 0 = (F (fact)) 0 because fact is a fixed point of F
= cond (isZero 0) 1 (Mult 0 (fact (Pred 0))) expanding F
= 1 by the definition of cond

fact 1

= (F (fact)) 1 because fact is a fixed point of F
= cond (isZero 1) 1 (Mult 1 (fact (Pred 1))) expanding F
= Mult 1 (fact (Pred 1)) by the definition of cond
= fact 0 by the definition of Mult and Pred
= 1 by the above calculation

fact 2

= (F (fact)) 2 because fact is a fixed point of F
= cond (isZero 2) 1 (Mult 2 (fact (Pred 2))) expanding F
= Mult 2 (fact (Pred 2)) by the definition of cond
= Mult 2 (fact 1) by the definition of Pred
= Mult 2 1 by the above calculation
= 2 by the definition of Mult

We could continue in the same vein for as long as we like. Essentially each computation of fact n unwinds to a definition involving Mult n (fact (Pred n)), which then gets further expanded until we get down to fact 0, when we can do the arithmetic to finish the answer.

Similar definitions may be given for other recursive functions, and from there it is not difficult (though it is tedious) to show that any function in ML can be represented by a function in the lambda calculus. I will ask you to define several of these as part of your homework this week.


Typed Lambda CalculusTopMore lambda calculus for foundations of computing