CSC 153 Grinnell College Spring, 2009
 
Computer Science Fundamentals
 

Procedures as Values

Abstract

Procedures in Scheme are first-class citizens. That is, in addition to specifying operations, procedures have all the qualities one associates with data. From this perspective, procedures are just another form of data. This laboratory exercise explores some consequences that result from considering procedures as data values. Some content in this lab overlaps the lab on higher order procedures, but this lab presents a somewhat different viewpoint and approach.

Associating Names with Procedures

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.

Identifying Patterns

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.

  1. (define list-of-numbers?
    ;;; 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.
    
      (lambda (whatever)
        (or (null? whatever)
            (and (pair? whatever)
                 (number? (car whatever))
                 (list-of-numbers? (cdr whatever))))))
    
  2. (define list-of-strings?
    ;;; 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.
    
       (lambda (whatever)
        (or (null? whatever)
            (and (pair? whatever)
                 (string? (car whatever))
                 (list-of-strings? (cdr whatever))))))
    
  3. (define association-list?
    ;;; 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.
    
      (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:

   (define 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.
   
     (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:

;Stage 1:
   (define exact-multiple-of-seven?
   ;;; 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.
   
     (lambda (whatever)
       (and (integer? whatever)
            (exact? whatever)
            (zero? (remainder whatever 7)))))

;Stage 2:
   (define list-of-exact-multiples-of-seven?
   ;;; 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.
   
     (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-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.
   
     (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:

   (define map
   ;;; 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.
   
     (lambda (procedure ls)
       (if (null? ls)
           '()
           (cons (procedure (car ls)) (map procedure (cdr ls))))))

Here's how map could be used to write a procedure lengths that takes a list of strings as parameter and returns a list of the lengths of those strings.

   (define lengths
   ;;; 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.
   
     (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

   (define double
   ;;; double -- compute the double of a given number
   
   ;;; Given:
   ;;;   N, a number.
   
   ;;; Result:
   ;;;   DUB, a number.
   
   ;;; Preconditions:
   ;;;   None.
   
   ;;; Postcondition:
   ;;;   DUB is twice N.
   
     (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 — we commonly 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:

   (define left-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.
   
     (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:

   (define remove-negatives
   ;;; 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.
   
     (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:

   (define remove-whitespace
   ;;; 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.
   
     (lambda (ls)
       (cond ((null? ls) '())
             ((char-whitespace? (car ls)) (remove-whitespace (cdr ls)))
             (else (cons (car ls) (remove-whitespace (cdr ls)))))))



   (define remove-n/a-symbols
   ;;; 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.
   
     (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:

   (define remove
   ;;; 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.
   
     (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)))

This document is available on the World Wide Web as

http://www.cs.grinnell.edu/~walker/courses/153.sp09/readings/reading-procedures-as-values.shtml

created 24 March 1997 by John David Stone
revised 2 March 2005 by John David Stone
last revised 2 April 2008 by Henry M. Walker
Valid HTML 4.01! Valid CSS!
For more information, please contact Henry M. Walker at walker@cs.grinnell.edu.