Lecture 7 — 2015-09-23

Church encodings: functions all the way down

This lecture is written in literate Haskell; you can download the raw source.

We carried on valiantly with the Church numerals. I reproduce the earlier definitions here, for your convenience.

First, we have the equational rules for the lambda calculus.

(λ x. e) =α (λ y. e[y/x])
(λ x. e1) e2 = e1[e2/x]

The Church booleans and cond.

true = λ x. &lambda y. x
false = λ x. &lambda y. x
cond = λ p. λ t. λ e. p t e

The Church numerals and the successor function.

zero = λs. λz. z
one = λs. λz. s z
two = λs. λz. s (s z)
succ = λn. λs. λz. s (n s z)

We spent most of class working with the Church numerals.

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

plus = λn. λm. n succ m

times = λn. λm. n (m plus) zero

Church pairs

We’re not explicitly working with types, but Church pairs do have the following type.

type ChurchPair a b = (a -> b -> c) -> c

pair = λa. λb. c a b

A pair is a function that you call, and it will tell you what the two values are. We can write functions that get the first and second parts of a pair out:

fst = λp. p (λa b. a) snd = λp. p (λa b. b)

Using pairs, we can define the predecessor function, which computes n-1 for all n > 0. For 0, we go ahead and return 0.

We defined the function in class as follows:

pred = λn. λs. λz. snd (n (λp. pair (s (fst p)) (fst p)) (pair z z))

Though we also could have written it:

pred = λn. snd (n (λp. pair (succ (fst p)) (fst p)) (pair zero zero))

Recursion

The lambda calculus doesn’t have recursion built in, but we can do it anyway.

Consider the term ω = λx. x x. What does ω do when applied to itself? It reduces right away to itself! This is kind of like running forever—(ω *omega;) will happily churn away, looping on its own forever.

We can use a behavior like this get recursion in the lambda caluclus, by using the paradoxical Y combinator.

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

We have, for all expression e:

Y e =β (λx. e (x x)) (λx. e (x x))

If we reduce one more step by β, we get:

e (λx. e (x x)) (λx. e (x x))

Note that the later term is the same as Y e, so:

Y e = e (Y e) = e (e (Y e)) = … and so on infinitely.

Whoa. How do we use this? Here’s a definition for factorial, and a derivation for running it on 3.

ff = (λfact. λn. cond (isZero n) one (times n (fact (pred n))))

factorial = Y ff

A derivation

factorial 3

=substituting factorial

(Y ff) 3

=substituting Y

((λf. (λx -> f (x x)) (λx -> f (x x)))) ff) 3

=β

((λx. ff (x x)) (λx. ff (x x))) 3

=β

ff ((λx. ff (x x)) (λx. ff (x x))) 3

NB we’ve already seen that Y ff = ((λx. ff (x x)) (λx. ff (x x))), so

= ff (Y ff) 3

=β

cond (isZero 3) one (times 3 ((Y ff) (pred 3)))

=β

cond (3 (λ_. false) true) one (times 3 ((Y ff) (pred 3)))

=β

cond false one (times 3 ((Y ff) (pred 3)))

=β

(λp t e. p t e) false one (times 3 ((Y ff) (pred 3)))

=β

false one (times 3 ((Y ff) (pred 3)))

=

(λx y. y) one (times 3 ((Y ff) (pred 3)))

=β

(times 3 ((Y ff) (pred 3)))

= (not writing out pred, yeesh)

times 3 ((Y ff) 2)

= from before

times 3 ((ff (Y ff)) 2)

=β

times 3 (cond (isZero 2) 1 (times 2 ((Y ff) (pred 2))))

=β (speeding up a bit now!)

times 3 (times 2 ((Y ff) 1))

=β

times 3 (times 2 (cond (isZero 1) 1 (times 1 ((Y ff) (pred 1)))))

=β

times 3 (times 2 (times 1 ((Y ff) (pred 1))))

= (not writing out pred)

times 3 (times 2 (times 1 ((Y ff) 0)))

=β

times 3 (times 2 (times 1 (cond (isZero 0) 1 (times 0 ((Y ff) (pred 0))))))

=β

times 3 (times 2 (times 1 1))

=β

times 3 (times 2 1)

=β

times 3 2

=β

6

Whew!

Another way to use Y combinator

It might be hard to figure out how to use Y. Here’s a step-by-step recipe for how to go from Haskell code to code using the Y combinator.

factorial 0 = 1
factorial n = n * factorial (n-1)

First step: eliminate pattern matching, since the lambda calculus doesn’t have that. Let’s rewrite this to use an if statement.

factorial n = if n == 0 then 1 else n * factorial (n-1)

Second step: write explicit lambdas.

factorial = \n -> if n == 0 then 1 else n * factorial (n-1)

Third step: use Church encondings.

factorial = \n -> cond (isZero n) 1 (times n (factorial (pred n)))

Fourth step: eliminate explicit recursion using Y. To do this, we come up with a new name—here, fact—and add it as a new parameter to our function. We’ll use fact to do a recursive call, and pass our whole function to Y.

factorial = Y (\fact -> \n -> cond (isZero n) 1 (times n (fact (pred n))))

Fifth and final step: translate to lambda calculus syntax! This amounts to changing arrows to dots and giving the slashes little legs to make them lambdas.

factorial = Y (λfact. λn. cond (isZero n) 1 (times n (fact (pred n)))