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.