Fundamentals of CS I (CS151 2001S)

[Current]
[Discussions]
[Glance]
[Honesty]
[Instructions]
[Links]
[News]
[Search]
[Syllabus]
**Primary**

[Examples]
[Exams]
[Handouts]
[Homework]
[Labs]
[Outlines]
[Quizzes]
[Readings]
[Reference]
**Sets**

[Blackboard]
[Scheme Report]
[SamR's Schedule]
[Rebelsky/Fall 2000]
[Walker/Fall2000]
[Stone/Spring2000]
**Links**

In this reading and laboratory, you will investigate the so called
*higher-order procedures* that take procedures as parameters or
return procedures as results.

- Background: Design Patterns
- A Simple Pattern: Apply a Procedure to List Values
- Built-in Higher-Order Procedures
- Anonymous Procedures
- Other Common Design Patterns
- Returning Procedures

One mark of successful programmers is that they identify and remember
common techniques for solving problems. Such abstractions of common
structures for solving problems are often called *patterns* or
*design patterns*. You should already have begun to identify
some patterns. For example, you know that procedures almost always have
the form

(defineprocname(lambda (parameters)body))

You may also have a pattern in mind for the typical recursive procedure over lists:

(defineprocname(lambda (lst) (if (null? lst)base-case(do-something(car lst) (procname(cdr lst))))))

In some languages, these patterns are simply guides to programmers as
they design new solutions. In other languages, such as Scheme, you can
often *encode* a design pattern in a separate procedure.

Let's begin with two similar problems, both of which an instructor might apply to a list of grades after determining that grading was too harsh. That instructor might want to create a new list in which each grade is incremented by 5 or that instructor might want to multiply every value in the list by 5/4.

Here are simple implementations of the two procedures.

;;; Procedure: ;;; add-five-to-all ;;; Parameters: ;;; lst, a list of numbers. ;;; Purpose: ;;; Add 5 to every value in lst. ;;; Returns: ;;; newlst, a list of numbers. ;;; Preconditions: ;;; lst contains only numbers. [Unverified] ;;; Postconditions: ;;; newlst is the same length as lst ;;; Each element of newlst list is five greater than the ;;; corresponding element of lst. (define add-five-to-all (lambda (lst) ; If no elements remain, we can't add 5 to anything, so ; stick with the empty list. (if (null? lst) null ; Otherwise, add 5 to the first number, add 5 to all ; the remaining numbers, and shove 'em together. (cons (+ 5 (car lst)) (add-five-to-all (cdr lst)))))) ;;; Procedure: ;;; scale-all-by-five-fourths ;;; Parameters: ;;; lst, a list of numbers. ;;; Purpose: ;;; Scales every value in a list of numbers by 5/4 ;;; Returns: ;;; newlst, a list of numbers. ;;; Preconditions: ;;; lst contains only numbers. [Unverified] ;;; Postconditions: ;;; newlst is the same length as lst ;;; Each element of newlst list is 5/4 times the ;;; corresponding element of lst. (define scale-all-by-five-fourths (lambda (lst) ; If no elements remain, we can't do any more multiplications, ; so stick with the empty list. (if (null? lst) null ; Otherwise, scale the first number, scale all ; the remaining numbers, and shove 'em together. (cons (* (/ 5 4) (car lst)) (scale-all-by-five-fourths (cdr lst))))))

What do these two procedures have in common? Most of the documentation. They also both return null when given the null list. More importantly, both do something to the car of the list, recurse on the cdr of the list, and then cons the two results together.

Hence, we can design a general pattern for *apply a procedure to
every value in a list*.

;;; Procedure: ;;; do-something-to-all ;;; Parameters: ;;; lst, a list of numbers. ;;; Purpose: ;;; Applys PROCEDURE to each value in a list. ;;; Returns: ;;; newlst, a list of numbers. ;;; Preconditions: ;;; lst contains only numbers. [Unverified] ;;; PROCEDURE is defined, takes one number as a parameter ;;; and returns a number. ;;; Postconditions: ;;; newlst is the same length as lst ;;; Each element of newlst list the result of applying PROCEDURE ;;; to the corresponding element of lst. (define do-something-to-all (lambda (lst) ; If no elements remain, we can't apply anything else, ; so stick with the empty list. (if (null? lst) null ; Otherwise, apply the procedure to the first number, ; apply the procedure to the remaining numbers, and ; put the results back together into a list. (cons (PROCEDURE (car lst)) (do-something-to-all (cdr lst))))))

Where does `PROCEDURE`

come from? We could define it first
(as the preconditions suggest). For example, here's an application of
`do-something-to-all`

in which we add 5 to each value.

>(define PROCEDURE (lambda (val) (+ val 5)))>(PROCEDURE 4)9 >(do-something-to-all (list 1 2 3))(6 7 8)

Similarly, we can multiply all the values in a list by 4/5 by redefining PROCEDURE appropriately.

>(define PROCEDURE (lambda (val) (* (/ 5 4) val)))>(PROCEDURE 4)5 >(do-something-to-all (list 1 2 3))(5/4 5/2 15/4)

You may find that this is inelegant (I certainly do). It also won't
work for all cases. For example, what if we want to add five to all
the numbers in a list and then scale by five-fourths? We'd have to
redefine `PROCEDURE`

in the middle of our code. Ugly.

Is there a better solution? **Yes!** Instead
of forcing `PROCEDURE`

to be defined before we call
`do-something-to-all`

, *we can make the procedure a
parameter to the pattern*. For example, here's a renamed version
of `do-something-to-all`

that takes the extra parameter.

;;; Procedure: ;;; apply-all ;;; Parameters: ;;; proc, a procedure that takes one parameter (a number) and ;;; returns a number. ;;; lst, a list of numbers. ;;; Purpose: ;;; Applys proc to each value in a list. ;;; Returns: ;;; newlst, a list of numbers. ;;; Preconditions: ;;; lst contains only numbers. [Unverified] ;;; proc is a procedure that maps numbers to numbers. [Unverified] ;;; Postconditions: ;;; newlst is the same length as lst ;;; Each element of newlst list the result of applying proc ;;; to the corresponding element of lst. (define apply-all (lambda (proc lst) ; If no elements remain, we can't apply anything else, ; so stick with the empty list. (if (null? lst) null ; Otherwise, apply the procedure to the first number, ; apply the procedure to the remaining numbers, and ; put the results back together into a list. (cons (proc (car lst)) (apply-all proc (cdr lst))))))

Here are some simple tests

>(let ((addfive (lambda (v) (+ 5 v)))) (apply-all addfive (list 1 2 3)))(6 7 8) >(let ((square (lambda (v) (* v v)))) (apply-all square (list 1 2 3)))(1 4 9) >(apply-all list (list 1 2 3))((1) (2) (3)) >(apply-all odd? (list 1 2 3))(#t #f #t)

You should have observed a very important (and somewhat stunning) moral
from this example, *procedures can be parameters to other procedures*.
We call the procedures that take other procedures as parameters
*higher-order procedures*.

We have seen that it is possible to write our own higher-order procedures. Scheme also includes a number of built-in higher-order procedures. You can read about many of them in section 6.4 of the Scheme report (r5rs), which is available through the DrScheme Help Desk. Here are some of the more popular ones.

The `map`

procedure takes as arguments a procedure and one
or more lists and builds a new list whose contents are the result of
applying the procedure to the corresponding elements of each list.
(That is, the ith element of the result list is the result of applying
the procedure to the ith element of each source list.)
`map`

is essentially the same as our `apply-all`

except that `map`

does not guarantee that it steps
through the list in a left-to-right order.

One of the most important built-in higher-order procedures is
`apply`

. which takes a procedure and a list as arguments
and invokes the procedure, giving it the elements of the list as its
arguments:

>(apply string=? (list "foo" "foo"))#t >(apply * (list 3 4 5 6))360 >(apply append (list (list 'a 'b 'c) (list 'd) (list 'e 'f) null (list 'g 'h 'i)))(a b c d e f g h i)

Recall that in the examples above we wrote something like

>(let ((square (lambda (v) (* v v)))) (map square (list 1 2 3)))

Let's take this apart. This says to make `square`

be
the name of a procedure of one parameter that squares the parameter.
It then says to apply the procedure to a list. Scheme
substitutes the procedure for `square`

(just as it
substitutes a value for a named value). Hence, to Scheme, the
code above is essentially equivalent to

(map (lambda (v) (* v v)) (list 1 2 3))

Because Scheme does this substitution, we can also do it. That is, we can write the previous code and have Scheme execute it.

>(map (lambda (v) (* v v)) (list 1 2 3))(1 4 9)

So, what does this code say? It says ``Apply a procedure that squares
its argument to ever element of the list (1 2 3)''. Do we care what
that procedure is named? No. We describe procedures without names
as *anonymous procedures*. You will find that you frequently
use anonymous procedures with design patterns and higher-order procedures.

At this point, you've seen many other design patterns that typically involve recursion. You may find it valuable to design corresponding procedures to encapsulate those patterns. Here are some of the patterns you may wish to think about:

*insert*, which inserts a binary (two parameter) operation between all
of values in a list. For example, `sum`

inserts a plus
between neighboring values in a list (i.e., `(sum (list 1 2 3 4))`

is `1+2+3+4`

). Similarly, `product`

inserts
a times between neighboring values.
If we had defined insert (see the lab),
we might define `sum`

as

(define sum (lambda (lst) (insert + lst)))

*tally*, which counts the number of values in a list that
meet a particular predicate. For example, to count the number of
odd values in a list, we'd use

(tally odd? lst)

Similarly, to count the number of sevens or elevens in a list, we'd use

(tally (lambda (v) (or (= 7 v) (= 11 v))) lst)

*select*, which selects all elements of a list that match
a particular predicte. For example,

>(select odd? (list 1 2 3 4 5 6 7))(1 3 5 7)

*remove*, which removes all elements of a list that match
a particular predicate. For example,

>(remove odd? (list 1 2 3 4 5 6 7))(2 4 6)

Just as it is possible to use procedures as parameters to procedures, it is also possible to return new procedures from procedures. For example, here is a procedure that takes one parameter, a number, and creates a procedure that multiplies its parameters by that number.

;;; Procedure: ;;; make-multiplier ;;; Parameters: ;;; n, a number ;;; Purpose: ;;; Creates a new procedue which multiplies its parameter by n. ;;; Produces: ;;; proc, a procedure of one parameter ;;; Preconditions: ;;; n must be a number ;;; Postconditions: ;;; (proc v) gives v times n. (define make-multiplier (lambda (n) (lambda (v) (* v n))))

Let's test it out

>(make-multiplier 5)#<procedure> >(define timesfive (make-multiplier 5))>(timesfive 4)20 >(timesfive 101)505 >(map (make-multiplier 3) (list 1 2 3))(3 6 9)

Wednesday, 14 March 2001

- Created. Based on outline 32 from CSC151 2000S.
- Turned bullets into prose.
- Updated code.
- Added new sections (e.g., additional design patterns, returnign procedures from procedures).
- Reformatted.

[Current]
[Discussions]
[Glance]
[Honesty]
[Instructions]
[Links]
[News]
[Search]
[Syllabus]
**Primary**

[Examples]
[Exams]
[Handouts]
[Homework]
[Labs]
[Outlines]
[Quizzes]
[Readings]
[Reference]
**Sets**

[Blackboard]
[Scheme Report]
[SamR's Schedule]
[Rebelsky/Fall 2000]
[Walker/Fall2000]
[Stone/Spring2000]
**Links**

**Disclaimer**:
I usually create these pages on the fly. This means that they
are rarely proofread and may contain bad grammar and incorrect details.
It also means that I may update them regularly (see the history for
more details). Feel free to contact me with any suggestions for changes.

This page was generated by Siteweaver on Thu May 3 23:10:22 2001.

This page may be found at `http://www.cs.grinnell.edu/~rebelsky/Courses/CS151/2001S/higher-order.html`

.

You may validate
this page's HTML.

The source was last modified Wed Mar 14 16:15:06 2001.