230 likes | 439 Views
A LISP interepreter for the Lambda Calculus Functional Programming. Academic Year 2005-2006 Alessandro Cimatti cimatti@itc.it. Homework. Write a program to normalize expressions for lambda calculus Suggested steps: Define the ADT for lambda terms constructors recognizers
E N D
A LISP interepreter for the Lambda CalculusFunctional Programming Academic Year 2005-2006 Alessandro Cimatti cimatti@itc.it
Homework • Write a program to normalize expressions for lambda calculus • Suggested steps: • Define the ADT for lambda terms • constructors • recognizers • Define substitution primitive • Define recognizer for redex’s • Define beta-reduction rule • Define the normalizer: • recursively traverse the term looking for redex, and reduce • careful with infinite looping!! • Some remarks • homework due by december 20 • oh, it’s today • try to use recursive definitions • homework is closely related to course project!!!
Syntax • Expressions have the following syntax • e is either • x a variable • λx.e a lambda abstraction • e1 e2 application • Notation • for variables we use x, y, z, w • for expressions we use e, e’, e1, e2, ...
The ADT for Expressions ; The ADT for expressions ; ; e ::= v | l | a ; v ::= symbolp ; a ::= ( e e ) ; l ::= ( lam v e ) ; (defun mk-v (sym) sym) (defun is-v (e) (symbolp e)) (defun mk-l (v b) (list 'lam v b)) (defun is-l (e) (and (consp e) (= (length e) 3) (equal 'lam (first e)) (is-v (second e)))) (defun l-get-v (l) (second l)) (defun l-get-b (l) (third l)) (defun mk-a (f a) (list f a)) (defun is-a (e) (and (consp e) (= (length e) 2))) (defun a-get-f (a) (first a)) (defun a-get-a (a) (second a))
Generalized Recognizer ; takes arbitrary sexp in input ; (defun is-e (e) (cond ((is-v e) t) ((is-a e) (and (is-e (a-get-f e)) (is-e (a-get-a e)))) ((is-l e) (and (is-e (l-get-v e)) (is-e (l-get-b e)))) (t nil)))
Free variables • The set of free variables in e, denoted as FV(e), is defined as • FV(x) = { x } • FV(e1 e2) = FV(e1) U FV(e2) • FV(λx.e) = FV(e) – { x } • e is a closed λ-term iff FV(e) is empty
Free variables ; Return the free variables of an expression ; (defun fv (e) (cond ((is-v e) (list e)) ((is-a e) (append (fv (a-get-f e)) (fv (a-get-a e)))) ((is-l e) (remove (l-get-v e) (fv (l-get-b e)))) (t (error "Unknown lambda term type")))) (defun free-in (v e) (member v (fv e)))
Alpha Renaming • λ-terms are equivalent up to renaming of bound variables • all occurrences of x are replaced by y • The rule of α-conversion • λx.e ≡αλy.[y/x]e • provided that y is not free in e • For instance, • λx.x ≡αλy.y • λx.y x ≡αλz.y z • But not • λx.y x ≡αλy.y y
Equivalence up to Alpha-conversion (defun alpha-equivalent (e1 e2) (alpha-equivalent1 e1 e2 nil nil)) (defun alpha-equivalent1 (e1 e2 rpl1 rpl2) (cond ((is-v e1) (and (is-v e2) (let ((new1 (cdr (assoc e1 rpl1))) (new2 (cdr (assoc e2 rpl2)))) (if (and (null new1) (null new2)) (equal e1 e2) (equal new1 new2))))) ((is-a e1) (and (is-a e2) (alpha-equivalent1 (a-get-f e1) (a-get-f e2) rpl1 rpl2) (alpha-equivalent1 (a-get-a e1) (a-get-a e2) rpl1 rpl2))) ((is-l e1) (and (is-l e2) (let* ((new (freshvar)) (old1 (l-get-v e1)) (old2 (l-get-v e2)) (newrpl1 (cons (cons old1 new) rpl1)) (newrpl2 (cons (cons old2 new) rpl2))) (alpha-equivalent1 (l-get-b e1) (l-get-b e2) newrpl1 newrpl2))))))
Substitution: [e’ / x] e • Replace free occurrences of x in e with e’ • No problem when free variables are replaced by term that “does not clash” • [λz.z w / x] λy.x = λy.λz.z w • However, there may be a problem if there is name capture/clash • [λz.z w / x] λx.x is not the same as λx.λz.z w • replacement of a bound variable! • [λz.z w / x] λw.x is not the same as λw.λz.z w • free variable w is now bound!
Definition of Substitution • [e’/x]x = e’ • [e’/x]y = y • [e’/x](e1 e2) = ([e’/x]e1) ([e’/x] e2) • [e’/x](λx.e) = λx.e • [e’/x](λy.e) = λy.e if x not in FV(e) • [e’/x](λy.e) = λy.[e’/x]e if x in FV(e), y not in FV(e’) • [e’/x](λy.e) = λz.[e’/x][z/y]e if x in FV(e), y in FV(e’)
Substitution (defun subst-with-in (x e1 exp) (cond ((is-v exp) (if (equal x exp) e1 exp)) ((is-a exp) (mk-a (subst-with-in x e1 (a-get-f exp)) (subst-with-in x e1 (a-get-a exp)))) ((is-l exp) ; say exp is (lam y e) (let ((y (l-get-v exp)) (e (l-get-b exp))) (cond ((equal x y) exp) ((not (free-in x e)) exp) ((and (free-in x e) (not (free-in y e1))) (mk-l y (subst-with-in x e1 e))) ((and (free-in x e) (free-in y e1)) (let ((z (freshvar))) (mk-l z (subst-with-in x e1 (subst-with-in y z e))))))))))
Beta Reduction • An application whose LHS is an abstraction • An expression of the form ((λx. e1) e2) • is called redex (reducible expression) • A redex evaluates to the body of the abstraction with parameter substitution • Examples • (λx.(x y)) z →β z y • (λx. y) z →β y • (λx.x x) y →β y y • (λx.x x) (λx.x x) →β (λx.x x) (λx.x x) • Beta-reduction • (λx. e1) e2 →β[e2/x]e1
Beta Reduction (defun is-rdx (e) (and (is-a e) (is-l (a-get-f e)))) (defun rdx-get-v (rdx) (l-get-v (a-get-f rdx))) (defun rdx-get-b (rdx) (l-get-b (a-get-f rdx))) (defun rdx-get-a (rdx) (a-get-a rdx)) ; Beta reduce: (a (l v e) e1) ==> [e1 / v] e (defun beta-reduce (rdx) (subst-with-in (rdx-get-v rdx) (rdx-get-a rdx) (rdx-get-b rdx))) ; Beta reduce if possible (defun beta-reduce-if-redex (e) (if (is-rdx e) (beta-reduce e) e))
Properties of Beta Reduction • If e1 → β e2, then • (e e1) →β (e e2) • (e1 e) →β (e2 e) • (λx.e1) →β (λx.e2) • The transitive closure of →β • e1 →β e2 →β ... →β en • e1 reduces to en • The induced equivalence relation is denoted ≡β
β-Normal Form • A λ-term is in β-normal form if it does not have any redex as subexpression • meaning: there is no way to apply beta reduction • A λ-term e has a β-normal form if for some e’ • e ≡β e’ • e’ is a β-normal form • Some λ-terms do not have any normal form: • (λx.xx)(λx.xx) →β [(λx.xx)/x]xx = (λx.xx)(λx.xx) • Intuition? • loop for ever
Beta Reduction (outer) ; Iterate beta reduction on outermost redex ; (defun beta-reduce-outer (e &optional (lim 100)) (cond ((< lim 0) e) ((is-rdx e) (beta-reduce-outer (beta-reduce e) (- lim 1))) ((is-v e) e) ((is-a e) (mk-a (beta-reduce-outer (a-get-f e)) (beta-reduce-outer (a-get-a e)))) ((is-l e) (mk-l (l-get-v e) (beta-reduce-outer (l-get-b e))))))
Beta Reduction (inner) ; Iterate beta reduction on inntermost redex ; (defun beta-reduce-inner (e &optional (lim 100)) (cond ((< lim 0) e) ((is-v e) e) ((is-a e) (beta-reduce-if-redex (mk-a (beta-reduce-inner (a-get-f e) lim) (beta-reduce-inner (a-get-a e) lim)))) ((is-l e) (mk-l (l-get-v e) (beta-reduce-inner (l-get-b e) lim)))))
Beta Normalization (defun beta-normalize-outer (e &optional (lim 100)) (beta-normalize-param e 'beta-reduce-outer lim)) (defun beta-normalize-inner (e &optional (lim 100)) (beta-normalize-param e 'beta-reduce-inner lim)) (defun beta-normalize-param (e fn &optional (lim 100)) (let* ((res (apply fn (list e lim))) (use-alpha-equivalent t) (stop (if use-alpha-equivalent (alpha-equivalent res e) (equal res e))) (if stop res ; fix point reached (beta-normalize-param res fn))))
Properties of Lambda Calculus • If e has a normal form, then this is unique (up to renaming) • The β-reduction satisfies the so-called Church-Rosser property • if e reduces to e1 and e reduces to e2 • then there exists e3 such that • e1 reduces to e3 and e2 reduces to e3 • Confluence
Top level normalization ; try with the two different strategies ; and compare results ; (defun beta-normalize (e) (let ((res-inner (beta-normalize-inner e 10)) (res-outer (beta-normalize-outer e 10))) (if (alpha-equivalent res-outer res-inner) (progn (format t "All right!~%") (format t "Inner: ~A~%" res-inner) (format t "Outer: ~A~%" res-outer)) (progn (format t "Uh oh... results not equivalent!!!") (format t "Inner: ~A~%" res-inner) (format t "Outer: ~A~%" res-outer) (error "error in beta-normalize")))))
Additional Remarks • no distinction between loaded code and submitted code • lisp is not case sensitive, use dashes • AGetVversus a-get-v • use comments (from ; to eol) and indentation • submit also the test cases you used to test your program • if you don’t have any, this is not a good sign... • normalization has to look for redexes on all the term, not only at top level • (x (x ((lambda y y) x))) has a redex • no need to be destructive unless clearly identified bottleneck! • no (quit) at end of file, otherwise interpreter is exited