In class, we have seen one main example of introducing and using abstract ("higher order") functions. Today we'll see additional common examples and summarize why they are important.
To use abstract functions, you need to change to the Advanced Student language level of DrScheme.
Index:
filter
,
map
,
foldr
,
foldl
,
Summary of abstract functions
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 alternatively 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
; double-nums : list-of-number -> list-of-number ; Purpose: Given a list of numbers, return a list of ; numbers that are twice the value of the corresponding items in ; the original list. (define (double-nums a-lon) (cond [(empty? a-lon) empty] [(cons? a-lon) (cons (* 2 (first a-lon)) (double-nums (rest a-lon)))]))
; lessthan3-nums : list-of-number -> list-of-boolean ; Purpose: Given a list of numbers, return a list of ; booleans that indicate whether the corresponding items in ; the original list are less than 3. (define (lessthan3-nums a-lon) (cond [(empty? a-lon) empty] [(cons? a-lon) (cons (< (first a-lon) 3) (lessthan3-nums (rest a-lon)))]))
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.
|
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 functions we've written fit this pattern, although they might not all be obvious at first.
|
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))...)
|
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.
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.