Abstracting Patterns of Recursion

Chapter: Abstracting Patterns of Recursion

In the previous examples you have seen numerous procedures which perform recursion on lists using similar steps.

Functions such as member?, norm, multirember, multiinsert, etc... all use the type of recursion pattern.

Here is the abstract notion we wish to capture:

(define list-recur
  (lambda (recur-case base-case ls)
    (if (null? ls)
	base-case
	(recur-case
	  (car ls)
          (list-recur recur-case base-case (cdr ls))))))

For member?, the base case is #f. recur-case is a procedure of two arguments, the first being the car of the list and the second the result of recurring on the cdr.

Q. 11
What might the recur-case procedure for member? look like?


We can now define member? in terms of list-recur. We call our procedure member-abst? to distinguish it from the usual member?

(define member-abst?
  (lambda (item ls)
    (list-recur (lambda (ca cd)
	          (if (eq? ca item)
		      #t
                      cd))
		#f
		ls)))

Here is a cleaner version of list-recur which uses letrec to avoid passing unnecessary arguments in the recursion:

(define list-recur
  (lambda (recur-case base-case lyst)
    (letrec ((flat-recur
	(lambda (l)
	  (if (null? l)
              base-case
              (recur-case (car l) (flat-recur (cdr l)))))))
	(flat-recur lyst))))

The procedure list-recur is also known as fold because it abstracts the notion of combining (or folding) some binary function over the elements of the list. We will use the list-recur and fold interchangeably: (define fold list-recur).

Here is a fold example in which the base value is 6. Usually, the base value in a recursion is more likely to be something like 0 or 1 or '(). This example shows that fold is general enough to handle some variety.

> (fold + 6 '(1 2 14 8))
31

Q. 12
What is (fold + 6 '())



Exercise 5




Exercise 6

Consider the member-abst? function we defined using list-recur. Modify list-recur to print debugging information on each recursive call to flat-recur. Use a global flag called verbose to control debugging. Use display or printf to print out the current value of l if verbose is #t. list-recur now looks like this:

(define list-recur
  (lambda (recur-case base-case lyst)
    (letrec ((flat-recur
	      (lambda (l)
		(if verbose
		    (printf "~s~%" l)
		    'do-not)
		(if (null? l)
		    base-case
		    (recur-case (car l) (flat-recur (cdr l)))))))
      (flat-recur lyst))))

Now run member-abst? on a variety of inputs including lists with and without the item being searched for and with the item being searched for in different locations in the list. Compare calls to member-abst? with calls to a more traditional version of member? defined as follows:

(define member?
  (lambda (item l)
     ... debug stuff goes here ...
    (cond
     ((null? l) #f)
     ((eq? (car l) item) #t)
      (else (member? item (cdr l))))))

Here are the sort of test to run:

> (define verbose #t)
> (member? 6 '(1 2 3 4 5 6))
   ...debug stuff...
> (member-abst? 6 '(1 2 3 4 5 6))
   ...debug stuff...
> (member? 3 '(1 2 3 4 5 6))
   ...debug stuff...
> (member-abst? 3 '(1 2 3 4 5 6))
   ...debug stuff...

Consider the number of recursive calls in each case. Write up an explanation for the difference in the number of calls in each case.



The exercise shows that we must be careful to consider efficiency when using these sort of abstractions. If the abstraction is too general, we may lose some performance. Consider the procedure member?. The abstract notion which more closely matches member? is the idea of trying to find something and then reporting a result based on whether the item has been found.


Exercise 7

Write a function called find-and-report which abstracts the notion of recursing down a list and checking if a predicate is true on each element of the list. If it finds an element for which the predicate returns true, it calls a report function with the current list as the only argument. Otherwise the recursion continues on the cdr of the list. The new procedure can be used as follows:

(define are-there-any-zeros?
  (lambda (ls)
    (find-and-report zero? (lambda (x) #t) #f ls)))

> (are-there-any-zeros? '(19 6 5 3 0 1 2 0 15))
#t

In this example zero? is the predicate, the lambda expression is the reporting function which simply returns #t, #f is the base value, and ls is the list to be checked.



rhyspj@gwu.edu