Laboratory: Verifying Preconditions

Summary: In the laboratory, you will consider mechanisms for verifying the preconditions of procedures. You will also consider some issues int he documentation of such procedures.

Contents:


Preparation

Add the following definitions to your definitions window. The first, four, spot.new, spot.col, spot.row, and spot.color, you should have defined when we were first building the spot type. The next, spot?, is a procedure you may have written on your own. Finally, spot-list.leftmost, comes from the reading.

(define spot.new
(lambda (col row color)
(list col row color)))
(define spot.col car)
(define spot.row cadr)
(define spot.color caddr)

(define spot?
(lambda (value)
(and (list? value)
(= (length value) 3)
(integer? (spot.col value))
(integer? (spot.row value))
(rgb? (spot.color value)))))

(define spot-list.leftmost
(lambda (spots)
(if (or (not (list? spots))
(null? spots)
(not (all-spots? spots)))
(throw "spot-list.leftmost: requires a non-empty list of spots")
(if (null? (cdr spots))
(car spots)
(spot.leftmost (car spots) (spot-list.leftmost (cdr spots)))))))

Exercises

Exercise 1: Are They All Spots?

You may note that spot-list.leftmost requires an all-spots? procedure. We defined the all-spots? procedure in Homework 9, and documented a very similar procedure on Friday. Here is a definition and documentation for that procedure.

;;; Procedure:
;;; all-spots?
;;; Parameters:
;;; values, a list
;;; Purpose:
;;; To decide whether all the elements of values are spots.
;;; Produces:
;;; all-spots?, a boolean
;;; Preconditions:
;;; None [other than it's a list].
;;; Postconditions:
;;; all-spots? is #f if (spot? x) is #f for any element x in values,
;;; #t otherwise.
(define all-spots?
(lambda (values)
(or (null? values)
(and (spot? (car values))
(all-spots? (cdr values))))))

Is it necessary to test the preconditions of all-spots? Why or why not?

When you've considered and discussed your answer, see the notes on Exercise 1.

Exercise 2: Differentiating Between Errors

Revise the definition of spot-list.leftmost so that it prints a different (and appropriate) error message for each error condition.

I'd recommend that you use cond rather than if in writing this revised version.

Exercise 3: Finding Values

a. Document (using the six-P style), define, and test a procedure named index-of that takes as its parameters a value, target, and a list, values, and returns the index of target in values. You should use 0-based indices, so that the initial value in a list is at index 0. 

Here are some examples of using index-of:

> (index-of color.blue (list color.red color.green color.blue color.yellow))
2
> (index-of color.red (list color.red color.green color.blue color.yellow))
0

Note that you can implement index-of using either "plain" recursion or tail recursion. The key insight is to recognize that the index of an element is the number of values you skip over before you find the target value. If you get stuck, see the notes on Exercise 3.

b. Arrange for index-of to explicitly signal an error (by invoking the throw procedure) if val does not occur at all as an element of vals.

> (index-of color.mauve (list color.red color.green color.blue color.yellow))
Error: The color does not appear in the list.

c. If val does not occur as an element of vals, is it better to have for index-of to invoke throw or return a special value (such as -1 or #f)? Explain your answer.

When you're done, add index-of to your library. This is a very useful procedure.

Exercise 4: Changing Colors

Consider the following procedure, which increments the red component of color by 64.

;;; Procedure:
;;; rgb.much-redder
;;; Parameters:
;;; color, an RGB color
;;; Purpose:
;;; To produce a color that is much redder than color.
;;; Produces:
;;; redder-color, a color
;;; Preconditions:
;;; FORTHCOMING
;;; Postconditions:
;;; (rgb.red redder-color) = (+ 64 (rgb.red color))
;;; (rgb.green redder-color) = (rgb.green color)
;;; (rgb.blue redder-color) = (rgb.blue color)
(define rgb.much-redder
(lambda (color)
(rgb.new (+ 64 (rgb.red color)) (rgb.green color) (rgb.blue color))))

a. What preconditions must be met in order for rgb.much-redder to meet its postconditions?

b. Should we test those preconditions? Why or why not?

Exercise 5: Weighted Color Averages

In a number of exercises, we were required to blend two colors. For example, we blended colors in a variety of ways to make interesting images, and we made a color more grey by averaging it with grey. In blending two colors, we are, in essence, creating an average of the two colors, but an average in which each color contributes a different percent.

For this problem, we might write a procedure, (rgb.weighted-average fraction color1 color2) that makes a new color, each of whose components is computed by multiplying the corresponding component of color1 by fraction and adding that to the result of multiplying the corresponding component of color2 by (1-fraction). For example, we might compute the red component with

(+ (* fraction (rgb.red color1)) (* (- 1 fraction) (rgb.red color2)))

a. What preconditions should rgb.weighted-average have? (Think about restrictions on fraction, color1, and color2.)

b. How might you formally specify the postconditions for rgb.weighted-average?

c. Document rgb.weighted-average.

d. Write the code for rgb.weighted-average, making sure to test for each precondition.

For Those With Extra Time

Extra 1: Substitution in Lists

Consider a procedure, (list.substitute lst old new), that builds a new list by substituting new for old whenever old appears in lst.

> (list.substitute (list "black" "red" "green" "blue" "black") "black" "white")
(list "white" "red" "green" "blue" "white")
> (list.substitute (list "black" "red" "green" "blue" "black") "yellow" "white")
(list "black" "red" "green" "blue" "black")
> (list.substitute null "yellow" "white")
()

a. Document this procedure, making sure to carefully consider the preconditions.

b. Implement this procedure, making sure to check the preconditions.

Extra 2: Substituting Colors, Revisited

Consider a procedure, (spots.substitute spots old new), that, given a list of spots and two colors, makes a new copy of spots by using the color new whenever old appeared in the original spots.

a. What preconditions does this procedure have?

b. Implementing this procedure, using the husk-and-kernel structure to ensure that old and new are rgb colors and that spots is a list of colors, before starting the recursion..

Extra 3: Substituting Colors, Revisited

Write a procedure, (image.substitute image old new), that, given an image, makes a new copy of image by using the color new whenever old appeared in the original image. Ensure that this procedure verifies its preconditions.

> (image.substitute "/home/rebelsky/glimmer/samples/rebelsky-stalkernet.jpg" rgb.red rgb.blue)
image.substitute: Expected an image as the first parameter
> (image.substitute (image.load "/home/rebelsky/glimmer/samples/rebelsky-stalkernet.jpg") "puce" "red")
image.substitute: Expected an rgb color as the second parameter

Notes on the Problems

Notes on Problem 1: Are They All Spots?

It depends on how we will use all-spots?. If we are sure that it will only be called correctly (e.g., after we've already tested that the parameter is a list or in a context in which we can prove that the parameter is list), then it need not check its preconditions. Otherwise, it should check its preconditions.

Notes on Exercise 3: Finding Values

Here's one version of index-of:

(define index-of
(lambda (target values)
; If the value appears first in the list
(if (equal? target (car values))
; Its index is 0.
0
; Otherwise, we find the index in the cdr. Since we've
; thrown away the car in finding that index, we need to add 1.
(+ 1 (index-of target (cdr values))))))

We might also write this procedure tail-recursively.

(define index-of
(lambda (target values)
(index-of-helper target 0 values)))
(define index-of-helper
(lambda (target skipped remaining)
(if (equal? target (car remaining))
skipped
(index-of-helper target (+ skipped 1) (cdr remaining)))))

You should be able to figure out what preconditions to test and how to test them.


Janet Davis (davisjan@cs.grinnell.edu)

Created October 7, 2007 based on http://www.cs.grinnell.edu/~rebelsky/Courses/CS151/2007F/Labs/prepost-lab.html
Last revised October 7, 2007