CS 342 Lecture -*- Outline -*- * lambda calculus advert: lambda really is the ultimate data and control structure essence of programming languages formalized way to reason about functional programs ** key idea: using thunks to represent data and control ** syntax, p. 178 ** booleans and if, p. 179 ** lists, p. 179 ** the calculus, p. 187 *** substitution different convention than Kamin's: *not* assuming value-ops and user-defined names distinct from names bound with lambda (this causes some difficulties below, but they point out what's happening in Kamin's account) **** free variables, Fv ---------- Fv(x) = {x} Fv(lambda (x) e) = Fv(e) - {x} Fv(e e') = Fv(e) union Fv(e') ---------- integer constants, user-define names, and value-ops treated as variables e.g., Fv((+ 1 2)) = {+, 1, 2} meaning of free variables given by an environment (which is why I'd rather define + as a free variable) **** bound variables Def: x is not bound in x x is bound in (U V), if x is bound in U or x is bound in V x is bound in (lambda (y) e), if x == y or x is bound in e note: x can be free and bound in the same term: (x (lambda (x) x)) bound variables simply place holders: alpha rule ------------------ [alpha] (lambda (x) U) = (lambda (y) [y/x]U), if y is not free in U ------------------ **** syntactic substitution ------------ [e/x]x == e [e/x]y == y if not(y == x) [e/x](e1 e2) == (([e/x]e1) ([e/x]e2)) [e/x](lambda (y) e') == (lambda (y) [e/x]e') if not(y == x), and not(y in Fv(e)) ------------ last restriction necessary to preserve lexical scoping e.g. [y/x](lambda (y) (x y)) is not (lambda (y) (y y)) rather (lambda (z) (y z)) *** equational theory meaning of expressions given by rewriting **** beta rule: application ---------------------- [beta] ((lambda (x) M) N) ==> [N/x]M ------------------ way to reason about applications of lambda expressions ------------------ e.g.: ((((lambda (f) (lambda (x) (lambda (y) (f x y)))) +) 1) 2) ==> ((((lambda (x) (lambda (y) (+ x y)))) 1) 2) ==> ((lambda (y) (+ 1 y)) 2) ==> (+ 1 2) ------------------ works the other way too: abstracting parts of an expression (+ 1 2) == ((lambda (f) (f 1 2)) +) what we did to derive combine, flat-recur from examples This is always correct for a lazy (normal order) language like SASL For eager (applicative order) langauges like Scheme, only correct as long as the arguments are defined: ------------ (((lambda (y) (lambda (x) y)) 27) (/ 3 0)) ==> ((lambda (x) 27) (/ 3 0)) ==> 27 ------------ **** delta rule: using primitive operations from the environment way to reason about built-in types of the language --------------------- [delta] (f e1 e2) ==> f(e1,e2), if e1 and e2 are integers, and f is not bound by surrounding expr. --------------------- **** mu rule: using defined names from the environment way to use definitions --------------------- [mu] f ==> e, if f is not bound by surrounding expr, and f is bound to e in global environment --------------------- **** eta rule: an optimization don't need to define a function (lambda (f) (f x)), can just use f --------------- [eta] (lambda (x) (e x)) ==> e, if x not in Fv(e) --------------- --------------- e.g.: (set with-characteristic (lambda (f) (lambda (x) (f x)))) ==> (set with-characteristic (lambda (f) f)) e.g.: (set member? (lambda (s) (lambda (x) (s x)))) ==> (set member? (lambda (s) s)) --------------- ** summary more interesting topics here: church numerals (numbers and for loops), Y combinator (recursion), SKI (even simpler language, optimization) but the rules above capture the essence of why functional programs are easy to reason about: simple equational theory *** referential transparency: equals can be replace by equals e.g. (f x) = (f x) so (lambda (f) (+ (f x) (f x))) can be replaced by (lambda (f) (* 2 (f x)) no side effects *** why give up referential transparency? efficiency - of programs at run-time of programming (for changes)??