The Lambda Calculus
The Lambda Calculus
Models of Computation
Turing machines (1936)
inspired by paper and pencil
Static Analysis
Turing Machines
A memory tape and a controlling program:
Static Analysis
Cellular Automata
Living cells that interact with neighbors:
No popular languages...
Static Analysis
Lambda Calculus
Functional expressions being rewritten:
fx.f(f(f(f(f(f(f(f(f(fx))))))))) fx.f(f((x.f(f(f(fx)))) fx.f(f((fx.f(f(f(fx)))) fx.f(f((x.(fx.f(f(f(fx)))) fx.f(f((fx.(fx.f(f(f(fx)))) fx.f((x.f((fx.(fx.f(f(f(fx)))) fx.f((fx.f((fx.(fx.f(f(f(fx)))) (nfx.f(nfx)) (nfx.f(nfx))((nfx.f(nfx)) (y.(nfx.f(nfx))((nfx.f(nfx)) (xy.(nfx.f(nfx))((nfx.f(nfx)) (z.(xy.(nfx.f(nfx))((nfx.f(nfx)) (hz.hzz)(xy.(nfx.f(nfx)) (g.g(xy.(nfx.f(nfx))((nfx.f(nfx)) (f.(g.gf(fx.f(f(f(fx)))))(hz.hzz)) (f(f(f(fx)))))) ((mnfx.mf(nfx))xy)))zz) ((mnfx.mf(nfx))xy))) f((fx.f(f(f(fx))))fx))fx))fx) f((fx.f(f(f(fx))))fx))fx)) (fx.(fx.f(f(f(fx)))) ((nfx.(fx.f(f(f(fx))))f(nfx)) ((mnfx.mf(nfx))(fx.f(f(f(fx)))) ((mnfx.mf(nfx))(fx.f(f(f(fx))))y))) ((mnfx.mf(nfx))xy)))(fx.f(f(f(fx)))) f((fx.f(f(f(fx))))fx))fx))x) (xy.(nfx.f(nfx))((nfx.f(nfx) f((fx.f(f(f(fx))))fx))x)) ((nfx.f(nfx))((mnfx.mf(nfx))xy))) ((x.f(f(f(fx))))x))) ((fx.f(f(f(fx))))fx))) f((fx.f(f(f(fx))))fx))) (fx.f((fx.(fx.f(f(f(fx)))) f((fx.f(f(f(fx))))fx))) (fx.f(f(f(fx)))))) (fx.f(f(f(fx)))) (fx.f(f(f(fx)))))(hz.hzz) ((mnfx.mf(nfx))xy))) f((fx.f(f(f(fx))))fx))fx))
Static Analysis
Static Analysis
Syntactic Conventions
We use currying as syntactic sugar: x1x2x3...xk.E x1.x2.x3.... xk.E Application is left-associative: E1E2E3...Ek (...((E1E2)E3)...Ek)
Static Analysis
x.y.xy(x.yx)x
A variable that is not bound is called free:
x.y.xy(x.yz)x
Static Analysis
Alpha Conversion
Bound variables may be renamed: x.x a.a f.g.x.f(gx) g.f.z.g(fz) which does not change the meaning of the term Free variables cannot be renamed: x.xyz a.ayz
Static Analysis
10
10
Beta Reduction
The computational engine of the calculus Reductions correspond to single steps A redex is an opportunity to reduce: (x.E1)E2 The reduction substitues all free occurrences of the variable x in E1 with a copy of E2: E1[x\E2]
Static Analysis
11
11
Substitution
find redex
(x.yxzx(x.yx)x)(abc)
reduce
(yxzx(x.yx)x)[x\abc]
find free occurrences
(yxzx(x.yx)x)[x\abc]
substitute
y(abc)z(abc)(x.yx)(abc)
Static Analysis
12
12
Static Analysis
13
13
Computing by Reduction
Intuitively, beta reduction models computations by simplifying expressions: (xy.x(2*y))(z.z+1)5 (y.(z.z+1)(2*y))5 (z.z+1)(2*5) 2*5+1 11 However, we don't have numbers and such yet...
Static Analysis
14
14
Variable Capture
A simple reduction: (xa.xa)(x.xa) a.(x.xa)a a.aa Now, first alpha convert xa.xa to xb.xb: (xb.xb)(x.xa) b.(x.xa)b b.ba The results are different, but alpha conversion should not change the meaning???
Static Analysis
15
15
Static Analysis
16
16
Static Analysis
17
17
Reduction Strategies
More that one redex may be available: (fgx.f(gx))(a.a)(b.bb)c (gx.(a.a)(gx))(b.bb)c ...
Static Analysis
18
18
Confluence
Fortunately, all strategies can only reach the same normal form:
(fgx.f(gx))(a.a)(b.bb)c (gx.(a.a)(gx))(b.bb)c (gx.gx)(b.bb)c (x.(b.bb)x)c (x.xx)c cc (fgx.f(gx))(a.a)(b.bb)c (gx.(a.a)(gx))(b.bb)c (x.(a.a)((b.bb)x))c (a.a)((b.bb)c) (a.a)(cc) cc
Static Analysis
19
19
Call-By-Name Reduction
Not all strategies are equally good at terminating But one strategy always terminates if possible: call-by-name reduction selects the left-most available redex in the term This is also known as lazy evaluation (in Haskell)
Static Analysis
20
20
Call-By-Value Reduction
Call-by-name is often rather slow, since arguments may be evaluated several times for each use Call-by-value is an alternative that tries to evaluate the arguments only once This is known as eager evaluation in ML, Scheme, Java, and most other languages
Static Analysis
21
21
Static Analysis
22
22
Terms are written with \ in place of Variables with more that one character are written as:
\<foo>.\<bar>.<bar><bar><foo>
Static Analysis
23
23
Abstract Values
Normal forms are values, but they mean nothing in particular by themselves:
x.xx abc xy.yyyyyyx
Static Analysis
24
24
Encoding Values
Interesting values must be encoded in the model:
1 0 1 1 0 0 1 0 0 0 1 0 1 1 1 1 1 0 0 0 1 1 1 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 1 1 0 1 1 1 0 0 0 1 1 0 0 0 0 1 1 1 0 1 0 0 0 1 1 1 45615 "AX" true
Static Analysis
25
25
Church Numerals
An encoding of natural numbers:
0 fx.x 1 fx.fx 2 fx.f(fx) 3 fx.f(f(fx)) ...
26
26
27
27
Static Analysis
28
28
Arithmetic Operations
Boolean operations:
and xy.xy(xy.y) or xy.x(xy.y)(xy.x) not x.x(xy.y)(xy.x)
Integer operations:
pred nfx.n(gh.h(gf))(u.x)(u.u) plus mnfx.mf(nfx) mult mnf.n(mf)
Static Analysis
29
29
(literals) (variables) (parentheses) (integers) (integers) (booleans) (pairs) (streams) (conditionals) (function call) (locals) (functions) (recursive functions)
30
30
Static Analysis
31
31
A Higher-Order Function
let f(x,y) = succ(succ(plus(x,y))) in let g(h,z) = h(z,z) in g(f,4) The result is 10
Static Analysis
32
32
Stream Programming
letrec inf(n) = cons(n,inf(succ(n))) in head(tail(tail(inf(7)))) The result is 9
Static Analysis
33
33
A Fibonacci Stream
letrec fib(x,y) = (let z = plus(x,y) in cons(z,fib(y,z))) in letrec take(n,s) = if (iszero(n)) 0 else pair(head(s),take(pred(n),tail(s))) in take(6,fib(0,1)) The result is: pair(1,pair(2,pair(3,pair(5,pair(8,pair(13,0))))))
Static Analysis
34
34
35
35
Static Analysis
36
36
Static Analysis
37
37
is compiled into:
(f.f87)(YF)
38
38
39
39
Static Analysis
40
40
and both things are bad for recursion We must delay some reductions:
if (E1) E2 else E3 E1 (a.E2 a)(b.E3b) fixed-point operator: g.(x.g(y.xxy)) (x.g(y.xxy))
Static Analysis
41
41
42
42
A program is compiled into a Lambda term The term is reduced to a normal form, which may turn out to be pure nonsense: mult(true,pair(1,2)) f.f(fx.f(fx)) ???
Static Analysis
43
43
27
Static Analysis
44
44
0: runtime error 1: integer 2: boolean 3: pair 4: stream 5: function with 1 argument 6: function with 2 arguments 7: function with 3 arguments ...
45
45
46
46
Type Checking
Fun contains many "wild" terms: let a = succ(first(1)) in let b = a(3) in b(cons(true,87)) The compiler should detect such type errors:
errors are caught earlier in the development process compile-time type checking avoids run-time type tags
Static Analysis
47
47
Undecidability
It is not possible to decide if a program will cause a runtime type error during execution:
type correct
type errors
Static Analysis
48
48
Static Analysis
49
49
Static Analysis
50
50
Type Examples
fac: fun(int, int) fib: fun(int,int,stream(int))
let f(x,y) = succ(succ(plus(x,y))) in let g(h,z) = h(z,z) in g(f,4)
f: g:
fun(int,int,int) fun(fun(int,int,int),int,int)
inf: fun(int,stream(int))
Static Analysis
51
51
Type Checking
Assign a type variable [[E]] to every expression E Generate type constraints relating these variables to each other Solve the constraints using some algorithm The generated constraints are solvable The program is statically type correct No type errors will occur at runtime
Static Analysis
52
52
Symbol Checking
We must first check that all used identifiers are also declared Fun has ordinary scope rules: letrec fib(x,y) = (let z = plus(x,y) in cons(z,fib(y,z))) in letrec take(n,s) = if (iszero(n)) 0 else pair(head(z),take(pred(n),tail(s))) in take(6,fib(0,1))
Static Analysis
53
53
Unique Identifiers
We then rename all identifiers to be unique: let f(f) = succ(f) in let f(f) = pair(f,let f = 17 in f) in f(10)
54
54
Generating Constraints
iszero(E)
"The argument must be of type int and the result is of type boolean"
55
55
[[k]] = int [[true]] = boolean [[false]] = boolean no constraints [[E]] = [[succ(E)]] = int [[E]] = [[pred(E)]] = int [[E]] = int [[iszero(E)]] = boolean [[plus(E1,E2)]] = [[E1]] = [[E2]] = int [[mult(E1,E2)]] = [[E1]] = [[E2]] = int [[E]] = [[not(E)]] = boolean [[and(E1,E2)]] = [[E1]] = [[E2]] = boolean
56
56
[[or(E1,E2)]] = [[E1]] = [[E2]] = boolean [[pair(E1,E2)]] = pair([[E1]],[[E2]]) [[E]] = pair([[first(E)]],) [[E]] = pair(,[[second(E)]]) [[cons(E1,E2)]] = stream([[E1]]) = [[E2]] [[E]] = stream([[head(E)]]) [[E]] = [[tail(E)]] = stream() [[E1]] = boolean [[E2]] = [[E3]] = [[if (E1) E2 else E3]] [[id]] = fun([[E1]],...,[[Ek]],[[id(E1,...,Ek)]])
57
57
Static Analysis
58
58
59
59
General Terms
Constructor symbols:
0-ary: a, b, c 1-ary: d, e 2-ary: f, g, h 3-ary: i, j, k
Terms:
a d(a) h(a,g(d(a),b))
Static Analysis
60
60
61
61
Unification Errors
Constructor error: d(X) = e(X) Arity error: a = a(X)
Static Analysis
62
62
Static Analysis
63
63
Static Analysis
64
64
Static Analysis
65
65
Static Analysis
66
66
Static Analysis
67
67
Static Analysis
68
68
69
69
Static Analysis
70
70
Static Analysis
71
71
Slack
A type checker will unfairly reject some programs:
type correct statically type correct
slack
Static Analysis
72
72
Concrete Slack
letrec fac2(n,foo) = if (iszero(n)) 1 else mult(n,foo(pred(n),foo)) in fac2(6,fac2) letrec f(n) = pair(n,f(pred(n))) in first(second(second(f(7))))
Static Analysis
73
73
Fighting Slack
Make the type checker a bit more clever:
An eternal struggle...
Static Analysis
74
74
Regular Types
int | boolean | pair(1, 2) | stream() | fun(1, ..., k, )
We have assumed finite types But we can accept more program if allow also infinite, but regular types
Static Analysis
75
75
Regular Terms
Infinite but (eventually) repeating:
e(e(e(e(e(e(...)))))) d(a,d(a,d(a, ...))) f(f(f(f(...),f(...)),f(f(...),f(...))),f(f(f(...),f(...)),f(f(...),f(...))))
A non-regular term:
f(a,f(d(a),f(d(d(a)),f(d(d(d(a))),...))))
Static Analysis
76
76
Regular Unification
Paterson and Wegman (1976) The unification problem can be solved in O(n(n)) (n) is the inverse Ackermann function:
smallest k such that n Ack(k,k) this is never bigger than 5 for any real value of n
Static Analysis
77
77
Static Analysis
78
78
Polymorphism
We still cannot type check a polymorphic function: let f(x) = pair(x,0) in pair(f(42),f(true)) *** unification constructor error int boolean The function f must have two different types
Static Analysis
79
79
80
80
81
81
82
82
Static Analysis
83
83
Static Analysis
84
84
Static Analysis
85
85
Static Analysis
86
86
Static Analysis
87
87