Fundamentals of Computer Science I: Media Computing (CS151.01 2008S)

Laboratory: Local Procedures and Recursion


Summary: In this laboratory, we consider the various techniques for creating local recursive procedures, particularly letrec and named let. We also review related issues, such as husk-and-kernel programming.

Reference

A letrec statement has the form

(letrec ((name-of-recursive-proc body)
         (name-of-recursive-proc body)
         ...
         (name-of-recursive-proc body))
  expression
  expression
  ...
  expression)

This statement builds a new environment (that maps names to values), adds all of the named procedures to the environment, evaluates the expressions and then forgets about the environment. It is useful for temporarily creating local procedures, and for limiting access to kernels.

A named let is an alternative mechanism for defining recursive kernels. It can be used only when you want to define a single recursive kernel and call it immediately, and has the form

(let kernel ((name_1 exp_1)
             ...
             (name_n exp_n))
  body)

The named let tells Scheme to define a new procedure (whose name is kernel), with parameters that correspond to the names and whose body corresponds to body. That is, it creates the following procedure

(let ((kernel (name_1 ... name_2) body))
   ...)

It then gives the result of of calling kernel on exp_1 ... exp_n.

Preparation

a. If any of the following procedures are not in your library, add them to your library.

;;; Procedure:
;;;   rgb-bright?
;;; Parameters:
;;;   color, an RGB color
;;; Purpose:
;;;   Determines whether the color is bright.
;;; Produces:
;;;   bright?, a boolean
;;; Preconditions:
;;;   color is a valid RGB color.  That is, each component is between
;;;     0 and 255, inclusive.
;;;   rgb-brightness is defined.
;;; Postconditions:
;;;   bright? is true iff (rgb-brightness color) >= 67.
(define rgb-bright?
  (lambda (color)
    (<= 67 (rgb-brightness color))))

;;; Procedure:
;;;   rgb-dark?
;;; Parameters:
;;;   color, an RGB color
;;; Purpose:
;;;   Determine if the color appears dark.
;;; Produces:
;;;   dark?, a Boolean
;;; Preconditions:
;;;   [No additional]
;;; Postconditions:
;;;   If color is relatively dark, then dark? is #t.
;;;   Otherwise, dark? is #f.
(define rgb-dark?
  (lambda (color)
     (> 33 (rgb-brightness color))))

;;; Procedure:
;;;   rgb-brightness
;;; Parameters:
;;;   color, an RGB color
;;; Purpose:
;;;   Computes the brightness of color on a 0 (dark) to 100 (light) scale.
;;; Produces:
;;;   b, an integer
;;; Preconditions:
;;;   color is a valid RGB color.  That is, each component is between
;;;     0 and 255, inclusive.
;;; Postconditions:
;;;   If color1 is likely to be perceived as lighter than color2,
;;;     then (brightness color1) > (brightness color2).
(define rgb-brightness
  (lambda (color)
    (round (* 100 (/ (+ (* 0.30 (rgb-red color))
                        (* 0.59 (rgb-green color))
                        (* 0.11 (rgb-blue color)))
                      255)))))

;; Procedure:
;;;   rgb-brighter?
;;; Parameters:
;;;   color1, a color
;;;   color2, a color
;;; Purpose:
;;;   Determine if color1 is strictly brighter than color 2.
;;; Produces:
;;;   brighter?, a Boolean
;;; Preconditions:
;;;   [No additional preconditions.]
;;; Postconditions:
;;;   If (rgb-brightness color1) > (rgb-brightness color2)
;;;     then brighter is true (#t)
;;;   Otherwise
;;;     brighter is false (#f)
(define rgb-brighter?
  (lambda (color1 color2)
    (> (rgb-brightness color1) (rgb-brightness color2))))

b. Create a new window in which you load your library file.

Exercises

Exercise 1: The Brightest Color, Revisited

As the reading suggested, local kernels are particularly appropriate for checking preconditions. Local kernels are also appropriate for writing recursive helper procedures that take an extra parameter to accumulate a result.

For example, here's a helper-based definition of rgb-brightest.

(define rgb-brightest
  (lambda (colors)
    (rgb-brightest-helper (car colors) (cdr colors))))
(define rgb-brightest-helper
  (lambda (brightest-so-far remaining-colors)
    (cond
      ((null? remaining-colors)
       brightest-so-far)
      ((rgb-brighter? (car remaining-colors) brightest-so-far)
       (rgb-brightest-helper (car remaining-colors) (cdr remaining-colors)))
      (else
       (rgb-brightest-helper brightest-so-far (cdr remaining-colors))))))

a. Rewrite this to make the helper local and to name the helper kernel.

b. Test your procedure on a few simple lists of colors. For example, you might try some of the following.

> (rgb->string (rgb-brightest (list color-red color-green color-blue)))
> (rgb->string (rgb-brightest (list color-black)))
> (rgb->string (rgb-brightest (list color-red color-black color-green color-yellow)))
> (rgb->string (rgb-brightest (list color-yellow color-red color-black color-green)))

c. When you are done, you may want to compare your answer to the sample solution in the notes on this problem.

Exercise 2: Safely Computing the Brightest Color

The procedure given above, and your rewrite of that procedure, will fail miserably if given an inappropriate parameter, such as an empty list, a list that contains values that are not colors, or a non-list. Rewrite rgb-brightest so that it checks its preconditions (including that the list contains only RGB colors) before calling the kernel.

Note that you can use rgb? to check if a value represents an RGB color.

Exercise 3: Computing the Brightest Color, Revisited Again

Rewrite rgb-brightest-helper using a named let rather than letrec. (The goal of this problem is to give you experience using named let, so you need not check preconditions for this exercise.)

When you are done, you may want to compare your answer to the sample solution in the notes on this problem.

Exercise 4: Taking Some Elements

Define and test a procedure, (list-take lst n), returns a list consisting of the first n elements of the list, lst, in their original order. You might also think of list-take as returning all the values that appear before index n.

For example,

> (list-take (list "a" "b" "c" "d" "e") 3)
("a" "b" "c")
> (list-take (list 2 3 5 7 9 11 13 17) 2)
(2 3)
> (list-take (list "here" "are" "some" "words") 0)
()
> (list-take (list null null) 2)
(() ())
> (map rgb->string (list-take (list color-black color-white color-green) 1))
("0/0/0")

The procedure should signal an error if lst is not a list, if n is not an exact integer, if n is negative, or if n is greater than the length of lst.

Note that in order to signal such errors, you may want to take advantage of the husk-and-kernel programming style.

For Those With Extra Time

Extra 1: Taking Some More Elements

Rewrite list-take to use whichever of named let and letrec you didn't use in the previous exercise.

Extra 2: Reflection

You've now seen two examples in which you've written two different solutions, one using letrec and one use named let. Reflect on which of the two strategies you prefer and why.

Extra 3: Alternating Lists

A list of spots is a bright-dark-alternator if its elements are alternately bright spots and dark spots, beginning with a bright spot. A list of spots is a dark-bright-alternator if its elements are alternately dark and bright, beginning with a dark spot. (We say that the empty list is both a bright-dark-alternator and a dark-bright-alternator.)

a. Write a predicate, spots-alternating-brightness?, that determines whether a non-empty list of spots is either a bright-dark-alternator or a dark-bright-alternator. Your definition should include a a letrec expression in which the identifiers bright-dark-alternator? and dark-bright-alternator? are bound to mutually recursive predicates, each of which determines whether a given list has the indicated characteristic.

(define spots-alternating-brightness?
  (lambda (spots)
    (letrec ((bright-dark-alternator?
              (lambda (spots) ...))
             (dark-bright-alternator?
              (lambda (spots) ...)))
      ...)))

When you are done, you may want to compare your answer to the sample solution in the notes on this problem.

b. Here are a few lists of spots. For which do you expect spots-alternating-brightness? to hold?

(define sample0 null)
(define sample1
  (list (spot-new 0 0 color-white)))
(define sample2
  (list (spot-new 0 0 color-black)))
(define sample3
  (list (spot-new 0 0 color-white)
        (spot-new 0 1 color-black)))
(define sample4
  (list (spot-new 0 0 color-black)
        (spot-new 0 1 color-white)))
(define sample5
  (list (spot-new 0 0 color-black)
        (spot-new 0 1 color-black)))
(define sample6
  (list (spot-new 0 0 color-white)
        (spot-new 0 1 color-black)
        (spot-new 0 2 color-white)))
(define sample7
  (list (spot-new 0 0 color-white)
        (spot-new 0 1 color-black)
        (spot-new 0 2 color-white)
        (spot-new 0 3 color-black)
        (spot-new 0 4 color-white)))
(define sample8
  (list (spot-new 0 0 color-red)
        (spot-new 1 0 color-yellow)
        (spot-new 2 0 color-blue)
        (spot-new 3 0 color-white)
        (spot-new 4 0 color-black)))

c. Check your answers experimentally.

Notes

Notes on Exercise 1: The Brightest Color, Revisited

Here's one possible solution.

(define rgb-brightest
  (lambda (colors)
    (letrec ((kernel
              (lambda (brightest-so-far remaining-colors)
                (cond 
                  ((null? remaining-colors)
                   brightest-so-far)
                  ((rgb-brighter? (car remaining-colors) brightest-so-far)
                   (kernel (car remaining-colors) 
                           (cdr remaining-colors)))
                  (else
                   (kernel brightest-so-far 
                           (cdr remaining-colors)))))))
      (kernel (car colors) (cdr colors)))))

Notes on Exercise 3: Computing the Brightest Color, Revisited Again

Here's one possible solution.

(define rgb-brightest
  (lambda (colors)
    (let kernel ((brightest-so-far (car colors))
                 (remaining-colors (cdr colors)))
            (cond
              ((null? remaining-colors)
               brightest-so-far)
              ((rgb-brighter? (car remaining-colors) brightest-so-far)
               (kernel (car remaining-colors)
                       (cdr remaining-colors)))
              (else
               (kernel brightest-so-far
                       (cdr remaining-colors)))))))

Notes on Extra 3: Alternating Lists

Once bright-dark-alternator? and dark-bright-alternator? are written, the definition is fairly straightforward. A non-empty list of spots has alternating brightness if it's not empty and it's either a bright-dark-alternator or a dark-bright-alternator.

      (and (not (null? spots))
           (or (bright-dark-alternator? spots)
               (dark-bright-alternator? spots)))

Each definition is also fairly straightforward. A list is a bright-dark-alternator if it is empty or if the car is bright and the cdr is a dark-bright-alternator.

             (bright-dark-alternator?
              (lambda (spots) 
                (or (null? spots)
                    (and (rgb-bright? (spot.color (car spots)))
                         (dark-bright-alternator? (cdr spots))))))

The definition of dark-bright-alternator? is so similar that we will not provide it separately.

Putting everything together, we get

(define spots-alternating-brightness?
  (lambda (spots)
    (letrec ((bright-dark-alternator?
              (lambda (spots)
                (or (null? spots)
                    (and (rgb-bright? (spot-color (car spots)))
                         (dark-bright-alternator? (cdr spots))))))
             (dark-bright-alternator?
              (lambda (spots)
                (or (null? spots)
                    (and (rgb-dark? (spot-color (car spots)))
                         (bright-dark-alternator? (cdr spots)))))))
      (and (not (null? spots))
           (or (bright-dark-alternator? spots)
               (dark-bright-alternator? spots))))))

Note that there's a hidden moral here: The procedures defined in a letrec can be mutually recursive.

Creative Commons License

Samuel A. Rebelsky, rebelsky@grinnell.edu

Copyright (c) 2007-8 Janet Davis, Matthew Kluber, and Samuel A. Rebelsky. (Selected materials copyright by John David Stone and Henry Walker and used by permission.)

This material is based upon work partially supported by the National Science Foundation under Grant No. CCLI-0633090. Any opinions, findings, and conclusions or recommendations expressed in this material are those of the author(s) and do not necessarily reflect the views of the National Science Foundation.

This work is licensed under a Creative Commons Attribution-NonCommercial 2.5 License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc/2.5/ or send a letter to Creative Commons, 543 Howard Street, 5th Floor, San Francisco, California, 94105, USA.