CSC 153 Grinnell College Spring, 2009
 
Computer Science Fundamentals
 

Procedures as Values

Goals

This laboratory exercise provides students with experience exploring patterns of problem solving in the development of higher-order procedures.

Steps for this Laboratory Exercise

  1. Here are three procedures with similar structures:

    1. (define whitespace-tally
      ;;; whitespace-tally: determine how many elements of a given
      ;;; list of characters are whitespace characters
      
      ;;; Given:
      ;;;   LS, a list of characters.
      
      ;;; Result:
      ;;;   TALLY, an integer.
      
      ;;; Preconditions:
      ;;;   None.
      
      ;;; Postcondition:
      ;;;   TALLY is the number of elements of LS that are
      ;;;   whitespace characters.
      
        (lambda (ls)
          (cond ((null? ls) 0)
                ((char-whitespace? (car ls))
                 (+ (whitespace-tally (cdr ls)) 1))
                (else (whitespace-tally (cdr ls))))))
      
    2. (define even-tally
      ;;; even-tally: determine how many elements of a given list
      ;;; of exact integers are even
      
      ;;; Given:
      ;;;   LS, a list of exact integers.
      
      ;;; Result:
      ;;;   TALLY, an integer.
      
      ;;; Preconditions:
      ;;;   None.
      
      ;;; Postcondition:
      ;;;   TALLY is the number of even elements of LS.
      
        (lambda (ls)
          (cond ((null? ls) 0)
                ((even? (car ls)) (+ (even-tally (cdr ls)) 1))
                (else (even-tally (cdr ls))))))
      
      
    3. (define symbol-tally
      ;;; symbol-tally: determine how many elements of a given
      ;;; list are symbols
      
      ;;; Given:
      ;;;   LS, a list.
      
      ;;; Result:
      ;;;   TALLY, an integer.
      
      ;;; Preconditions:
      ;;;   None.
      
      ;;; Postcondition:
      ;;;   TALLY is the number of elements of LS that are symbols.
      
        (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, develop 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.

  2. How would you use the tallier procedure that you defined in step 1 to create procedures to determine

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

    1. (define generate-list-of-squares
      ;;; generate-list-of-squares: construct and return a list of
      ;;; the squares of natural numbers less than a given natural
      ;;; number
      
      ;;; Given:
      ;;;   LEN, an integer.
      
      ;;; Result:
      ;;;   LS, a list.
      
      ;;; Precondition:
      ;;;   LEN is exact and not negative.
      
      ;;; Postconditions:
      ;;;   (1) The length of LS is LEN.
      ;;;   (2) For every natural number k less than LEN, the element
      ;;;       at position k of LS is the square of k.
      
        (lambda (len)
          (let kernel ((remaining len)
                       (so-far '()))
            (if (zero? remaining)
                so-far
                (let ((next (- remaining 1)))
                  (kernel next (cons (* next next) so-far)))))))
      
    2. (define generate-list-of-hyphen-strings
      ;;; generate-list-of-hyphen-strings: construct and return
      ;;;  a list of strings of hyphens of length less than
      ;;;  a given natural number.
      
      ;;; Given:
      ;;;   LEN, an integer.
      
      ;;; Result:
      ;;;   LS, a list.
      
      ;;; Precondition:
      ;;;   LEN is exact and not negative.
      
      ;;; Postconditions:
      ;;;   (1) The length of LS is LEN.
      ;;;   (2) For every natural number k less than LEN, the element
      ;;;       at position k of LS is the a string of hyphens of length
      ;;;       k.
      
        (lambda (len)
          (let kernel ((remaining len)
                       (so-far '()))
            (if (zero? remaining)
                so-far
                (let ((next (- remaining 1)))
                  (kernel next (cons (make-string next #\-) so-far)))))))
      
    3. ;;; generate-list-of-termials: construct and return a list of
      ;;; the ``termials'' of natural numbers less than a given
      ;;; natural number
      
      ;;; Given:
      ;;;   LEN, an integer.
      
      ;;; Result:
      ;;;   LS, a list.
      
      ;;; Precondition:
      ;;;   LEN is exact and not negative.
      
      ;;; Postconditions:
      ;;;   (1) The length of LS is LEN.
      ;;;   (2) For every natural number k less than LEN, the element
      ;;;       at position k of LS is the sum of the natural numbers
      ;;;       less than or equal to k.
      
      (define generate-list-of-termials
        (lambda (len)
          (let kernel ((remaining len)
                       (so-far '()))
            (if (zero? remaining)
                so-far
                (let ((next (- remaining 1)))
                  (kernel next (cons (termial next) so-far)))))))
      

    (The termial procedure was defined in the reading on recursion with integers. It computes 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.

    Design, write, 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 procedure 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 procedure to every natural number less than len.

  4. The map procedure can actually take more than two arguments, if all of the extras are lists, and the arity of the procedure that map applies is increased to match:

       > (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))
    

    Using map, 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.

  5. Design, write, and test 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. Here are two sample calls:

       > (dot-product '(1 2 4 8) '(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 '() '())
       0
       ; ... because in this case there are no products to add
    

    One can use apply and map to give an extremely concise definition of this procedure.

  6. Develop the higher-order procedure right-section, which takes a procedure of two arguments and a value to drop in as its second argument, and returns the operator section that expects the first argument. (For instance, (right-section expt 3) is a procedure that computes the cube of any number it is given.) You can use the definition of left-section from today's reading on procedures as values as a model.

  7. Using the generate-list procedure from step 3, above, and an operator section, define a procedure powers-of-two that constructs and returns a list of powers of two, in ascending order, given the length of the desired list. Here's a sample call:

       > (powers-of-two 7)
       (1 2 4 8 16 32 64)
    
  8. Design, write, and test a procedure bounded-mu that takes two arguments, a predicate predicate and a natural number limit, and returns the least natural number less than limit that satisfies predicate, or #f if there is no such number.

    Use bounded-mu to find the least natural number less than 1000 that leaves a remainder of 5 when divided by 7, a remainder of 7 when divided by 11, and a remainder of 11 when divided by 13.

  9. The filters constructed by remove are designed to exclude list elements that satisfy a given predicate. Define a higher-order procedure filter that returns a filtering procedure that retains the elements that satisfy a given predicate (excluding those that fail to satisfy it). For instance, applying the filter (filter even?) to a list of integers should return a list consisting of just the even elements of the given list.

Acknowledgements

Much of this lab comes from materials by John David Stone. Dr. Stone, in turn, acknowledges Professor Ben Gum for his contributions to the development of this lab.


This document is available on the World Wide Web as

http://www.cs.grinnell.edu/~walker/courses/153.sp09/labs/lab-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.