CS 227 Lecture -*- Outline -*- * Procedural Abstraction of flat recursion (7.4) Now we fulfil the promise made at the start of this chapter: to write down the code for flat recursion once and for all. ** The pattern Recall programs 7.16 (member-c) and 7.17 (apply-to-all). (Apply-to-all is like map-c, but uses letrec.) Two more examples of flat recursion over lists. ------------------ ; - Program 7.18, pg. 213 - (define sum ; TYPE: (-> ((list number)) number) (letrec ((helper (lambda (ls) (if (null? ls) 0 (+ (car ls) (helper (cdr ls))))))) helper)) ------------------ ------------------ ; - Program 7.19, pg. 213 - (define product ; TYPE: (-> ((list number)) number) (letrec ((helper (lambda (ls) (if (null? ls) 1 (* (car ls) (helper (cdr ls))))))) helper)) ------------------ *** Common Features Q: what are the common features of these definitions? -- first 4 lines of letrec the same -- all return helper ------------------ COMMON PARTS (define flat-recur (lambda (_________) (letrec ((helper (lambda (ls) (if (null? ls) ___________ _________________)))) helper))) ------------------ The first lambda is not a common feature. Recall, however that as we abstract out the common features, we will have to wrap the whole thing in a lambda. The extra lambda for member-c and apply-to-all will be taken care of elsewhere. *** Different Features Now we know what the different parts are... **** What they return when the list ls is null. This is called the seed, will be a parameter "seed". ------------------ SEEDS procedure seed ==================== member?-c #f apply-to-all '() sum 0 product 1 ------------------ **** What they do with the car and cdr of ls does not look like something we can capture with a parameter (+ (car ls) (helper (cdr ls))) vs. (cons (proc (car ls)) (helper (cdr ls))) -- We could write a procedure that took the car and cdr of ls as arguments and applied proc to car of ls, then cons to that and helper of (cdr ls). ------------------- (cons (proc (car ls)) (helper (cdr ls))) = (let ((car-ls (car ls)) (h-cdr-ls (helper (cdr ls)))) (cons (proc car-ls) h-cdr-ls)) = ((lambda (car-ls h-cdr-ls) (cons (proc car-ls) h-cdr-ls)) (car ls) (helper (cdr ls))) ------------------- Similarly for member?-c, can use a procedure that calls or and equal? on the car of ls. What is passed as the list-proc is given in the following table. ------------------ LIST-PROCS procedure list-proc ========================================== member?-c (lambda (x y) (or (equal? x item) y)) apply-to-all (lambda (x y) (cons (proc x) y)) sum + = (lambda (x y) (+ x y)) product * = (lambda (x y) (* x y)) ------------------ So make this algorithm a parameter, it is a replacable part of flat recursion. ------------------ ; - Program 7.23, pg. 221 - (define flat-recur ; TYPE: (-> (T (-> (S T) T)) ; (-> ((list S)) T)) (lambda (seed list-proc) ; ENSURES: for example, ; (result (list a b)) ; = (list-proc a (list-proc b seed)) (letrec ((helper ; TYPE: (-> ((list S)) T) (lambda (ls) ; ENSURES: as above (if (null? ls) seed (list-proc (car ls) (helper (cdr ls))))))) helper))) ------------------ *** recreation of the 4 procedures Now, we can recreate the 4 procedures ------------------ RECOVERING THE PROCEDURES (define member?-c (lambda (item) (flat-recur #f (lambda (x y) (or (equal? x item) y))))) (define sum (flat-recur 0 +)) ------------------ Q: Can you write apply-to-all and product? (have them do that) Note that the difference of lambdas shows up here. The extra lambda needed for member?-c is here still in the recreation. It did not go away in using flat-recur because we did not change its type. *** type checking (can omit) Show how the types add up. ------------------ HOW THE TYPES CHECK for sum... 0: number +: (-> (number number) number) flat-recur: (-> (T (-> (S T) T)) (-> ((list S)) T)) so S = number, T = number (flat-recur 0 +) : (-> ((list number)) number) ------------------ This gives the desired type of sum. ------------------ for member?-c... item: S ;; assumption #f : boolean (lambda (x y) (or ... )) : (-> (S boolean) boolean) flat-recur: (-> (T (-> (S T) T)) (-> ((list S)) T)) so T = boolean, S still S (flat-recur #f (lambda (x y) ... )) : (-> ((list S)) boolean) (lambda (item) (flat-recur #f ...)) : (-> (S) (-> ((list S)) boolean)) ------------------ ** Using flat-recur on new examples ------------------ WRITE USING FLAT-RECUR ((filter-in-c odd?) '(1 2 3 4 5)) ==> (1 3 5) ((filter-in-c (lambda (x) (not (= 2 x)))) '(3 2 1 5 7 2 3)) ==> (3 1 5 7 3) filter-in-c: (-> ((-> (S) boolean)) (-> ((list S)) (list S))) ------------------ Want to define this using flat-recur Q: What should the seed be? -- building a list, so want '() Q: What should list-proc be? -- like our remove procedures (remove each copy) -- so cons the car in if it passes, otherwise, return the cdr. ; - Program 7.24, pg. 222 - (define filter-in-c (lambda (pred) (flat-recur '() (lambda (x y) (if (pred x) (cons x y) y))))) ** Summary -- This process of looking for common features of procedures and making a procedure that takes the differing parts as parameters is called *procedural abstraction*. -- Disadvantages --- sometimes less efficient. (footnote p. 220). -- Advantages --- do not have to write the same code many times --- once get it right, do not have to check again. --- programs easier to write and understand.