Com S 342 meeting -*- Outline -*- * local binding constructs (3.1) ** let (3.1.1) ------------------------------------------ THE PROBLEMS SOLVED BY LET (double-reverse-sublist '((a b) (c d))) ==> ((b a) (b a) (d c) (d c)) (define double-reverse-sublist ;; TYPE: (-> ((list (list T))) ;; (list (list T))) (lambda (lst) (if (null? lst) '() (cons (reverse (car lst)) (cons (reverse (car lst)) (double-reverse-sublist (cdr lst))))))) problems: ------------------------------------------ ... - loss of time by repeating computations - less clear, tedious to repeat code - with side effects, need to save value Q: What's the time complexity of reverse? Q: How much slower is the code than it would be if didn't have to repeat that computation? Q: What mechanism do we have, so far, that binds names to values? ------------------------------------------ REWRITES TO AVOID REPEATED COMPUTATION (define double-reverse-sublist ;; TYPE: (-> ((list (list T))) ;; (list (list T))) (lambda (lst) (if (null? lst) '() ------------------------------------------ ... (cons-twice (reverse (car lst)) (double-reverse-sublist (cdr lst)))))) (define cons-twice ;; TYPE: (-> (T (list T)) (list T)) (lambda (elem ls) (cons elem (cons elem ls)))) But it's annoying to have to use a helping procedure. We could eliminate it, since it's only used once, by substituting its definition... ------------------------------------------ SUBSTITUTING THE DEFINITION OF CONS-TWICE (define double-reverse-sublist ;; TYPE: (-> ((list (list T))) ;; (list (list T))) (lambda (lst) (if (null? lst) '() ((lambda (elem ls) (cons elem (cons elem ls))) (reverse (car lst)) (double-reverse-sublist (cdr lst)))))) ------------------------------------------ But this is hard to read... explain it Q: What do other languages do to avoid recomputation? variables, assignment, constant declarations... Better for humans to have the names, like elem and ls, bound to values first, close to each other... ------------------------------------------ USING LET (define double-reverse-sublist ;; TYPE: (-> ((list (list T))) ;; (list (list T))) (lambda (lst) (if (null? lst) '() (cons elem (cons elem ls)) ;; or more usually... (define double-reverse-sublist ;; TYPE: (-> ((list (list T))) ;; (list (list T))) (lambda (lst) (if (null? lst) '() (cons elem (cons elem (double-reverse-sublist (cdr lst)))) ------------------------------------------ ... (let ((elem (reverse (car lst))) (ls (double-reverse-sublist (cdr lst)))) ... )))) ... (let ((elem (reverse (car lst)))) ... )))) Explain the parentheses in the let notation So this is the idea, we define the meaning of let by translating it back to that hard-to-read lambda application, humans get nice syntax, language gets simple semantics Q: What is this like in other langauges? declaration forms (in C, Pascal), so this is a kind of local declaration construct. ------------------------------------------ SYNTAX OF LET ::= | ... SEMANTICS OF LET (let ((var1 exp1) ... (varn expn)) body) = ------------------------------------------ ... (let ({}+) ) ::= ( ) ::= The production for is a bit of a lie, it's actually a list of , but we won't see why until we discuss side effects. This is where the two parentheses come from. Q: What is this equal to? ... ((lambda (var1 ... varn) body) exp1 ... expn) ------------------------------------------ FOR YOU TO DO (IN PAIRS) 1. What is the difference between the following? (define x 9) (let ((x 3)) (let ((y (+ x 4))) (* x y))) ; and (define x 9) (let ((x 3) (y (+ x 4))) (* x y)) Hints: what is the desugared form? draw arrows from the varrefs to the var declarations. ------------------------------------------ in the first one, the x's refer to 3, while in the second let, the x refers to 9 so the first is 21, the second is 33 Q: How would you define the region of a var decl in a let? the scope? be sure they understand this! ** letrec (3.1.2) ------------------------------------------ LET HELPS AVOID NAMESPACE CLUTTER (define circle-area ;; TYPE: (-> (number number) number) (let ((pi 3.14159)) (lambda (radius) (* pi (* radius radius))))) ------------------------------------------ so you might like to do that for helping procedures... ------------------------------------------ DOES LET WORK FOR PROCEDURES? (define list-product ;; TYPE: (-> ((list number)) number) (let ((prod-iter ;; TYPE: (-> ((list number) number) ;; number) (lambda (lon acc) (if (null? lon) acc (if (zero? (car lon)) 0 (prod-iter (cdr lon) (* acc (car lon)))))))) (lambda (lon) (prod-iter lon 1)))) ------------------------------------------ Q: What does the recursive call of prod-iter refer to? nothing! Q: Why doesn't that work? the scope of prod-iter doesn't include it's body! *** syntax ------------------------------------------ LETREC (define list-product ;; TYPE: (-> ((list number)) number) (letrec ((prod-iter ;; TYPE: (-> ((list number) number) ;; number) (lambda (lon acc) (if (null? lon) acc (if (zero? (car lon)) 0 (prod-iter (cdr lon) (* acc (car lon)))))))) (lambda (lon) (prod-iter lon 1)))) SYNTAX ::= | ... ::= (lambda ) ::= ({}*) | ::= ------------------------------------------ ... (letrec ({}+) ) ... ::= ( ) Q: How are local declarations of recursive procedures handled in other langauges? Pascal, ML Q: Is there any difference from Scheme? they aren't allowed within expressions in other languages ------------------------------------------ FOR YOU TO DO (IN PAIRS) fill in the following, so that it returns the indicated value (letrec ((odd? ;; TYPE: (-> (number) boolean) (lambda (n) (if (zero? n) (even? (- n 1))))) (even? ;; TYPE: (-> (number) boolean) (odd? (- n 1)) )) (list (odd? 227) (even? 342))) ==> (#t #t) ------------------------------------------ *** advantages and disadvantages disadvantages: harder to debug the internal procedures ------------------------------------------ ADVANTAGES OF USING LETREC - namespace control - avoid passing (define subst ------------------------------------------ ... unchanged arguments ;; TYPE: (-> (symbol symbol (list symbol)) (list symbol)) (lambda (new old ls) (letrec ((helper ;; TYPE: (-> ((list symbol)) (list symbol)) (lambda (ls) (if (null? ls) '() (cons (if (eq? old (car ls)) new (car ls)) (helper (cdr ls))))))) (helper ls)))) ------------------------------------------ FOR YOU TO DO (IN PAIRS) Using tail-recursion and a letrec, define: expt: (-> (number) number) Examples: (expt 4 0) = 1 (expt 3 1) = 3 (expt 3 2) = 9 (expt 3 3) = 27 ------------------------------------------ *** free and bound variables ------------------------------------------ FREE VARIABLES IN LET AND LETREC x \in FV(E) if E is (let ((v1 E1) (v2 E2) ... (vn En)) B) and x \in FV(E) if E is (letrec ((v1 E1) (v2 E2) ... (vn En)) B) and FOR YOU TO DO Define the BV(E) when E is a let or letrec ------------------------------------------ ... x \in (FV(E1) \union ... \union FV(En)) \union (FV(B) - {v1,...,vn}) show how this is the save as x \in FV((lambda (v1 ... vn) B) E1 ... En) ... x \in (FV(E1) \union ... \union FV(En) \union FV(B)) - {v1,...,vn} *** summary Q: What is the region of a variable definition in a let compared to a letrec? In a let the region of the variable declarations v1, v2, ... , vn includes only the expression B. Whereas, in a letrec the region of the variable declarations v1, v2, ... , vn includes the expressions E1, E2, ... , En, and B Q: What are the advantages of using letrec? + sometimes faster (can avoid passing arguments repeatedly) ++ control of global namespace + access control (no one outside can use local procedure) Q: Any disadvantages? - difficult to test the local helping procedures! *** semantics (optional) For now, we'll only do this for one binding, and 1-argument procedures as well, same ideas apply to the general case, but there are more complications (see below) ------------------------------------------ SEMANTICS OF LETREC Want it to be a syntactic sugar. So want some Y such that: (letrec ((var lexp)) body) = (let ((var (Y lexp))) body) Y should have "unrolling property": ((Y lexp) arg ...) = (lexp[(Y lexp)/var] arg ...) This allows one to expand out enough recursive calls to evaluate a call. ------------------------------------------ ------------------------------------------ UNROLLINGS (letrec ((fact ;; TYPE: (-> (number) number) (lambda (n) (if (zero? n) 1 (* n (fact (- n 1))))))) (fact 3)) = ((Y (lambda (n) (if (zero? n) 1 (* n (fact (- n 1)))))) 3) = ((lambda (n) (if (zero? n) 1 (* n ((Y (lambda (n) (if (zero? n) 1 (* n (fact (- n 1)))))) (- n 1))))) 3) = (* 3 ((Y (lambda (n) (if (zero? n) 1 (* n (fact (- n 1)))))) 2)) ------------------------------------------ Q: What's this if we unroll it again? = (* 3 ((lambda (n) (if (zero? n) 1 (* n ((Y (lambda (n) (if (zero? n) 1 (* n (fact (- n 1)))))) (- n 1))))) 2)) ------------------------------------------ MAKING Y PRACTICAL Y has to satisfy the unrolling property: ((Y lexp) args) = (lexp[(Y lexp)/var] args) But this isn't a sugar yet, because - haven't written Y yet - can't write it, because Y doesn't have access to the code in lexp in order to do the substitution. Solution: - pass (lambda (var) lexp) to Y, so can make substitution, because ((lambda (var) lexp) z) = lexp[z/var] ------------------------------------------ ------------------------------------------ THE NEW SUGAR (letrec ((var lexp)) body) = (let ((var (Y (lambda (var) lexp)))) body) Y must satisfy: ((Y (lambda (var) lexp)) arg ...) = (lexp[(Y (lambda (var) lexp))/var] arg ...) = letting g = (lambda (var) lexp), Y must satisfy: ((Y g) arg ...) = (g (Y g) arg ...) So just use that to define it: (define Y ;; TYPE: (-> ((-> ((-> (datum ...) T)) ;; (-> (datum ...) T))) ;; (-> (datum ...) T)) (lambda (g) (lambda args (apply (g (Y g)) args)))) ------------------------------------------ by definition of application (backwards!) ... (((lambda (var) lexp) (Y (lambda (var) lexp))) arg ...) g is called a generator, as in the following Q: does Y have the unrolling property? you bet! Y doesn't have to be written recursively, see section 4.4 for details, also $PUB/lib/y.scm ------------------------------------------ EXAMPLE (define fact-gen ;; TYPE: (-> ((-> (number) number)) ;; (-> (number) number)) (lambda (f) (lambda (n) (if (zero? n) 1 (* n (f (- n 1))))))) (let ((fact (Y fact-gen))) (fact 2)) = ((Y fact-gen) 2) = ((fact-gen (Y fact-gen)) 2) = ((lambda (n) (if (zero? n) 1 (* n ((Y fact-gen) (- n 1))))) 2) = (* 2 ((Y fact-gen) 1)) ------------------------------------------ Note that fact-gen is not recursive! **** multiple bindings (can omit) To deal with complications of multiple bindings, put them in a list, use extraction procedures to get them. ------------------------------------------ SEMANTICS FOR MULTIPLE BINDINGS (letrec ((var1 exp1) ... (varn expn)) body) = (let ((*list-of-defs* (Y (lambda (*list-of-defs*) (list (apply (lambda (var1 ... varn) exp1) *list-of-defs*) ... (apply (lambda (var1 ... varn) expn) *list-of-defs*) ))))) (apply (lambda (var1 ... varn) body) *list-of-defs*)) where *list-of-defs* is not free in the original letrec. ------------------------------------------