Comp 210 Lab 8: Abstract functions

Index: filter, map, foldr, foldl, Summary of abstract functions

In class, we have seen one main example of introducing and using abstract functions. Today we'll see more common examples and summarize why this is important.

To use abstract functions, you need to change to the Advanced Student language level of DrScheme.


filter (Review)

As a reminder, we discussed filter, which can be defined as

     ; filter : (alpha -> boolean) list-of-alpha -> list-of-alpha
     ; Purpose: Returns a list like the one given, except with those
     ; elements not passing the given predicate test removed.
     (define (filter keep? l)
        (cond
           [(empty? l) empty]
           [(cons? l)  (cond
                           [(keep? (first l))
                            (cons (first l) (filter keep? (rest l)))]
                           [else
                            (filter keep? (rest l))])]))
Starting from several examples using specific predicates, we noticed a great deal of similarity. By abstracting away that similarity into a function parameter, we obtained filter.

Also, remember that we could also write

     ; filter : (alpha -> boolean) list-of-alpha -> list-of-alpha
     ; Purpose: Returns a list like the one given, except with those
     ; elements not passing the given predicate test removed.
     (define (filter keep? l)
        (local
           [(define (filter-helper l)
               (cond
                  [(empty? l) empty]
                  [(cons? l)  (cond
                                  [(keep? (first l))
                                   (cons (first l) (filter-helper (rest l)))]
                                  [else
                                   (filter-helper (rest l))])]))]
           (filter-helper l)))
to avoid repeatedly passing around the invariant (i.e., unchanging) predicate paramemter.


map

We are familiar with writing functions such as

and many others of similar form.

Since these functions are so similar, we'd like to package together the similar parts and separate out the different parts. We'll "package" the similar parts as a new function that can take either of the "different" parts as an argument that tells us what to do:

     ; map : ?? (see question below)
     (define (map f l)
        (cond
           [(empty? l) empty]
           [(cons? l)  (cons (f (first l)) (map f (rest l)))]))

     (define (double-num n) (* 2 n))
     (define (double-nums a-lon) (map double-num a-lon))

     (define (lessthan3-num n) (< n 3))
     (define (lessthan3-nums a-lon) (map lessthan3-num a-lon))
map abstracts the general idea of "computing a new element from each old element and building a list of the new elements" away from what specifically you are computing from each element.

Another way to understand map is by the following equation:

     (map f (list x1 ... xN)) = (list (f x1) ... (f xN))
This should be easier to understand since it describes what map does without the usually extraneous details of how it does it.

Q: What is the contract for map? (Consider examples not involving lists of numbers.)

To do:


foldr

We have written many functions to combine elements of a list, e.g., to sum the numbers in a list and to find the maximum element in a list. In each, we have some value base as the result for the empty list and a function f to combine the first element and the result on all the rest of the elements. Combining all the elements means satisfying the following equation:

     (foldr f base (list x1 x2 ... xN)) = (f x1 (f x2 ... (f xN base)))
Many other functions we've written fit the same pattern, although less obviously for now.

To do/Q:

For the curious... To do: map and foldr each abstract part of the standard template for lists. map abstracts the computation on each element of the input list, while foldr abstracts the combining on the first element and the recursive call on the other elements. Write a function map-and-foldr which abstracts all the pieces.


foldl

The mathematically inclined should have noticed that foldr groups the binary operations right-associatively. Thus the "r" in the name. What if we want to group left-associatively? Well, we also have

     (foldl f base (list x1 x2 ... xN)) = (f xN ... (f x2 (f x1 base))...)

To do/Q:

Defining foldl would be difficult for you at this point in the course. (Try it later, when we talk about accumulators.)


Summary of abstract functions

This idea is very common because it is both powerful and convenient. By using abstract functions to group all the common similar elements of many functions, we can concentrate on what's different. This allows us to write shorter code that is also cleaner and more understandable.

These are certainly not the only abstract functions, but filter, map, foldr, and foldl have proven to be particularly useful because they correspond to common forms of computations on lists. Because Scheme has lists built in and since these functions are so useful, Scheme has them predefined. But the same ideas are also useful on other data structures. To do: Write an abstract mapping function for some version of binary trees.

Usually, using abstract functions takes the place of following our standard templates. So, what happens to our design recipes? In the short term, while you are still getting used to abstract functions, we strongly recommend that you first follow the design recipe, and then go back and edit your code to use abstract functions where applicable. In the long term, you will learn to identify common patterns before you actually write code and be able to go directly to writing the shorter versions using abstract functions. You will probably find filter and map the easier to use at first. foldr and foldl are more general and take more practice to get comfortable with. You will get practice on homeworks and the next two exams.