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) ;Pre-condition: pred is a procedure; ls is a list ;Post-conditions: returns error if pre-conditions are not met ; returns #t if pred is true for every element of list ; 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")) (every-kernel pred ls))) (define every-kernel (lambda (pred ls) ;Pre-condition: pred is a procedure; ls is a list ;Post-conditions: returns #t if pred is true for every element of list (or (null? ls) (and (pred (car ls)) ; Invoke the predicate here! (every-kernel pred (cdr ls))))))
As in many previous examples in the course, the code for
every?
actually just performs error checking that is needed
just at the start. Then every?
does the actual work of going
through the list.
The every-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
invocation of the predicate returns #f
, this value is
immediately returned by every-kernel
; but if the predicate is
true of the car, every-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.)
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) ;Pre-condition: n is a number ;Post-condition: n2 is returned (* 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 map (lambda (operation ls) ;Pre-condition: operation is a procedure; ls is a list ;Post-conditoin: the operation is applied to every element of ls (if (null? ls) '() (cons (operation (car ls)) (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) ;Pre-condition: ls is a list of numbers ;Post-condition: returns a list with every number on ls doubled (map (lambda (x) (* x 2)) ls)))Using
map
several times provides a simple mechanism to
capitalize all letters in a string:
(define capitalize (lambda (str) ;Pre-condition: str is a character string ;Post-condition: str is returned with all letters capitalized (list->string (map char-upcase (string->list str))) ) )
capitalize
on several strings to test precisely
what it does.
capitalize
does.
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")
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)
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.
eval
is related to
apply
, in that eval
evaluates the expression
that follows it:
> (eval '(< 1 2 4 6 9)) ; the numbers 1 2 4 6 9 are in assending #t > (eval '(< 1 2 7 6 9)) ; < applied to these numbers is false #f > (eval (cons '< '(1 2 4 6 9))) ; form the list and evaluate it as above #tThe very first lab mentioned that the basic Scheme environment follows a read-eval-print cycle. Here, you see that
eval
is a Scheme procedure that does this
evaluation.
do-it
that reads an expression, evaluates
it, and prints the result -- paralleling one pass through the
read-eval-print cycle.
do-it
so that it reads, evaluates, and prints multiple
expressions, until the user types (exit)
.
This document is available on the World Wide Web as
http://www.math.grin.edu/~walker/courses/153.sp00/lab-procedure-arguments.html