Professional Documents
Culture Documents
Lambda
Lambda
Lambda
-calculus
-terms. A -term is either: A variable x, y , z , . . . x.M MN (M ) Examples: x Conventions: Application associates to the left, so that M N P should be understood as (M N ) P . The body of a function x.M goes as far to the right as possible. Thus, x.y.x should be understood as x.(y.x). The scope of x in x.M is all of M . A variable x is free in a -term if some use of x does not appear in the scope of a x. Examples: y is free in x.y ; x is free in y.(x (x.x)); z is not free in z.(x.z ) Substitution. Substitution: M [x N ] x[ x N ] = N y [x N ] = y (if x = y ) (where x is a variable and M a -term) (where M, N are -terms) (where M is a -term) x.x y.(x.x) x.(x (y.y ))
(M1 M2 )[x N ] = M1 [x N ] M2 [x N ] (x.M )[x N ] = x.M (y.M )[x N ] = y.(M [x N ]) (if y is not free in N )
x.M = y.(M [x y ]) if y is not free in M . (There is a question what happens when the substitution rules do not apply. For instance, (x.y )[y x] is not dened, because x, the variable parameter in x.y , is free in x, the term being substituted for y . One possibility is to return an error. Another possibility is to apply renaming to the problematic variable parameter to get (z.y )[y x] which by the substitution rules above gives you z.x. The latter approach is often called capture-avoiding substitution.) Reduction Rules. Reduction (or evaluation): M N (x.M ) N M [x N ] M P N P P M P N x.M x.N (if M N ) (if M N ) (if M N )
A term of the form (x.M ) N is called a redex. It is the one place where a reduction does any work. Examples:
((x.(y.x)) z1 ) z2 = =
(y.x)[x z1 ] z2 (y.z1 ) z2 z1 [y z2 ] z1
The -term ((x.(y.x)) z1 ) z2 could be written more simply as (x.y.x) z1 z2 using the conventions above. Similarly, ((x.(y.y )) (z.z )) (x.(y.x)) could be written (x.y.y ) (z.z ) (x.y.x) I will use the conventions fully from now on. I will generally expand out substitutions immediately in reductions, skipping the intermediate step of showing what the substitution looks like. A -term is in normal form if there is no applicable reduction. Not every -term eventually reduces to a normal form:
(x.x x) (x.x x) (x.x x) (x. x) (x.x x) (x. x) . . . There can be more than one redex in a -term, meaning that there may be more than one applicable reduction. For instance, ((x.x) (y.x)) ((x.y.x) z1 z2 ). A property of the calculus is that all the ways to reduce a term to a normal form yield the same normal form (up to renaming of bound variables). This is called the Church-Rosser property. It says that the order in which we perform reductions to reach a normal form is not important. Encoding Booleans. Even though the lambda-calculus only has variables and functions, we can encode traditional data types within in. Boolean values encoding (` a la Church):
true = x.y.x false = x.y.y In what sense are these encodings of Boolean values? Booleans are useful because they allow you to select one branch or the other of a conditional expression. The trick is that when B reduces to either true or false, then B M N reduces either to M or to N , respectively: 3
If B true, then B M N = while if B false, then B M N = We can also dene an explicit if expression: if = c.x.y.c x y so that B M N could also be written if B M N . We can dene logical operators: and = m.n.m n m or = m.n.m m n not = m.x.y.m y x Thus: and true false = = (m.n.m n m) true false (n.true n true) false true false true (x.y.x) false true (y.false) true false false M N (x.y.y ) M N (y.y ) N N true M N (x.y.x) M N (y.M ) N M
not false = = =
Encoding Natural Numbers. 0 = f.x.x 1 = f.x.f x 2 = f.x.f (f x) 3 = f.x.f (f (f x)) 4 = ... In general, natural number n is encoded as f.x.f n x. Successor operations: succ = n.f.x.(n f ) (f x)
plus 1 2 = = =
(m.n.f.x.(m f ) (n f x)) 1 2 (n.f.x.(1 f ) (n f x)) 2 f.x.(1 f ) (2 f x) f.x.((f.x.f x) f ) ((f.x.f (f x)) f x) f.x.(x.f x) ((f.x.f (f x)) f x) f.x.f ((f.x.f (f x)) f x) f.x.f ((x.f (f x)) x) f.x.f (f (f x)) 3
times 2 3 = = =
(m.n.f.x.m (n f ) x) 2 3 (n.f.x.2 (n f ) x) 3 f.x.2 (3 f ) x f.x.(f.x.f (f x)) ((f.x.f (f (f x))) f ) x f.x.(f.x.f (f x)) (x.f (f (f x))) x f.x.(x.(x.f (f (f x))) ((x.f (f (f x))) x)) x f.x.(x.(x.f (f (f x))) (f (f (f x)))) x f.x.(x.f (f (f (f (f (f x)))))) x f.x.f (f (f (f (f (f x))))) 6
iszero? 0 =
(n.n (x.false) true) (f.x.x) (f.x.x) (x.false) true (x.x) true true
iszero? 2 =
(n.n (x.false) true) (f.x.f (f x)) (f.x.f (f x)) (x.false) true (x.(x.false) ((x.false) x)) true (x.false) ((x.false) true) false
More dicult is dening a predecessor function, taking a nonzero natural number n and returning n 1. There are several ways of dening such a function; here is probably the simplest: pred = n.f.x.n (g.h.h (g f )) (u.x) (u.u)
pred 2 = (n.f.x.n (g.h.h (g f )) (u.x) (u.u)) (f.x.f (f x)) f.x.(f.x.f (f x)) (g.h.h (g f )) (u.x) (u.u) f.x.(x.(g.h.h (g f )) ((g.h.h (g f )) x)) (u.x) (u.u) 6
f.x.(g.h.h (g f )) ((g.h.h (g f )) (u.x)) (u.u) f.x.(g.h.h (g f )) (h.h ((u.x) f )) (u.u) f.x.(g.h.h (g f )) (h.h x) (u.u) f.x.(h.h ((h.h x) f )) (u.u) f.x.(h.h (f x)) (u.u) f.x.(u.u) (f x) f.x.f x 1
Note that pred 0 is just 0: pred 0 = = Encoding Pairs. pair = x.y.z.z x y rst = p.p (x.y.x) second = p.p (x.y.y ) (n.f.x.n (g.h.h (g f )) (u.x) (u.u)) (f.x.x) f.x.(f.x.x) (g.h.h (g f )) (u.x) (u.u) f.x.(x.x) (u.x) (u.u) f.x.(u.x) (u.u) f.x.x 0
rst (pair 1 2) =
(p.p (x.y.x)) ((x.y.z.z x y ) 1 2) (p.p (x.y.x)) ((y.z.z 1 y ) 2) (p.p (x.y.x)) (z.z 1 2) (z.z 1 2) (x.y.x) (x.y.x) 1 2 (y.1) 2 1
Recursion. With conditionals and basic data types, we are very close to having a Turingcomplete programming language (that is, one that can simulate Turing machines). All that is missing is a way to do iteration: loops. It turns out we can write recursive functions in the -calculus, which gives us loops. Consider factorial. Intuitively, we would like to dene fact by fact = n.(iszero? n) 1 (times n (fact (pred n))) but this is not a valid denition, since the right-hand side refers to the term being dened. It is really an equation, the same way x = 3x is an equation. Consider that equation, x = 3x. Dene F (t) = 3t. Then, a solution of x = 3x is really a xed-point of F , namely, a value t0 for which F (t0 ) = t0 . And F has only one xed-point, namely t0 = 0, which gives us the one solution to x = 3x, namely x = 0. Similarly, if we dene Ffact = f.n.(iszero? n) 1 (times n (f (pred n))) then we see that the denition that were looking for is a xed point of Ffact , namely, a term f such that Ffact f = f . Indeed, if we have such a term, then:
f3= = = = =
Ffact f 3 (f.n.(iszero? n) 1 (times n (f (pred n)))) f 3 (n.(iszero? n) 1 (times n (f (pred n)))) 3 (iszero? 3) 1 (times 3 (f (pred 3))) times 3 (f (pred 3)) times 3 (f 2) times 3 (Ffact f 2) times 3 (times 2 (f 1)) times 3 (times 2 (Ffact f 1)) times 3 (times 2 (times 1 (f 1))) times 3 (times 2 (times 1 (Ffact f 1))) times 3 (times 2 (times 1 1)) 8
(The notation indicates that one or more reductions have taken place.) Thus, what we need is a way to nd xed points in the -calculus. The following function does just that: Y = f.(x.f (x x)) (x.f (x x)) Y F gives us a xed point of F . (Technically, Y F reduces in one step to a xed-point of F ):
YF =
(f.(x.f (x x)) (x.f (x x))) F (x.F (x x)) (x.F (x x)) F ((x.F (x x)) (x.F (x x))) F (F ((x.F (x x)) (x.F (x x)))) ...
So indeed, (x.F (x x)) (x.F (x x)) is a xed point of F . We can use Y to dene our factorial function: fact = Y Ffact and we can check: fact 3 = = = = = = = Y Ffact 3 (f.(x.f (x x)) (x.f (x x))) Ffact 3 (x.Ffact (x x)) (x.Ffact (x x)) 3 Ffact ((x.Ffact (x x)) (x.Ffact (x x))) 3 Ffact fact 3 (f.n.(iszero? n) 1 (times n (f (pred n)))) fact 3 (n.(iszero? n) 1 (times n (fact (pred n)))) 3 (iszero? 3) 1 (times 3 (fact (pred 3))) times 3 (fact (pred 3)) times 3 (fact 2) times 3 (Ffact fact 2) times 3 (times 2 (fact 1)) times 3 (times 2 (Ffact fact 1)) times 3 (times 2 (times 1 (fact 1))) times 3 (times 2 (times 1 (Ffact fact 1))) 9
where fact = (x.Ffact (x x)) (x.Ffact (x x)) is the xed point of Ffact , such that fact = Ffact fact .
10