More Higher-Order Procedures
Built-in 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. You already know about the basic use of the map procedure. After our discussion above, you also know how one might implement it. It turns out that map has some additional capabilities. For example, you can apply a procedure to multiple lists (in which case it takes the corresponding value from each list). 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)
Other Common Design Patterns 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: fold, 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 fold (see the lab), we might define sum as (define sum (lambda (lst) (fold + 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 would use > (tally odd? lst) Similarly, to count the number of sevens or elevens in a list, we might use > (tally (lambda (v) (or (= 7 v) (= 11 v))) lst) select, which selects all elements of a list that match a particular predicate. 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)
Building Procedures We can use a more advanced technique to build a procedure that builds list predicates. We'll write a procedure, make-list-pred that takes one parameter, a predicate, and returns a procedure of one parameter, a list, and determines whether the predicate holds for every element of the list. Since we need to build a recursive procedure, we'll use letrec to build that procedure. It's now even simpler to define our favorite list predicates. > (define all-int? (make-list-predicate integer?)) > (define all-rgb? (make-list-predicate rgb?)) > (define all-odd? (make-list-predicate odd?)) > (all-int? null) #t > (all-int? (list 1 2 3)) #t > (all-int? (list 1 2.5 3)) #f > (all-int? 1) #f > (all-odd? (list 1 3 5)) #t > (all-odd? (list 1 2 3 4 5)) #f > (all-odd? (list 1 2.5 2)) Error: Requires integer. That last error message is a direct result of the author ignoring the preconditions of make-list-predicate. Since odd? can't be applied to all values, (make-list-predicate odd?) is not guaranteed to work. There's also an important benefit to using higher-order procedures, suggested by the final all-int? example. As that example suggests, we've succeeded in not only defining the procedure more concisely, we've also defined it more generally. (The old all-int? would crash if given a non-list as a parameter.) That means that all of the procedures we define with make-list-predicate benefit from this change. If we make other improvements, they are automatically propagated to other procedures.
More Common Procedure Builders Here are some common procedures that you might use to build other procedures. Not all are predefined in Scheme. We've included some definitions. You should be able to write the others. (left-section binary-proc arg1) creates a new procedure by filling in the first argument of a binary procedure. For example, we might define a procedure that multiplies a value by 2 with > (define mul2 (left-section * 2)) The left-section procedure is surprisingly powerful. Note that with a little more work, we could even use it to define a variant of make-list-predicate. (define make-list-predicate (lambda (pred?) (left-section all pred?))) The code for left-section is relatively straightforward. (right-section binary-proc arg2) creates a new procedure by filling in the second argument of a binary procedure. For example, we might define a procedure that subtracts 3 from a value with > (define sub3 (right-section - 3)) (make-filter pred?) creates a new filter that uses pred? to determine whether or not to keep elements. Note that make-filter is very similar to remove except that remove takes two parameters (the predicate and the list) and returns a list, while make-filter takes one parameter (the predicate) and returns a function whose purpose is to read a list and remove elements that match the predicate. (conjunction pred1? pred2? ... predn?) create a news predicate that holds only if all of pred1?, pred2?, through predn? hold (the predicates should be tested in order). We can use conjunction to build a better version of even. (define better-even? (conjunction number? even?)) (disjunction pred1? pred2? ... predn?) creates a new predicate that holds if pred1? holds or pred2? holds or ... or predn? hods. The predicates should be tested in order. (complement pred?) creates a new predicate whose result is the opposite of the result of pred?. This procedure can be defined as (define complement (lambda (pred?) ; Result procedure (lambda (val) (not (pred? val)))))