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)))