Procedures as values

Course links

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.

;;; list-of-numbers?: determines whether its argument is a
;;; list of numbers

;;; Given:
;;;   WHATEVER, a value.

;;; Result:
;;;   OUTCOME, a Boolean.

;;; Preconditions:
;;;   None.

;;; Postcondition:
;;;   OUTCOME is #T if WHATEVER is a list and each of its
;;;   elements is a number, #F if WHATEVER is not a list or
;;;   if any of its elements is not a number.

(define list-of-numbers?
  (lambda (whatever)
    (or (null? whatever)
        (and (pair? whatever)
             (number? (car whatever))
             (list-of-numbers? (cdr whatever))))))
;;; list-of-strings?: determines whether its argument is a
;;; list of strings

;;; Given:
;;;   WHATEVER, a value.

;;; Result:
;;;   OUTCOME, a Boolean.

;;; Preconditions:
;;;   None.

;;; Postcondition:
;;;   OUTCOME is #T if WHATEVER is a list and each of its
;;;   elements is a string, #F if WHATEVER is not a list or
;;;   if any of its elements is not a string.

(define list-of-strings?
  (lambda (whatever)
    (or (null? whatever)
        (and (pair? whatever)
             (string? (car whatever))
             (list-of-strings? (cdr whatever))))))
;;; association-list?: determines whether its argument is an
;;; association list

;;; Given:
;;;   WHATEVER, a value.

;;; Result:
;;;   OUTCOME, a Boolean.

;;; Preconditions:
;;;   None.

;;; Postcondition:
;;;   OUTCOME is #T if WHATEVER is an association list
;;;   elements is a number, #F otherwise.

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

Writing and testing one of these definitions is an interesting and instructive exercise for the beginning Scheme programmer. Writing and testing another 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-lists-of-symbols?, and so on, eventually we get the feeling that we are just typing rather than programming. This is an indication that we are doing work that the computer should be doing for us.

Once the process of writing list-of-X? predicates has become purely routine, we should automate it. Ideally, we would want 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 the finished definition of list-of:

;;; LIST-OF: construct and return a procedure for
;;; determining whether something is a list of values
;;; satisfying a given predicate 

;;; Given:
;;;   PREDICATE, a unary predicate.

;;; Result:
;;;   RECURRER, a unary predicate.

;;; Preconditions:
;;;   None.

;;; Postconditions:
;;;   Given an argument WHATEVER, the predicate RECURRER
;;;   returns #T if WHATEVER is a list of values that all
;;;   satisfy PREDICATE, and returns #F if WHATEVER is not
;;;   a list or if any element of WHATEVER fails to satisfy
;;;   PREDICATE.

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

Once we have a ``higher-order'' procedure like list-of 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. One approach would be to define this in two stages, thus:

;;; exact-multiple-of-seven?: determine whether a given value
;;; is an exact integer that is a multiple of 7

;;; Given:
;;;   WHATEVER, a value.

;;; Result:
;;;   OUTCOME, a Boolean.

;;; Preconditions:
;;;   None.

;;; Postcondition:
;;;   OUTCOME is #T if WHATEVER is an exact integer that is a
;;;   multiple of 7, #F if it is not an integer, not exact, or
;;;   not a multiple of 7.

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

;;; list-of-exact-multiples-of-seven?: determine whether a
;;; given value is a list of exact integers, each of which
;;; is a multiple of 7

;;; Given:
;;;   WHATEVER, a value.

;;; Result:
;;;   OUTCOME, a Boolean.

;;; Preconditions:
;;;   None.

;;; Postcondition:
;;;   OUTCOME is #T if WHATEVER is a list in which each element
;;;   is an exact integer that is a multiple of 7, #F if WHATEVER
;;;   is not a list or if any of its elements is not an integer,
;;;   not exact, or not a multiple of 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:

;;; list-of-exact-multiples-of-seven?: determine whether a
;;; given value is a list of exact integers, each of which
;;; is a multiple of 7

;;; Given:
;;;   WHATEVER, a value.

;;; Result:
;;;   OUTCOME, a Boolean.

;;; Preconditions:
;;;   None.

;;; Postcondition:
;;;   OUTCOME is #T if WHATEVER is a list in which each element
;;;   is an exact integer that is a multiple of 7, #F if WHATEVER
;;;   is not a list or if any of its elements is not an integer,
;;;   not exact, or not a multiple of 7.

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

map and apply

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)

In this example, you can think of the map procedure as if it were defined like this:

;;; map: apply a given procedure to each element of a
;;; given list

;;; Givens:
;;;   PROCEDURE, a unary procedure.
;;;   LS, a list.

;;; Result:
;;;   COLLECTED-RESULTS, a list.

;;; Precondition:
;;;   Every element of LS satisfies the preconditions
;;;   that PROCEDURE imposes on its argument.

;;; Postconditions:
;;;   (1) The length of COLLECTED-RESULTS is the same as the
;;;       length of LS.
;;;   (2) Each element of COLLECTED-RESULTS is the result of
;;;       applying PROCEDURE to the corresponding element of
;;;       LS.

(define map
  (lambda (procedure ls)
    (if (null? ls)
        '()
        (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:

;;; lengths -- compute the lengths of the lists in a given list

;;; Given:
;;;   LS, a list of lists.

;;; Result:
;;;   LIST-LENGTHS, a list of exact integers.

;;; Preconditions:
;;;   None.

;;; Postconditions:
;;;   (1) The length of LIST-LENGTHS is the same as that of LS.
;;;   (2) Each element of LIST-LENGTHS is the length of the
;;;       corresponding element of LS.

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

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=? '("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)

Operator sections

An operator section is a procedure that is derived from another procedure by ``filling in'' some but not all of its arguments. For instance, the double procedure defined by

;;; double -- compute the double of a given number

;;; Given:
;;;   N, a number.

;;; Result:
;;;   DUB, a number.

;;; Preconditions:
;;;   None.

;;; Postcondition:
;;;   DUB is twice N.

(define double
  (lambda (n)
    (* 2 n)))

qualifies as an operator section, since it fills in the first argument to the * procedure with the particular value 2.

Operator sections are often used as arguments to higher-order procedures such as list-of and map. For instance, we could cons the symbol begin at the front of each list in a given list ls of lists by writing

(map (lambda (whatever)
       (cons 'begin whatever)) ls)

Here the value of the lambda-expression is an operator section of cons, with the first argument filled in with the particular value begin.

We can even define higher-order procedures to construct operator sections for us. Such procedures are not primitives, but they are easily defined -- I'll use the name left-section for a higher-order procedure that takes a procedure of two arguments and a value to drop in as its first argument, and returns the relevant operator section:

;;; left-section: construct an operator section by filling
;;; in the first argument of a given binary procedure

;;; Given:
;;;   PROCEDURE, a binary procedure.
;;;   FILLER-IN, a value.

;;; Result:
;;;   SECTION, a unary procedure.

;;; Precondition:
;;;   FILLER-IN satisfies the preconditions that PROCEDURE
;;;   imposes on its first argument.

;;; Postconditions:
;;;   Given a value EXPECTED that satisfies any preconditions
;;;   that PROCEDURE imposes on its second argument, SECTION
;;;   returns the same value that PROCEDURE would return if
;;;   applied to FILLER-IN and EXPECTED.

(define left-section
  (lambda (procedure filler-in)
    (lambda (expected)
      (procedure filler-in expected))))

So, for example, we could define double as (left-section * 2), and (lambda (whatever) (cons 'begin whatever)) is the same thing as (left-section cons 'begin).

Filtering

To filter a list is to examine each of its elements in turn, retaining some for a new list while eliminating others. For instance, given a list of integers, the following procedure filters it to remove the negative ones:

;;; remove-negatives: construct a list comprising the
;;; non-negative elements of a given list of real numbers

;;; Given:
;;;   LS, a list of real numbers.

;;; Result:
;;;   FILTERED, a list of real numbers.

;;; Preconditions:
;;;   None.

;;; Postconditions:
;;;   FILTERED is like LS except that no negative element
;;;   of LS is an element of FILTERED.

(define remove-negatives
  (lambda (ls)
    (cond ((null? ls) '())
          ((negative? (car ls)) (remove-negatives (cdr ls)))
          (else (cons (car ls) (remove-negatives (cdr ls)))))))

We could write similar procedures to remove the whitespace characters from a list of characters, or to exclude any occurrences of the symbol 'n/a (``not applicable'') from a list:

;;; remove-whitespace: construct a list comprising the
;;; non-whitespace elements of a given list of characters

;;; Given:
;;;   LS, a list of characters.

;;; Result:
;;;   FILTERED, a list of characters.

;;; Preconditions:
;;;   None.

;;; Postconditions:
;;;   FILTERED is like LS except that no whitespace character
;;;   in LS is an element of FILTERED.

(define remove-whitespace
  (lambda (ls)
    (cond ((null? ls) '())
          ((char-whitespace? (car ls)) (remove-whitespace (cdr ls)))
          (else (cons (car ls) (remove-whitespace (cdr ls)))))))
;;; remove-n/a-symbols: construct a list comprising the
;;; elements of a given list that are not occurrences of
;;; the symbol 'n/a

;;; Given:
;;;   LS, a list.

;;; Result:
;;;   FILTERED, a list.

;;; Preconditions:
;;;   None.

;;; Postconditions:
;;;   FILTERED is like LS except that no occurrence of the
;;;   symbol 'N/A is an element of FILTERED.

(define remove-n/a-symbols
  (lambda (ls)
    (cond ((null? ls) '())
          ((eq? 'n/a (car ls)) (remove-n/a-symbols (cdr ls)))
          (else (cons (car ls) (remove-n/a-symbols (cdr ls)))))))

Similar filtering procedures occur so frequently that it's useful to have a higher-order procedure to construct them. Using the method described at the beginning of this reading, we can easily define such a procedure:

;;; remove: construct a procedure that takes a list and
;;; returns a list that is similar, except that elements
;;; satisfying a given predicate have been left out

;;; Given:
;;;   PREDICATE, a unary predicate.

;;; Result:
;;;   RECURRER, a unary procedure.

;;; Preconditions:
;;;   None.

;;; Postconditions:
;;;   Given any list LS of values, each satisfying any
;;;   preconditions that PREDICATE imposes on its argument,
;;;   RECURRER returns a list FILTERED that is similar to
;;;   except that no value that satisfies PREDICATE is an
;;;   element of FILTERED.

(define remove
  (lambda (predicate)
    (letrec ((recurrer
              (lambda (ls)
                (cond ((null? ls) '())
                      ((predicate (car ls)) (recurrer (cdr ls)))
                      (else (cons (car ls)
                                  (recurrer (cdr ls))))))))
      recurrer)))

(define remove-negatives (remove negative?))
(define remove-whitespace (remove char-whitespace?))
(define remove-n/a-symbols (remove (left-section eq? 'n/a)))