CSE 230: Winter 2007 Principles of Programming Languages Lecture 12: The λ-calculus Ranjit Jhala UC San Diego Review The lambda calculus is a calculus of functions: e := x λx. e e 1 e 2 Several evaluation strategies exist based on β-reduction: (λx.e) e β [e /x] e The Diamond Property Languages defined by nondeterministic rules: Logic programming languages Expert systems Constraint satisfaction systems Make Useful to know if systems have diamond property! If so, a unique result is guaranteed Equality = β : symmetric, reflexive, transitive closure of β = β is ( β β ) * So e 1 = β e 2 if e 1 converts to e 2 via a seq of forward and backward β : e 1 e 2 The Church-Rosser Theorem If e 1 = β e 2 then exists e s.t. e 1 β* e and e 2 β* e e Proof (informal): apply the diamond property as many times as necessary. e 1 e 2 Corollaries If e 1 = β e 2 and e 1, e 2 are normal forms then e 1 e 2 Proof: From CR we have e. e 1 * β e and e 2 * β e As e 1, e 2 are normal forms they are e If e * β e 1 and e * β e 2 and e 1, e 2 are normal forms then e 1 e 2 Proof? Significance: Each term reduces to one normal form 1
Evaluation Strategies Q: Which β-redex should be picked? Good news, Church-Rosser Theorem: independent of strategy, there is at most one normal form Bad news, some strategies may fail to find a normal form: (λx. y) ((λy.y y) (λy.y y)) (λx. y) ((λy.y y) (λy.y y)) (λx. y) ((λy.y y) (λy.y y)) y Normal-Order Reduction outermost redex: a redex not contained inside another redex (λe. λf. e) ((λa.λb. a) x y) ((λc.λd. c) u v) Outermost: We consider three strategies: normal call-by-name call-by-value Normal order: leftmost outermost redex first Theorem: If e has a normal form e then normal order reduction will reduce e to e. Weak vs Strong Reduction In most PL, functions considered fully evaluated Weak Reduction: No reduction done under lambdas i.e. inside a function body Strong Reduction: Reduction is done under lambdas Partial evaluation of function, other optimizations Normal order reduces under lambda λx.((λy.y y) (λy.y y)) λx.((λy.y y) (λy.y y)) Not always desired Call-by-Name Reduction Don t reduce under λ Don t evaluate the argument to a function call Directly substitute; evaluate when reducing body Demand-driven an expression is not evaluated unless needed in body Normalizing it converges whenever normal order converges but does not always evaluate to a normal form Call-by-Name Example (λy. (λx. x) y) ((λu. u) (λv. v)) β (λy. y) ((λu. u) (λv. v)) β (λu. u) (λv. v) β λv. v Call-by-Value Reduction Don t reduce under lambda Do evaluate argument to function call Most languages are call-by-value Not normalizing (λx. y) ((λy.y y) (λy.y y)) diverges, but normal order (or CBN) converges 2
Call-by-Value Example: Evaluate arg (λy. (λx. x) y) ((λu. u) (λv. v)) β (λy. (λx. x) y) (λv. v) β (λy. y) (λv. v) β λv. v CBV vs. CBN Call-by-value: Easy to implement May diverge Call-by-name: More difficult to implement must pass unevaluated expressions Args multiply-evaluated inside function body Simpler theory than call-by-value Terminates more often (always if normal form exists) e.g. if arg is non-terminating, but not used Various other reduction strategies Programming with the λ-calculus How does the λ-calculus relate to real programming languages? Bools? If-then-else? Integers? Recursion? Functions: well, those we have Functional Programming λ-calculus = prototypical functional PL: no side effects, several evaluation strategies, lots of functions, nothing but functions Q: How can we program with functions? Are they a sufficient abstraction? Functional Programming Lisp, Scheme, ML, Haskell Pure: No locations, update, (side) effects Functions as args to/results from other functions: Higher-order programming Some impure functional languages permit side-effects (e.g., Lisp, Scheme, ML, OCaml) references (pointers), arrays, exceptions Variables in Functional Languages We can introduce new variables: let x = e 1 in e 2 x is bound by let i.e., x is statically scoped in e 2 essentially like (λx. e 2 ) e 1 Variables are never updated just names for expressions e.g., x is a name for the value denoted by e 1 in e 2 Equivalent to meaning of let in mathematics 3
Why? Referential Transparency Enables reasoning equationally, by substitution: let x = e 1 in e 2 [e 1 /x]e 2 Imp. langs: side-effects in e 1 invalidate equation evaluation of e_1 can alter semantics of e_2 Expressiveness of λ-calculus λ calculus can express: data types (ints, bools, pairs, lists, trees, ) branching Recursion (f e_1) and (f e_2) can produce different results even if e_1 and e 2 evaluate to the same thing! FP: function s behavior depends only on arguments Doesn t matter how function was called/used before! No state, like a mathematical function Localizes, simplifies understanding, reasoning about FP Enough to encode Turing machines Corollary: e = β e is undecidable But how to encode using only λ? Idea: encode the behavior of values Encoding Booleans in λ-calculus Q: What can we do with a boolean? A: Make a binary choice Q: So, how can you view this as a function? A: Bool = fun that takes two choices, returns one true = def λx. λy. x false = def λx. λy. y if E 1 then E 2 else E 3 = def E 1 E 2 E 3 Example: if true then u else v is (λx. λy. x) u v β (λy. u) v β u Boolean Operations Boolean operations: not Function takes b: returns function takes x,y: returns opposite of b s return not = def λb.(λx.λy. b y x) Boolean operations: or Function takes b 1, b 2 : returns function takes x,y: returns (if b 1 then x else (if b 2 then x else y)) or = def λb 1.λb 2.(λx.λy. b 1 x (b 2 x y)) Encoding Pairs (and so, Records) Q: What can we do with a pair? A: We can select one of its elements Pair: function takes a bool, returns the left or the right element mkpair e 1 e 2 = def λb. b e 1 e 2 Note: pair encoded as λ-abstraction, waiting for bool fst p = def p true snd p = def p false Ex: fst (mkpair x y) (mkpair x y) true true x y x Encoding Natural Numbers Q: What can we do with a natural number? A: Iterate a number of times over some function Nat: function that takes fun f, starting value s: returns: f applied to s a number of times 0 = def λf. λs. s 1 = def λf. λs. f s 2 = def λf. λs. f (f s) M Called Church numerals, unary representation Note: (n f s) : apply f to s n times, i.e. f n (s) 4
Operating on Natural Numbers Testing equality with 0 iszero n = def n (λb. false) true iszero = def λn.(λ b.false) true The successor function succ n = def λf. λs. f (n f s) succ = def λn. λf. λs. f (n f s) Addition add n 1 n 2 = def n 1 succ n 2 add = def λn 1.λn 2. n 1 succ n 2 Multiplication mult n 1 n 2 = def n 1 (add n 2 ) 0 mult = def λn 1.λn 2. n 1 (add n 2 ) 0 Ex: Computing with Naturals What is the result of add 0? (λn 1. λn 2. n 1 succ n 2 ) 0 β λn 2. 0 succ n 2 = λn 2. (λf. λs. s) succ n 2 β λn 2. n 2 = λx. x Ex: Computing with Naturals mult 2 2 2 (add 2) 0 (add 2) ((add 2) 0) 2 succ (add 2 0) 2 succ (2 succ 0) succ (succ (succ (succ 0))) succ (succ (succ (λf. λs. f (0 f s)))) succ (succ (succ (λf. λs. f s))) succ (succ (λg. λy. g ((λf. λs. f s) g y))) succ (succ (λg. λy. g (g y))) * λg. λy. g (g (g (g y))) = 4 Encoding Recursion Write a function find that: takes predicate P, natural n, returns smallest nat., larger than n, satisfying P find can encode all recursion but how to write it? Encoding Recursion find satisfies the equation: find p n = if p n then n else find p (succ n) Define: F = λf.λp.λn.(p n) n (f p (succ n)) A fixpoint of F is an x s.t. x = F x find is a fixpoint of F! as find p n = F find p n so find = F find Q: Given λ-term F, how to write its fixpoint? The Y-Combinator Define: Y = def λf. (λy.f(y y)) (λx. F(x x)) Called the fixpoint combinator because Y F β (λy.f (y y)) (λx. F (x x)) β F ((λx.f (x x))(λz. F (z z))) β F(YF) i.e. Y F = β F (Y F) Can write fixpoint for any λ-calculus function Define: F = λf.λp.λn.(p n) n (f p (succ n)) find = Y F 5
Whoa! Define: F = λf.λp.λn.(p n) n (f p (succ n)) and: find = Y F Whats going on? find p n = β Y F p n = β F (Y F) p n = β F find p n = β (p n) n (find p (succ n)) Fixpoint Combinators Y = def λf. (λy.f(y y)) (λx. F(x x)) How does this mix with Call-by-Value? Y F β (λy.f (y y)) (λx. F (x x)) β F ((λx.f (x x))(λz. F (z z))) β F (F ((λx.f (x x))(λz. F (z z)))) β F (F (F ((λx.f (x x))(λz. F (z z))))) β Many other fixpoint combinators Including those that work for CBV See upcoming HW Including Klop s Combinator: Y k = def (L L L L L L L L L L L L L L L L L L L L L L L L L L) where: L = def λaλbλcλdλeλfλgλhλiλjλkλlλmλnλoλpλqλsλtλuλvλwλxλyλzλr. r (t h i s i s a f i x p o i n t c o m b i n a t o r) Expressiveness of λ-calculus Encodings are fun but programming in pure λ-calculus is not Encodings complicate static analysis Know the λ-calculus encodes them, so we add 0,1,2,,true,false,if-then-else to language Next, we will add types 6