0% found this document useful (0 votes)
58 views

The Lambda Calculus

The document provides an overview of the Lambda Calculus, which originated in 1936 as a model of computation inspired by mathematical functions. It describes the basic syntax and reduction rules of the Lambda Calculus, including bound and free variables, alpha conversion, beta reduction, and strategies like call-by-name and call-by-value reduction. It also discusses how numeric and boolean values can be encoded in the Lambda Calculus using techniques like Church numerals. Finally, it presents an example programming language called Fun that compiles to the Lambda Calculus.
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
58 views

The Lambda Calculus

The document provides an overview of the Lambda Calculus, which originated in 1936 as a model of computation inspired by mathematical functions. It describes the basic syntax and reduction rules of the Lambda Calculus, including bound and free variables, alpha conversion, beta reduction, and strategies like call-by-name and call-by-value reduction. It also discusses how numeric and boolean values can be encoded in the Lambda Calculus using techniques like Church numerals. Finally, it presents an example programming language called Fun that compiles to the Lambda Calculus.
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 87

The Lambda Calculus

Static Analysis 2009

Michael I. Schwartzbach Computer Science, University of Aarhus

Models of Computation
Turing machines (1936)
inspired by paper and pencil

Lambda calculus (1936)


inspired by mathematical functions

Cellular automata (1940)


inspired by life forms

All such can emulate each other...

Static Analysis

Turing Machines
A memory tape and a controlling program:

Popular languages: C++, Java, C#, ...

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

Popular languages: Haskell, ML, Scheme...

Static Analysis

The Role of The Lambda Calculus


The origin of many fundamental programming language concepts The inner workings of all functional languages The "white lab mouse" of language research:
start with the lambda calculus extend it with some novel features experiment with the resulting language

Just plain fun (if you love programming)

Static Analysis

Syntax of the Lambda Calculus


Only three grammar rules: E x.E | E1 E2 | x Example terms: x.x f.g.x.f(gx) x.xyz
Static Analysis

(function definition) (function application) (variable reference)

(identity function) (function composition) (???)


7

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

Bound and Free Variables


A variable is bound to the nearest declaration:

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

Example Beta Reduction


(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

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

Avoiding Variable Capture


The problem occurs when a term with a free variable is copied into a term where that variable is already bound The solution is to implicitly alpha convert the bound variable into something harmless: (xa.xa)(x.xa) b.(x.xa)b b.ba

Static Analysis

16

16

Normal Forms and Termination


A term with no more redexes is a normal form Normal forms correspond to the results of our computations (values in Haskell) Not all terms have normal forms: (x.xx)(x.xx) (x.xx)(x.xx) ... (x.xxx)(x.xxx) (x.xxx)(x.xxx)(x.xxx) ...

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

Which one should we reduce?

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

CBN vs. CBV


CBV sometimes fails to terminate:
(x.a)((y.yy)(y.yy)) CBN a (x.a)((y.yy)(y.yy)) CBV (x.a)((y.yy)(y.yy)) ...

CBV is often faster (but not always)

Static Analysis

22

22

The Lambda Tool


java Lambda -evaluate [-cbn|-cbv] [-<limit>] [-trace] [-full] [-stats] -evaluate: Lambda normalization -cbn: call-by-name reduction -cbv: call-by-value reductions -<limit>: max number of reductions -trace: print after every reduction -full: print with all parentheses -stats: print alpha and beta statistics

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

But similarly, bit patterns in memory mean nothing by themselves:


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

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

Programming is: Information Representation Transformation

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

An encoding of boolean values:


true xy.x false xy.y
Static Analysis

26

26

The Successor Function


A term that computes n+1 given a number n:
succ nfx.f(nfx)

Why does this work:


succ 3 (nfx.f(nfx))(fx.f(f(fx))) fx.f((fx.f(f(fx)))fx) fx.f((x.f(f(fx)))x) fx.f(f(f(fx)) 4

An induction proof shows that this always works


Static Analysis

27

27

Testing for Zero


iszero n.n(x.(xy.y))(xy.x)
iszero 0 (n.n(x.(xy.y))(xy.x))(fx.x) (fx.x)(xxy.y)(xy.x) (x.x)(xy.x) xy.x true iszero 3 (n.n(x.(xy.y))(xy.x))(fx.f(f(fx)) (fx.f(f(fx)))(xxy.y)(xy.x) (x.(xxy.y)((xxy.y)((xxy.y)x)))(xy.x) (xxy.y)((xxy.y)((xxy.y)(xy.x))) xy.y false

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

The Fun Language


E int | true | false | id | (E)| succ(E) | pred(E) | iszero(E) | plus(E1,E2) | mult(E1,E2) | not(E) | and(E1,E2) | or(E1,E2) | pair(E1,E2) | first(E) | second(E) | cons(E1,E2) | head(E) | tail(E) | if (E1) E2 else E3 | id ( E1, ..., Ek) | let id = E1 in E2 | let id(id1, ..., idk) = E1 in E2 | letrec id(id1, ..., idk) = E1 in E2
Static Analysis

(literals) (variables) (parentheses) (integers) (integers) (booleans) (pairs) (streams) (conditionals) (function call) (locals) (functions) (recursive functions)
30

30

The Factorial Function


letrec fac(n) = if (iszero(n)) 1 else mult(n,fac(pred(n))) in fac(6) The result is 720

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

Compiling From Fun To Lambda (1/3)


k fx.fkx true xy.x false xy.y id id succ(E) (nfx.f(nfx)) E pred(E) (nfx.n(gh.h(gf))(u.x)(u.u)) E iszero(E) (n.n(x.(xy.y))(xy.x)) E plus(E1,E2) (mnfx.mf(nfx)) E1 E2 mult(E1,E2) (mnf.n(mf)) E1 E2 not(E) (x.x(xy.y)(xy.x)) E and(E1,E2) (xy.xy(xy.y)) E1 E2 or(E1,E2) (xy.x(xy.x)y) E1 E2
Static Analysis

35

35

Compiling From Fun To Lambda (2/3)


pair(E1,E2) (abx.xab) E1 E2 first(E) (p.p(xy.x)) E second(E) (p.p(xy.y)) E cons(E1,E2) (abx.xab) E1 E2 head(E) (p.p(xy.x)) E tail(E) (p.p(xy.y)) E if (E1) E2 else E3 E1 E2 E3 id(E1,...,Ek) id E1 ... Ek let id = E1 in E2 (id.E2) E1 let id(id1,...,idk) = E1 in E2 (id.E2)(id1... idk. E1)

Static Analysis

36

36

Compiling From Fun To Lambda (3/3)


letrec id(id1,...,idk) = E1 in E2 (id.E2)(Y(id.id1... idk. E1)) where Y is a fixed-point operator such that YX X(YX) Y is used to enable recursive calls It provides dynamic unfoldings of the definition: Y(id.id1... idk. E1) (id.id1... idk. E1)(Y(id.id1... idk. E1))

Static Analysis

37

37

The Fixed-Point Operator in Action


The program:
letrec f(n) = if (iszero(n)) 42 else f(pred(n)) in f(87)

is compiled into:
(f.f87)(YF)

where F f. n.(iszeron) 42(f(predn))


(f.f87)(YF) (YF) 87 F((YF)) 87 (f. n.(iszeron) 42(f(predn)))((YF)) 87 (iszero 87) 42(((YF))(pred 87)) ((YF))(pred 87) (YF) 86 ...
Static Analysis

38

38

Concrete Fixed-Point Operators


A famous fixed-point operator (1936): Y ZZ (xy.y(xxy))(xy.y(xxy)) It works: YF (ZZ)F (y.y(ZZy))F F(ZZF) F(YF) There are infinitely many such operators
Static Analysis

39

39

The Lambda Tool


java Lambda -evaluate [-cbn|-cbv] [-<limit>] [-trace] [-full] [-stats] java Lambda -compile [-cbn|-cbv] java Lambda -decompile -evaluate: Lambda normalization -cbn: call-by-name reduction -cbv: call-by-value reductions -<limit>: max number of reductions -trace: print after every reduction -full: print with all parentheses -stats: print alpha and beta statistics -compile: translate from Fun programs to Lambda -cbn: call-by-name code -cbv: call-by-value code -decompile: translate from Lambda to Fun pairs and numerals

Static Analysis

40

40

Compiling for CBV


The previous compilation only works for CBN For CBV:
the Y operator loops the if-expression always evaluates both branches

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

Still, streams only work with CBN (as in Haskell)

Static Analysis

41

41

CBN vs. CBV


let f(x) = pair(x,pair(x,pair(x,pair(x,pair(x,pair(x,0)))))) in f(f(f(f(2))))

CBN: 3,368 reductions, CBV: 53 reductions


let f(x) = pair(x,pair(x,pair(x,pair(x,pair(x,pair(x,0)))))) in let g(y) = 7 in g(f(f(f(f(2)))))

CBN: 3 reductions, CBV: 55 reductions


Static Analysis

42

42

Runtime Errors (1/2)


Fun programs do not generate runtime errors:
no division operator no empty list no type checking

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

Runtime Errors (2/2)


Even worse, sometimes the nonsense actually happens to make sense: let a = succ(first(1)) in let b = a(3) in b(cons(true,87))

27

This means that errors are not detected!

Static Analysis

44

44

Modelling Runtime Errors (1/2)


To catch runtime errors, programs must be compiled in a more complicated manner Each value is modelled as a pair(tag,val) where:
val is the "old" value tag is an integer interpreted as:

Static Analysis

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

Modelling Runtime Errors (2/2)


E denotes the old compilation E denotes the new compilation Examples:
k pair(1,k) plus(E1,E2) let x = E1 in let y = E2 in if (and(iszero(pred(first(x))),iszero(pred(first(y)) pair(1,plus(second(x),second(y))) else pair(0,0)
Static Analysis

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 Type Checking


Assign a type to every expression Check that certain type rules are satisfied If so, then no type errors will occur
type correct statically type correct

Static Analysis

49

49

Types for Fun


A type is used to classify values: int | boolean | pair(1, 2) | stream() | fun(1, ..., k, )

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)

letrec inf(n) = cons(n,inf(succ(n))) in head(tail(tail(inf(7))))

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)

let f(f1) = succ(f1) in let f2(f3) = pair(f3,let f4 = 17 in f4) in f2(10)


Static Analysis

54

54

Generating Constraints
iszero(E)
"The argument must be of type int and the result is of type boolean"

[[E]] = int [[iszero(E)]] = boolean if (E1) E2 else E3


"The condition must be of type boolean, both brances must have the same type, and the whole expression has the same type as the branches"

[[E1]] = boolean [[if (E1) E2 else E3]] = [[E2]] = [[E3]]


Static Analysis

55

55

Type Constraints (1/3)


k: true: false: id: succ(E): pred(E): iszero(E): plus(E1,E2): mult(E1,E2): not(E): and(E1,E2):
Static Analysis

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

Type Constraints (2/3)


or(E1,E2): pair(E1,E2): first(E): second(E): cons(E1,E2): head(E): tail(E): if (E1) E2 else E3: id(E1,...,Ek):
is a "fresh" type variable
Static Analysis

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

Type Constraints (3/3)


let id = E1 in E2: [[id]] = [[E1]] [[let id = E1 in E2]] = [[E2]] let id(id1,...,idk) = E1 in E2: [[id]] = fun([[id1]],...,[[idk]],[[E1]]) [[let id(id1,...,idk) = E1 in E2]] = [[E2]] letrec id(id1,...,idk) = E1 in E2: [[id]] = fun([[id1]],...,[[idk]],[[E1]]) [[letrec id(id1,...,idk) = E1 in E2]] = [[E2]]

Static Analysis

58

58

Constraints Are Equalities


All type constraints are of the form: T1 = T2 where Ti is a type term with variables:
T int | boolean | pair(T1, T2) | stream(T) | fun(T1, ..., Tk, T) |
Static Analysis

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

Terms with variables:


d(X,b) h(X,g(Y,Z))

Static Analysis

60

60

The Unification Problem


An equality between two terms with variables: k(X,b,Y) = k(f(Y,Z),Z,d(Z)) A solution (a unifier) is an assignment from variables to terms that makes both sides equal: X = f(d(b),b) Y = d(b) Z=b
Static Analysis

61

61

Unification Errors
Constructor error: d(X) = e(X) Arity error: a = a(X)

Static Analysis

62

62

The Unification Algorithm


Paterson and Wegman (1976) In time O(n):
finds a most general unifier or decides that none exists

This is used as a backend for type checking

Static Analysis

63

63

The Lambda Tool


java Lambda -symbol java Lambda -type java Lambda -unify -symbol: check Fun programs and make identifiers unique -type: generate type constraints for Fun programs -unify: solve general constraints by unification

Static Analysis

64

64

The Factorial Function (1/3)


letrec fac(n) = if (iszero(n)) 1 else mult(n,fac(pred(n))) in fac(6)

Static Analysis

65

65

The Factorial Function (2/3)


[[fac]] = fun([[n]],[[1:if (iszero(n)) 1 else mult(n,fac(pred(n)))]]) [[letrec fac(n) = if (iszero(n)) 1 else mult(n,fac(pred(n))) in fac(6)]] = [[fac(6)] [[iszero(n)]] = boolean [[if (iszero(n)) 1 else mult(n,fac(pred(n)))]] = [[1]] [[if (iszero(n)) 1 else mult(n,fac(pred(n)))]] = [[mult(n,fac(pred(n)))]] [[1]]= int [[mult(n,fac(pred(n)))]] = int [[n]] = int [[fac(pred(n))]] = int [[fac]] = fun([[pred(n)]],[[fac(pred(n))]]) [[pred(n)]] = int [[n]] = int [[fac]] = fun([[6]],[[fac(6)]]) [[6]] = int

Static Analysis

66

66

The Factorial Function (3/3)


[[n]] = int [[1]] = int [[fac]] = fun(int,int) [[mult(n,fac(pred(n)))]] = int [[6]] = int [[pred(n)]] = int [[fac(6)]] = int [[iszero(n)]] = boolean [[fac(pred(n))]] = int [[if (iszero(n)) 1 else mult(n,fac(pred(n)))]] = int [[letrec fac(n) = if (iszero(n)) 1 else mult(n,fac(pred(n))) in fac(6)]] = int

Static Analysis

67

67

The Nonsense Program (1/3)


let a = succ(first(1)) in let b = a(3) in b(cons(true,87))

Static Analysis

68

68

The Nonsense Program (2/3)


[[a]] = [[succ(first(1))]] [[let a = succ(first(1)) in let b = a(3) in b(cons(true,87))]] = [[let b = a(3) in b(cons(true,87))]] [[succ(first(1))]] = int [[first(1)]] = int [[1]] = pair([[4:first(1)]],[[6]]) [[1]] = int [[b]] = [[a(3)]] [[let b = a(3) in b(cons(true,87))]] = [[b(cons(true,87))]] [[a]] = fun([[3],[[7:a(3)]]) [[3]] = int [[b]] = fun([[cons(true,87)],[[b(cons(true,87))]]) [[cons(true,87)]] = [[87]] [[cons(true,87)]] = stream([[true]]) [[87]] = int [[true]] = boolean
Static Analysis

69

69

The Nonsense Program (3/3)


*** unification constructor error pair(int,#v1) int The type error is caught at compile-time

Static Analysis

70

70

Efficiency Through Types


Untyped programs must use the expensive compilation strategy: E Typed programs can use the much cheaper strategy: E Errors must be caught at either runtime or at compile-time

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

Regular Types in Action


letrec fac2(n,foo) = if (iszero(n)) 1 else mult(n,foo(pred(n),foo)) in fac2(6,fac2) This function now has a type: [[fac2]] = fun(int,fun(int,fun(int,...,int),int),int)

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

Polymorphic Expansion (1/3)


Expand all non-recursive functions before generating the type constraints
let f(x) = pair(x,0) in pair(f(42),f(true))

pair(let f(x) = pair(x,0) in f(42),let f(x) = pair(x,0) in f(true))

pair(let f(x) = pair(x,0) in f(42), let f1(x1) = pair(x1,0) in f1(true))


Static Analysis

80

80

Polymorphic Expansion (2/3)


[[pair(let f(x) = pair(x,0) in f(42),let f1(x1) = pair(x1,0) in f1(true))]] = pair([[let f(x) = pair(x,0) in f(42)],[[let f1(x1) = pair(x1,0) in f1(true)]]) [[f]] = fun([[x],[[pair(x,0)]]) [[let f(x) = pair(x,0) in f(42)]] = [[f(42)] [[pair(x,0)]] = pair([[x],[[0]]) [[0]] = int [[f]] = fun([[42],[[f(42)]]) [[42]] = int [[f1]] = fun([[x1],[[pair(x1,0)]]) [[let f1(x1) = pair(x1,0) in f1(true)]] = [[f1(true)]] [[pair(x1,0)]] = pair([[x1],[[0]]) [[0]] = int [[f1]] = fun([[true],[[f1(true)]]) [[true]] = boolean
Static Analysis

81

81

Polymorhic Expansion (3/3)


[[true]] = boolean [[f1]] = fun(boolean,pair(boolean,int)) [[x]] = int [[pair(x,0)]] = pair(int,int) [[f1(true)]] = pair(boolean,int) [[f(42)]] = pair(int,int) [[0]] = int [[pair(let f(x) = pair(x,0) in f(42),let f1(x1) = pair(x1,0) in f1(true))]] = pair(pair(int,int),pair(boolean,int)) [[pair(x1,0)]] = pair(boolean,int) [[f]] = fun(int,pair(int,int)) [[x1]] = boolean [[let f1(x1) = pair(x1,0) in f1(true)]] = pair(boolean,int) [[let f(x) = pair(x,0) in f(42)]] = pair(int,int) [[42]] = int
Static Analysis

82

82

A Polymorphic Explosion (1/2)


let f1(y) = pair(y,y) in let f2(y) = f1(f1(y)) in let f3(y) = f2(f2(y)) in let f4(y) = f3(f3(y)) in f4(0) This is polymorphically typable! But what is the type of f4?

Static Analysis

83

83

A Polymorphic Explosion (2/2)


fun(int,pair(pair(pair(pair(pair(pair(pair(pair(int,int),pair(int,int)),pair(pair(int,int),pair(int,int))),pair(pair(pair(in t,int),pair(int,int)),pair(pair(int,int),pair(int,int)))),pair(pair(pair(pair(int,int),pair(int,int)),pair(pair(int,int),pair(in t,int))),pair(pair(pair(int,int),pair(int,int)),pair(pair(int,int),pair(int,int))))),pair(pair(pair(pair(pair(int,int),pair(int ,int)),pair(pair(int,int),pair(int,int))),pair(pair(pair(int,int),pair(int,int)),pair(pair(int,int),pair(int,int)))),pair(pair(p air(pair(int,int),pair(int,int)),pair(pair(int,int),pair(int,int))),pair(pair(pair(int,int),pair(int,int)),pair(pair(int,int),p air(int,int)))))),pair(pair(pair(pair(pair(pair(int,int),pair(int,int)),pair(pair(int,int),pair(int,int))),pair(pair(pair(int,i nt),pair(int,int)),pair(pair(int,int),pair(int,int)))),pair(pair(pair(pair(int,int),pair(int,int)),pair(pair(int,int),pair(int,i nt))),pair(pair(pair(int,int),pair(int,int)),pair(pair(int,int),pair(int,int))))),pair(pair(pair(pair(pair(int,int),pair(int,i nt)),pair(pair(int,int),pair(int,int))),pair(pair(pair(int,int),pair(int,int)),pair(pair(int,int),pair(int,int)))),pair(pair(p air(pair(int,int),pair(int,int)),pair(pair(int,int),pair(int,int))),pair(pair(pair(int,int),pair(int,int)),pair(pair(int,int),p air(int,int))))))),pair(pair(pair(pair(pair(pair(pair(int,int),pair(int,int)),pair(pair(int,int),pair(int,int))),pair(pair(pai r(int,int),pair(int,int)),pair(pair(int,int),pair(int,int)))),pair(pair(pair(pair(int,int),pair(int,int)),pair(pair(int,int),pai r(int,int))),pair(pair(pair(int,int),pair(int,int)),pair(pair(int,int),pair(int,int))))),pair(pair(pair(pair(pair(int,int),pair (int,int)),pair(pair(int,int),pair(int,int))),pair(pair(pair(int,int),pair(int,int)),pair(pair(int,int),pair(int,int)))),pair(pa ir(pair(pair(int,int),pair(int,int)),pair(pair(int,int),pair(int,int))),pair(pair(pair(int,int),pair(int,int)),pair(pair(int,int ),pair(int,int)))))),pair(pair(pair(pair(pair(pair(int,int),pair(int,int)),pair(pair(int,int),pair(int,int))),pair(pair(pair(i nt,int),pair(int,int)),pair(pair(int,int),pair(int,int)))),pair(pair(pair(pair(int,int),pair(int,int)),pair(pair(int,int),pair(i nt,int))),pair(pair(pair(int,int),pair(int,int)),pair(pair(int,int),pair(int,int))))),pair(pair(pair(pair(pair(int,int),pair(i nt,int)),pair(pair(int,int),pair(int,int))),pair(pair(pair(int,int),pair(int,int)),pair(pair(int,int),pair(int,int)))),pair(pair (pair(pair(int,int),pair(int,int)),pair(pair(int,int),pair(int,int))),pair(pair(pair(int,int),pair(int,int)),pair(pair(int,int), pair(int,int)))))))))

Static Analysis

84

84

The Full Lambda Tool


java Lambda -evaluate [-cbn|-cbv] [-<limit>] [-trace] [-full] [-stats] java Lambda -compile [-cbn|-cbv] java Lambda -symbol java Lambda -decompile java Lambda -type java Lambda -polymorph java Lambda -unify -evaluate: Lambda normalization -compile: translate from Fun programs to Lambda -symbol: check Fun programs and make identifiers unique -decompile: translate from Lambda to Fun pairs and numerals -type: generate type constraints for Fun programs -polymorph: expand non-recursive functions in Fun programs -unify: solve general constraints by regular unification

Static Analysis

85

85

Always More Slack


Regular types and polymorphism is not enough: letrec f(n) = if (iszero(n)) 0 else pair(f(pred(n)),f(pred(n))) in f(4) is unfairly rejected by the type checker

Static Analysis

86

86

Things to Worry About

The type checker is unsound

The type checker is undecidable

Static Analysis

87

87

You might also like