Com S 342 meeting -*- Outline -*- * local binding constructs See R5RS for details ** let (SICP pp. 63-64) ------------------------------------------ THE PROBLEMS SOLVED BY LET (double-reverse-sublist '((a b) (c d))) ==> ((b a) (b a) (d c) (d c)) (deftype double-reverse-sublist (forall (T) (-> ((list-of (list-of T))) (list-of (list-of T))))) (define (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 (deftype double-reverse-sublist (forall (T) (-> ((list-of (list-of T))) (list-of (list-of T))))) (define double-reverse-sublist (lambda (lst) (if (null? lst) '() ------------------------------------------ ... (cons-twice (reverse (car lst)) (double-reverse-sublist (cdr lst)))))) (deftype cons-twice (forall (T) (-> (T (list-of T)) (list-of T)))) (define cons-twice (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 (deftype double-reverse-sublist (forall (T) (-> ((list-of (list-of T))) (list-of (list-of T))))) (define double-reverse-sublist (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 (deftype double-reverse-sublist (forall (T) (-> ((list-of (list-of T))) (list-of (list-of T))))) (define double-reverse-sublist (lambda (lst) (if (null? lst) '() (cons elem (cons elem ls)) ;; or more usually... (define double-reverse-sublist (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 (SICP p. 391, EOPL p. 23) ------------------------------------------ LET HELPS AVOID NAMESPACE CLUTTER (deftype circle-area (-> (number number) number)) (define circle-area (let ((pi 3.14159)) (lambda (radius) (* pi (* radius radius))))) ------------------------------------------ so you might like to do that for helping procedures... ------------------------------------------ DOES LET WORK FOR PROCEDURES? (deftype list-product (-> ((list-of number)) number)) (define list-product (let ((prod-iter (has-type (-> ((list-of 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 (deftype list-product (-> ((list-of number)) number)) (define list-product (letrec ((prod-iter (has-type (-> ((list-of 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? (has-type (-> (number) boolean) (lambda (n) (if (zero? n) (even? (- n 1)))))) (even? (has-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 (deftype subst (-> (symbol symbol (list-of symbol)) (list-of symbol))) (define subst ------------------------------------------ ... unchanged arguments (lambda (new old ls) (letrec ((helper (has-type (-> ((list-of symbol)) (list-of 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 a letrec, define: expt: (-> (number) number) Examples: (expt 4 0) = 1 (expt 3 1) = 3 (expt 3 2) = 9 (expt 3 3) = 27 ------------------------------------------ *** 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!