Procedures are data in Scheme: They can be passed as arguments to other procedures, returned as results by other procedures, and bound to variables. It is common, in the course of a Scheme program, to invoke a procedure that is constructed -- computed -- by the program itself. This gives Scheme a kind of expressive power that few other programming languages share.
Let's consider, first, a simple case in which a procedure is passed as an
argument to another procedure. The every? procedure defined
below takes two arguments, a predicate pred and a list
ls, and determines whether or not the predicate is true of
every element of the list.
(define every?
(lambda (pred ls)
; precondition tests
(if (not (procedure? pred))
(error 'every? "The first argument must be a predicate"))
(if (not (list? ls))
(error 'every? "The second argument must be a list"))
(let kernel ((rest ls))
(or (null? rest)
(and (pred (car rest)) ; Invoke the predicate here!
(kernel (cdr rest)))))))
The kernel procedure returns #t immediately if it
is given an empty list. Otherwise, it takes the list apart into its car
and its cdr and applies the given predicate to the car. If the predicate
returns #f, this value is immediately returned by
kernel; but if the predicate is true of the car of the list,
kernel is invoked recursively to continue the investigation,
searching through the cdr to make sure that the predicate is true of all of
its elements as well.
Even though it is passed in as an argument, one can issue a procedure call
to the predicate, invoking it under its local name, pred.
Whatever predicate is passed in will be invoked at that point.
The predicate that is invoked will be different in different calls to
every?; that's okay with Scheme.
Here are some applications of every?:
> (every? odd? '(1 3 5 7 9)) ; True: 1, 3, 5, 7, and 9 are odd.
#t
> (every? odd? '(1 3 5 7 10)) ; False: 10 is not odd.
#f
> (every? odd? '(1 3 5 8 foo)) ; False: 8 is not odd.
#f
> (every? (lambda (n)
(and (integer? n) (zero? (remainder n 7))))
'(0 -35 91 7 -1001))
; True: every element of the list is an integer that is
; divisible by 7.
#t
> (every? procedure? (list + number? cons procedure? every? list))
; True: +, number?, cons, procedure?, every?, and list are all procedures.
#t
As these examples show, the predicate that is given as the argument to
every? can be either built-in or programmer-defined.
Write and test an analogous procedure any? that determines
whether a given predicate is true of at least one element of a given list.
(Note: every? returns #t if it is given an empty
list; any? should return #f in this case.)
Suppose, next, that one would like to use every? to test
whether all of the elements of a given list are positive even numbers.
One possibility would be to define a special predicate,
positive-and-even?, to test for this specific combination of
traits:
(define positive-and-even?
(lambda (n)
(and (positive? n)
(even? n))))
> (every? positive-and-even? '(8 36 12 2 14))
#t
But this approach would become tiresome if one had to test many such combinations of traits. A better method is to develop a general procedure for combining two predicates into one:
(define conjoin
(lambda (pred-1 pred-2)
; precondition tests
(if (or (not (procedure? pred-1))
(not (procedure? pred-2)))
(error 'conjoin "Both arguments must be predicates"))
; body
(lambda (arg) ; That's right -- return the value of a
(and (pred-1 arg) ; lambda-expression -- a procedure!
(pred-2 arg)))))
> (every? (conjoin positive? even?) '(8 36 12 2 14))
#t
The idea is that conjoin takes two predicates as arguments and
returns a new predicate -- one that combines the conditions imposed by its
arguments. This new predicate is the value of the
lambda-expression marked ``body'' in the source code just
shown. Since conjoin returns a predicate and
every? needs a predicate as its first argument, it's okay to
write a call to conjoin to compute the first argument to
every?.
Two predicates are said to be complementary if each one
returns #t for exactly those arguments for which the other
returns #f. (For instance, even? and
odd? are complementary predicates.) Write and test a
complement procedure that takes a predicate as its argument
and returns a predicate that is complementary to it.
Scheme provides several built-in procedures that take procedures as
arguments. One is map, which takes as arguments a procedure
and a list and applies the procedure to each element of the list,
collecting the results into a list:
(define square
(lambda (n)
(* n n)))
> (map square '(3 -9 4.1 8/3 0))
(9 81 16.81 64/9 0)
The map procedure works as if it were defined like this:
(define our-map
(lambda (operation ls)
(if (null? ls)
'()
(cons (operation (car ls)) (our-map operation (cdr ls))))))
Here's how map could be used to write the
double-each-element procedure from the first lab on recursion.
(define double-each-element
(lambda (ls)
(let ((double (lambda (x) (* x 2))))
(map double ls))))
or, even more simply,
(define double-each-element
(lambda (ls)
(map (lambda (x) (* x 2)) ls)))
Use map to give a similarly concise definition of a procedure
association-list-keys that takes one argument, an association list, and returns a list of the key
values for that association list.
The map procedure can actually take more than two arguments,
if all of the extras are lists:
> (map string-append '("left" "start" "beginning")
'("-to-" "-to-" "-to-")
'("right" "finish" "end"))
("left-to-right" "start-to-finish" "beginning-to-end")
> (map cons '(a b c) '(d e f))
((a . d) (b . e) (c . f))
Exercise 3.2 on page 81 of the textbook asks you to define a procedure
pairwise-sum that takes as arguments two lists of numbers,
equal in length, and returns a new list whose components are the sums of
the corresponding components of the arguments. Define this procedure using
map.
Another useful ``higher-order'' procedure that is built into Scheme 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 * '(3 4 5 6)) 360 > (apply append '((a b c) (d) (e f) () (g h i))) (a b c d e f g h i)
Exercise 3.3 on page 82 of the textbook asks you to define a procedure
dot-product that takes as arguments two lists of numbers,
equal in length, and returns the sum of the products of corresponding
elements of the arguments:
> (dot-product '(3 4 -1) '(1 -2 -3)) -2 > (dot-product '(0.003 0.035) '(8 2)) 0.094 > (dot-product '(5.3e4) '(2.0e-3)) 106.0 > (dot-product '() '()) 0
Use apply and map to give a concise definition of
this procedure.
This document is available on the World Wide Web as
http://www.math.grin.edu/courses/Scheme/procedures-as-values.html
created March 24, 1997
last revised March 30, 1998