CSE 230: Winter 2010 Principles of Programming Languages Lecture 10: Programming in λ-calculusc l l 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 Order of Evaluation λ-term can have many β-redexes (λx. E) E (λy. (λx. x) y) E Reduce the inner or the outer application? Normal Forms A term without redexes is in normal form A reduction sequence stops at a normal form If e in normal form and e * β e then e e inner (λ y. (λ x. x) y) E outer (λx.λy. x) is in normal form (λ y. [y/x] x) E = (λ y. y) E [E/y] (λ x. x) y =(λ x. x) E E (λx.λy. x) (λz. z) is not in normal form
Church-Rosser/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 * e * 1 β 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 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 We consider three strategies: normal call-by-name call-by-value Normal-Order Reduction outermost redex: a redex not contained inside another redex (λe. λf. e) ((λa.λb. λb a) x y) ((λc.λd. λd c) u v) Outermost: 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 WeakReduction: No reduction done under lambdas i.e. inside a function body StrongReduction: Reduction is done under lambdas Partial evaluation of function, other optimizations Normal order reduces under lambda λx.((λy.y yyy)( y) (λy.y yyy)) y)) λx.((λy.y yyy)( y) (λy.y yyy)) 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 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 Doevaluate 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 Call-by-Value Example: (λy. (λx. x) y) ((λu. u) (λv. v)) β (λy. (λx. x) y) (λv. v) β β (λy. y) (λv. v) β λv. v Evaluate arg
CBV vs. CBN Call-by-value (CBV): 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 l Terminates more often (always if normal form exists) eg e.g. if arg is non-terminating, but not used Others 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 ti 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 Why? Referential Transparency Enables reasoning equationally, by substitution: let x = e in e [e /x]e let x = e 1 in e 2 [e 1 /x]e 2 Imp. langs: side-effects effects in e 1 invalidate equation evaluation of e_1 can alter semantics of e_2 (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, t like a mathematical ti function Localizes, simplifies understanding, reasoning about FP Expressiveness of λ-calculus λ calculus can express: data types (ints, bools, pairs, lists, trees, ) branching Recursion 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. 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. 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) 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 λ Calculus Review Equivalent to Turing machine Encodes several datatypes bool, int, pairs, (HW: lists ) Recursion Encoding Recursion Write a function find that: takes predicate P, natural n returns: smallest natural larger than n satisfying i 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 st 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 as Y F β (λy.f (y y)) (λx. F (x x)) β F ((λx.f (x x))(λz. F (z z))) β F (Y F) ie i.e. Y F = β F (Y F) Can get fixpoint for any λ-calculus function 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))))) β F (F (F ((λx.f (x x))(λz. F (z z))))) β
Many other fixpoint combinators Including those that work for CBV 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 then else to language Next, we will add types