Com S 342 meeting -*- Outline -*- Bring to class copies of the following files from $PUB/lib: sym-exp.scm lambda-1.scm * recursively specified programs (2.2) advertisement: this may seem to be just for lists at first, but also applies to languages (grammars), and other recursive data (like windows ...) ** tips (summary) warning: be systematic! I'm trying to give you another way to think; the usual imperative way (try something and debug) wastes lots of time (hours for 4 line program). There is no way you'll get the right answer quickly by tinkering and debugging. ------------------------------------------ TIPS FOR REDUCING HOMEWORK TIME FOR RECURSIVE PROGRAMS 1. Write out your own examples if needed a. base case(s) b. related simpler example 2. What are the types? Write it down (define num-of ;; TYPE: ------------------------------------------ ... (num-of 'a (parse-sym-exp 'a)) ==> 1 (num-of 'a (parse-sym-exp 'b)) ==> 0 (num-of 'x (parse-sym-exp '(x y (x)))) ==> 2 (num-of 'x (parse-sym-exp '(y (x)))) ==> 1 ... (-> (symbol sym-exp) number) ------------------------------------------ TIPS CONTINUED 3. Use an outline that matches the grammar for the input data type. ::= | ::= ( {}* ) 4. Use helping procedures for the types. (load-from-lib (define num-of ;; TYPE:(-> (symbol sym-exp) ;; number) ------------------------------------------ (load-from-lib "sym-exp") (define num-of ;; TYPE: (-> (symbol sym-exp) number) (lambda (sym symexp) (if (sym-exp-symbol? symexp) (if (eq? sym (sym-exp->symbol symexp)) 1 0) (num-of-slist sym (sym-exp->s-list symexp))))) ------------------------------------------ MORE TIPS 5. One type, one grammar, one recursion. Don't mix 2 recursions in a procedure. 6. Recurse for each helping procedure. ------------------------------------------ ;; Examples: ;; (num-of-slist 'x (parse-s-list '())) ==> 0 ;; (num-of-slist 'x (parse-s-list '(x y (x)))) ==> 2 ;; (num-of-slist 'x (parse-s-list '(y (x)))) ==> 1 (define num-of-slist ;; TYPE: (-> (symbol (list sym-exp)) number) (lambda (sym slist) (if (null? slist) 0 (+ (num-of sym (car slist)) (num-of-slist sym (cdr slist)))))) summary: pay attention to the data, make your program match the grammar ** deriving recursive programs from examples ------------------------------------------ DERIVATION FROM RELATED EXAMPLES (fact 5) = 120 (fact 4) = 24 (fact 3) = 6 (fact 0) = 1 ------------------------------------------ write out the right hand sides as ... = (* 5 (fact 4)) = (* 4 (fact 3)) Q: What's the type? What's the relevant inductive definition? ::= 0 | (succ ) Q: What are the cases? Q: What is the answer for the base case? Q: How to decompose? Where is the recursion? Q: How do we get from the answer for the recursion to what we want? look at the related example, generalize. Point out: basis: (zero? n), returns 1 the "one step": n the "rest of the journey": (fact (- n 1)) combining the one step with the rest of the journey: * ------------------------------------------ HOW IT WORKS (fact 5) = ((lambda (n) (if ...)) 5) = (if (zero? 5) 1 (* 5 (fact (sub1 5)))) = (* 5 (fact (sub1 5))) = (* 5 (fact 4)) = (* 5 (* 4 (fact 3))) = (* 5 (* 4 (* 3 (fact 2)))) = (* 5 (* 4 (* 3 (* 2 (fact 1))))) = (* 5 (* 4 (* 3 (* 2 (* 1 (fact 0)))))) = (* 5 (* 4 (* 3 (* 2 (* 1 1))))) = (* 5 (* 4 (* 3 (* 2 1)))) = (* 5 (* 4 (* 3 2))) = (* 5 (* 4 6)) = (* 5 24) = 120 ------------------------------------------ go over the substitution carefully, then faster ------------------------------------------ FLAT RECURSION ON LISTS (map add1 '(4 5 6)) ==> (5 6 7) (map add1 '(5 6)) ==> (6 7) (map add1 '(6)) ==> (7) (map add1 '()) ==> () (define map ;; TYPE: (-> ((-> (S) T) (list S)) ;; (list T)) (lambda (f ls) ------------------------------------------ Q: What's the outline? talk about the "anti-subtraction idea" Q: how do you make 5 from 4? add 1 to it Q: how do you make the list (5 6 7) from the lists (4 5 6) and (6 7)? now generalize. ------------------------------------------ FOR YOU TO DO (IN PAIRS) (xerox '(3 9 2)) ==> (3 3 9 9 2 2) (xerox '(9 2)) ==> (9 9 2 2) (xerox '()) ==> () ------------------------------------------ Q: What's a related example? (xerox '(9 2)) ==> (9 9 2 2) Q: What's the type? Q: How do you make (3 9 9 2 2) from (3 9 2) and (9 9 2 2)? Q: How do you make (3 3 9 9 2 2) from (3 9 2) and (3 9 9 2 2)? Do more in discussion sections, and help sessions. ** deriving programs from BNF specifications of argument types (2.2.1) *** recursion over flat lists why? recursion used extensively in grammars for programming languages matching recursion produces a "well-structured" program, that is easy to write and understand ------------------------------------------ RECURSION PATTERNS THAT MATCH THE GRAMMAR FOR INPUT (remove-first 'x '()) ==> () (remove-first 'x '(x y z x)) ==> (y z x) (remove-first 'x '(a x y z)) ==> (a y z) Grammar for input data: ::= () | ( . ) Outline that matches grammar: (define remove-first ;; TYPE: (-> (symbol (list symbol)) ;; (list symbol)) (lambda (s los) ------------------------------------------ Q: What examples are we missing? Q: Which argument has an inductively specified type? so the outline, which gives partial credit, is 2 cases, with a test for the first also in the 2nd case, will recurse on cdr if need be because that's where the grammar is recursive in the second case there are also 2 cases but those are not the context-free cases. finish writing this, by working with the examples... summary: it's the input data that determines the overall structure. ------------------------------------------ FOR YOU TO DO (IN PAIRS) (remove 'x '()) ==> () (remove 'x '(x y z x)) ==> (y z) (remove 'x '(a x y z x)) ==> (a y z) Any other examples you want? What's the type? What's the structure of the code? Write it! ------------------------------------------ Q: How does this differ from remove-first? *** recursion over s-lists and sym-exps (trees) ------------------------------------------ S-LIST AND SYM-EXP RECURSION (subst 'x 'y (parse-s-list '())) ==> () (subst 'k 'a (parse-s-list '(a b c d a))) ==> (k b c d k) (subst 'b 'a (parse-s-list '((a a) (c)))) ==> ((b b) (c)) (subst 'k 'a (parse-s-list '(a ((b () a)) ((a ()))))) ==> (k ((b () k)) ((k ()))) ::= ( {}* ) ::= | (define subst ;; TYPE: (-> (symbol symbol ------------------------------------------ Q: any other examples we could use? Q: What's the type? ... (list sym-exp)) (list sym-exp)) We called (tree symbol) in Com S 227. Q: What does the grammar tell us about cases? recursion? null, others, recursion from a helping procedure Compare the following to the book; this version uses the helpers in sym-exp.scm (load-from-lib "sym-exp") ;; Examples: ;; (subst 'y 'x (parse-s-list '())) ==> () ;; (subst 'y 'x (parse-s-list '(x y () (x)))) ==> (y y () (y)) ;; (subst 'y 'x (parse-s-list '(y () (x)))) ==> (y () (y)) (define subst ;; TYPE: (-> (symbol symbol (list sym-exp)) (list sym-exp)) (lambda (new old slist) (if (null? slist) slist (cons (subst-sym-exp new old (car slist)) (subst new old (cdr slist)))))) ;; Examples: ;; (subst-sym-exp 'x 'a (parse-sym-exp 'a)) ==> x ;; (subst-sym-exp 'x 'a (parse-sym-exp 'b)) ==> b ;; (subst-sym-exp 'x 'a (parse-sym-exp '(x y (x)))) ;; ==> (x y (x)) ;; (subst-sym-exp 'x 'a (parse-sym-exp '(y (x)))) ;; ==> (y (x)) (define subst-sym-exp ;; TYPE: (-> (symbol symbol sym-exp) sym-exp) (lambda (new old symexp) (if (sym-exp-symbol? symexp) (if (eq? old (sym-exp->symbol symexp)) (symbol->sym-exp new) symexp) (s-list->sym-exp (subst new old (sym-exp->s-list symexp)))))) Note (over and over!) how the recursion pattern matches the grammar. Summary: the question to ask is: what do we do to get the answer from the list of answers for the sym-exps? ------------------------------------------ FOR YOU TO DO (IN PAIRS) Using the sym-exp helpers. Write: remove-sym-exp : (-> (symbol sym-exp) (list sym-exp)) remove-s-list : (-> (symbol (list sym-exp)) (list (list sym-exp))) Examples: (remove-sym-exp 'z (parse-sym-exp 'a)) ==> (a) (remove-sym-exp 'z (parse-sym-exp 'z)) ==> () (remove-sym-exp 'z (parse-sym-exp '())) ==> (()) (remove-sym-exp 'z (parse-sym-exp '(w z (y z) () (x)))) ==> ((w (y) () (x))) (remove-s-list 'z (parse-s-list '())) ==> (()) (remove-s-list 'z (parse-s-list '(z (y z) () (x)))) ==> (((y) () (x))) ------------------------------------------ (load-quietly-from-lib "sym-exp.scm") (define remove-sym-exp ;; TYPE: (-> (symbol sym-exp) (list sym-exp)) (lambda (sym symexp) (if (sym-exp-symbol? symexp) (if (eq? sym (sym-exp->symbol symexp)) '() (list symexp)) (list (s-list->sym-exp (car (remove-s-list sym (sym-exp->s-list symexp)))))))) (define remove-s-list ;; TYPE: (-> (symbol (list sym-exp)) (list (list sym-exp))) (lambda (sym slst) (list (if (null? slst) '() (append (remove-sym-exp sym (car slst)) (car (remove-s-list sym (cdr slst)))))))) *** using map talk about how to do this using map, and how to relate that to the Kleene-star grammars, also when you can and can't use map ;(define remove-s-list ; ;; TYPE: (-> (symbol (list sym-exp)) (list (list sym-exp))) ; (lambda (sym slst) ; (list (apply append (map (lambda (sexp) (remove-sym-exp sym sexp)) ; slst))))) *** recursion over lambda calculus grammar ------------------------------------------ RECURSION OVER LAMBDA-1 (count-varrefs (parse-lambda-1 'x))) ==> 1 (count-varrefs (parse-lambda-1 '(x y))) ==> 2 (count-varrefs (parse-lambda-1 '(lambda (x) x))) ==> 1 (count-varrefs (parse-lambda-1 '(f (lambda (x) x)))) Grammar: (define count-varrefs ;; TYPE: (lambda (exp) ------------------------------------------ ... ::= | (lambda () ) | ( ) ::= ::= Q: Would more examples help? Q: What other questions should we ask? ... (load-from-lib "lambda-1.scm") (define count-varrefs ; TYPE: (-> (lambda-1-exp) number) (lambda (exp) ;; ENSURES: result is the number of s in exp (if (varref? exp) 1 (if (lambda? exp) (count-varrefs (lambda->body exp)) (+ (count-varrefs (app->rator exp)) (count-varrefs (app->rand exp))))))) Write also the helping, or show them from the handout: Let's define a (set symbol) as a (list symbol) without duplicates ------------------------------------------ FOR YOU TO DO (IN PAIRS) Using the helping procedures varref?, lambda?, varref->var, lambda->body, app->rator, app->rand, set-of, set-union write a procedure for: (all-varrefs (parse-lambda-1 'x)) ==> (x) (all-varrefs (parse-lambda-1 '(x y))) ==> (x y) (all-varrefs (parse-lambda-1 '(lambda (x) y))) ==> (y) (all-varrefs (parse-lambda-1 '(f (lambda (x) (x (f x)))))) ==> (f x) Grammar: ::= | (lambda () ) | ( ) ::= ::= ------------------------------------------ order doesn't matter in the answers all-varrefs: (-> (lambda-1-exp) (set symbol)) (define all-varrefs ;; TYPE: (-> (lambda-1-exp) (list symbol)) (lambda (exp) ;; ENSURES: result is a list of all the s in exp ;; (this list may contain duplicate occurrences of symbols). (if (varref? exp) (list (varref->var exp)) (if (lambda? exp) (all-varrefs (lambda->body exp)) (append (all-varrefs (app->rator exp)) (all-varrefs (app->rand exp))))))) Q: How would this change if we added quote to the grammar? ** tail recursion (p. 50) important for efficiency ------------------------------------------ FULL vs. TAIL RECURSION def: a procedure is tail recursive if full recursion trace: (list-sum '(3 5 7)) = (+ 3 (list-sum '(5 7))) = (+ 3 (+ 5 (list-sum '(7)))) = (+ 3 (+ 5 (+ 7 (list-sum '())))) = (+ 3 (+ 5 (+ 7 0))) = (+ 3 (+ 5 7)) = (+ 3 12) = 15 tail recursion trace: (list-sum '(3 5 7)) = (list-sum-iter '(3 5 7) 0) = (list-sum-iter '(5 7) 3) = (list-sum-iter '(7) 8) = (list-sum-iter '() 15) = 15 ------------------------------------------ ... it has no pending computations when makes a recursive call point out the *pending computations* in the fully recursive version Q: Which of these is more like a while loop? This is how you design a tail recursive program, by designing the sequence of iterates. ------------------------------------------ FULL vs. TAIL RECURSION (define list-sum ; fully recursive ;; TYPE: (-> ((list number)) number) (lambda (lon) (if (null? lon) 0 (+ (car lon) (list-sum (cdr lon)))))) (define list-sum ; tail recursive ;; TYPE: (-> ((list number)) number) (lambda (lon) ------------------------------------------ ... (list-sum-iter lon 0))) (define list-sum-iter ;; TYPE: (-> ((list number) number) ;; number) (lambda (lon acc) (if (null? lon) acc (list-sum-iter (cdr lon) (+ (car lon) acc))))) ------------------------------------------ FOR YOU TO DO (IN PAIRS) Write a tail-recursive procedure for: (reverse '()) ==> () (reverse '(a b c)) ==> (c b a) (reverse '(x y z h)) ==> (h z y x) ------------------------------------------ Hint: use an accumulator emphasize that non-tail solutions won't do. Q: do our questions still work? yes, but you also have to think about: - using an iterator - designing the sequence of iterates ------------------------------------------ WHEN YOU NEED AN ACCUMULATOR When working with vectors (define vector-sum ;; TYPE: (-> ((vector number)) number) (lambda (von) (partial-vector-sum von (vector-length von)))) (define partial-vector-sum ;; TYPE: (-> ((vector number) number) ;; number) (lambda (von n) (if (zero? n) 0 (+ (vector-ref von (- n 1)) (partial-vector-sum von (- n 1)))))) ------------------------------------------ ------------------------------------------ WHEN TO USE TAIL RECURSION 1. When working with strings or vectors (define vector-sum ;; TYPE: (-> ((vector number)) number) (lambda (von) (partial-vector-sum von (vector-length von) 0))) (define partial-vector-sum ;; TYPE: (-> ((vector number) number ;; number) ;; number) (lambda (von n acc) (if (zero? n) (partial-vector-sum von (- n 1)) (+ (vector-ref von (- n 1)) acc)))) ------------------------------------------ ... acc ------------------------------------------ WHEN TO USE TAIL RECURSION 2. To return directly to caller (since no pending computations) (define list-product ;; TYPE: (-> ((list number)) number) (lambda (lon) (prod-iter lon 1))) (define 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))))))) ------------------------------------------ Q: What in C/C++ is like a tail recursion? while loop, for loop... ------------------------------------------ CORRESPONDENCE TO WHILE LOOP struct Cons {int car; Cons *cdr}; // C++ typedef Cons *ConsPtr; int list_product(ConsPtr *lon) { int acc = 1; while (!(lon == NULL)) { if (lon->car == 0) { return 0; } else { acc = acc * lon->car; lon = lon->cdr; } } return acc; } (define list-product ; Scheme (lambda (lon) (prod-iter lon 1))) (define prod-iter (lambda (lon acc) (if (null? lon) acc (if (zero? (car lon)) 0 (prod-iter (cdr lon) (* acc (car lon))))))) ------------------------------------------ ** summary Q: What are the key ideas for designing tail recursion? correspondence to while design the iteration (sequence of values of variables) Q: When should you use tail recursion? for vectors when need to return directly to caller for other efficiency reasons (less stack, etc.) Q: How do you design a fully recursive program? think of examples, how to get answer from cdr of problem's ans Q: What are the questions to ask? what's the first step, what's the rest, how to combine...