Procedures as values

Procedures are values in Scheme. A definition such as

(define square
  (lambda (number)
    (* number number)))

associates the name square with the procedure that is the value of the lambda-expression, just as a definition such as

(define side 125)

associates the name side with the number that is the value of the numeral 125. When the Scheme processor subsequently evaluates the expression

(square side)

it determines the meaning of square in exactly the same way as it determines the meaning of side. Procedures, in short, are managed like any other values.

Since procedures are values, Scheme permits you to supply them as arguments to other procedures, so that they can be bound to parameters of those other procedures. So we programmers can abstract general patterns of computation from the particular procedures that are used in particular computations, just as we abstract patterns of computation from the particular lists or numbers that we operate on in particular computations.

For example, here are the definitions for three procedures that share a pattern. Each takes one argument. The first determines whether its argument is a list of numbers; the second, whether its argument is a list of strings; the third, whether its argument is an association list, a list of pairs.

(define list-of-numbers?
  (lambda (whatever)
    (or (null? whatever)
        (and (pair? whatever)
             (number? (car whatever))
             (list-of-numbers? (cdr whatever))))))

(define list-of-strings?
  (lambda (whatever)
    (or (null? whatever)
        (and (pair? whatever)
             (string? (car whatever))
             (list-of-strings? (cdr whatever))))))

(define association-list?
  (lambda (whatever)
    (or (null? whatever)
        (and (pair? whatever)
             (pair? (car whatever))
             (association-list? (cdr whatever))))))

Writing and testing any one of these definitions is an interesting and instructive exercise for the beginning Scheme programmer. Writing and testing the second one is good practice. Writing and testing the third one is, frankly, a little tedious. If we then move on to list-of-symbols?, list-of-integers?, list-of-ballots?, and so on, eventually programming is reduced to typing.

We need to automate the process, now that it's become purely routine. We should write a single procedure that takes, as its argument, the predicate that we want the elements of a list to satisfy and returns, as its value, the appropriate list-testing predicate. Let's call this delightful, time-saving procedure list-of. If it were a primitive of Scheme, we could simply write

(define list-of-numbers? (list-of number?))
(define list-of-strings? (list-of string?))
(define association-list? (list-of pair?))
(define list-of-symbols? (list-of symbol?))

and so on, and the results would be just as if we had actually typed out the full definitions of those procedures.

The procedure list-of is not a primitive of Scheme, but we can define it ourselves. Here's a step-by-step method for figuring out what the definition of list-of looks like.

We first make up a template showing the common structure of the bodies of the definitions of procedures that we're abstracting from, with a generic identifier, predicate, in place of the part that varies. We also use a generic identifier, recurrer, to name the template itself, when we need to invoke it recursively. Here is the template:

(lambda (whatever)
  (or (null? whatever)
      (and (pair? whatever)
           (predicate (car whatever))
           (recurrer (cdr whatever)))))

We embed this template inside a letrec-expression that binds it to the identifier recurrer and then, in its body, simply repeats that name, so that the template procedure itself is the value of the whole expression:

(letrec ((recurrer (lambda (whatever)
                     (or (null? whatever)
                         (and (pair? whatever)
                              (predicate (car whatever))
                              (recurrer (cdr whatever)))))))
  recurrer)

When we invoke the list-of procedure, we give it a predicate as its argument, and list-of drops this predicate into the correct position in the template, completing the construction of the specialized version of recurrer, which it then returns. Here's how it looks in Scheme:

(define list-of
  (lambda (predicate)
    (letrec ((recurrer (lambda (whatever)
                         (or (null? whatever)
                             (and (pair? whatever)
                                  (predicate (car whatever))
                                  (recurrer (cdr whatever)))))))
      recurrer)))

Exercise 1

Here are three procedures with similar structures. The first one finds out how many even numbers there are in a given list of integers; the second finds out how many whitespace characters there in a given list of characters; the third one finds out how many of the elements of a given list are symbols.

(define even-tally
  (lambda (ls)
    (cond ((null? ls) 0)
          ((even? (car ls)) (+ (even-tally (cdr ls)) 1))
          (else (even-tally (cdr ls))))))

(define whitespace-tally
  (lambda (ls)
    (cond ((null? ls) 0)
          ((char-whitespace? (car ls)) (+ (whitespace-tally (cdr ls)) 1))
          (else (whitespace-tally (cdr ls))))))

(define symbol-tally
  (lambda (ls)
    (cond ((null? ls) 0)
          ((symbol? (car ls)) (+ (symbol-tally (cdr ls)) 1))
          (else (symbol-tally (cdr ls))))))

By abstracting what these procedures have in common, define and test a procedure tallier that takes a predicate as its argument and constructs and returns a specialized tallying procedure that counts the number of elements of a given list that satisfy the predicate.


Once we have a ``higher-order'' procedure like list-of or tallier to work with, we can even construct our own predicates and pass them as arguments. For example, suppose that we want a procedure that checks whether its argument is a list of exact multiples of seven. We could define this in two stages, thus:

(define exact-multiple-of-seven?
  (lambda (whatever)
    (and (integer? whatever)
         (zero? (remainder whatever 7)))))

(define list-of-exact-multiples-of-seven?
  (list-of exact-multiple-of-seven?))

Alternatively, we could write the lambda-expression that denotes the predicate right into the call to list-of:

(define list-of-exact-multiples-of-seven?
  (list-of (lambda (whatever)
             (and (integer? whatever)
                  (zero? (remainder whatever 7))))))

Exercise 2

How would you use the tallier procedure that you defined in exercise 1 to create procedures to determine


Exercise 3

Here are three procedures, each of which takes a natural number as argument and returns a list:

(define generate-list-of-squares
  (lambda (len)
    (let kernel ((remaining len)
                 (so-far null))
      (if (zero? remaining)
          so-far
          (let ((next (- remaining 1)))
            (kernel next (cons (* next next) so-far)))))))

(define generate-list-of-hyphen-strings
  (lambda (len)
    (let kernel ((remaining len)
                 (so-far null))
      (if (zero? remaining)
          so-far
          (let ((next (- remaining 1)))
            (kernel next (cons (make-string next #\-) so-far)))))))

(define generate-list-of-termials
  (lambda (len)
    (let kernel ((remaining len)
                 (so-far null))
      (if (zero? remaining)
          so-far
          (let ((next (- remaining 1)))
            (kernel next (cons (termial next) so-far)))))))

(The termial procedure was defined in the lab on recursion with natural numbers; it finds the sum of all the natural numbers up to and including its argument.)

Apply each of these procedures to a few small natural numbers to see what they do.

Define and test a generate-list procedure that abstracts the common structure of these three definitions. Generate-list should take, as its argument, any one-argument procedure proc that can be applied to a natural number. Generate-list should return, as its value, a procedure that, when applied to a natural number len, constructs and returns a list of the results of applying proc to every natural number less than len.


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:

> (map square (list 3 -9 8/7 0))
(9 81 64/49 0)

The map procedure works as if it were defined like this:

(define map
  (lambda (procedure ls)
    (if (null? ls)
        null
        (cons (procedure (car ls)) (map procedure (cdr ls))))))

Here's how map could be used to write the lengths procedure from the lab on recursion with lists.

(define lengths
  (lambda (ls)
    (map length ls)))

Exercise 4

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 keys for that association list.


Exercise 5

The map procedure can actually take more than two arguments, if all of the extras are lists:

> (map string-append (list "left" "start" "beginning")
                     (list "-to-" "-to-" "-to-")
                     (list "right" "finish" "end"))
("left-to-right" "start-to-finish" "beginning-to-end")

> (map cons (list 'a 'b 'c) (list '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 * (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)

Exercise 6

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 (list 1 2 4 8) (list 11 5 7 3))
73
; ... because (1 x 11) + (2 x 5) + (4 x 7) + (8 x 3) = 11 + 10 + 28 + 24 = 73

> (dot-product null null)
0
; ... because in this case there are no products to add

Use apply and map to give an extremely concise definition of this procedure.


This document is available on the World Wide Web as

http://www.cs.grinnell.edu/~stone/courses/scheme/procedures-as-values.xhtml

created March 24, 1997
last revised March 17, 2000

John David Stone (stone@cs.grinnell.edu)