Let's return to our shape problem again. Instead of the class of shapes in a single data definition, we could start with two data definitions, one for each basic shape:
A circle is a structure:
(make-circle p s) where p is a posn and s is a number.
A square is a structure:
(make-square p s) where p is a posn and s is a number.
Once we have developed and understood the basic data definitions, possibly by playing with examples and by writing simple functions, we can introduce data definitions that combine them. For example, we can introduce a data definition for a class of shapes that refers to the two above:
A shape is either
- a circle, or
- a square.
Now suppose we need a function that consumes shapes. First, we form a cond-expression with conditions for each part of the data definition:
;; f : shape -> ???
(define (f a-shape)
(cond
[(circle? a-shape) ...]
[(square? a-shape) ...]))
Given our guideline concerning the composition of functions from
section (define (f a-shape)
(cond
[(circle? a-shape) (f-for-circle a-shape)]
[(square? a-shape) (f-for-square a-shape)]))
This, in turn, requires that we develop the two auxiliary functions,
f-for-circle and f-for-square, including their
templates.
;; Data Definition:
(define-struct circle (center radius))
(define-struct squareP (nw lengthP))
;; A shape is either
;; 1. a structure: (make-circle p s)
;; where p is a posn, s a number;
;; 2. a structure: (make-square p s)
;; where p is a posn, s a number.
;; Data Definitions:
(define-struct circle (center radius))
;; A circle is a structure:
;; (make-circle p s)
;; where p is a posn, s a number;
(define-struct squareP (nw lengthP))
;; A square is a structure:
;; (make-square p s)
;; where p is a posn, s a number.
;; A shape is either
;; 1. a circle, or
;; 2. a square.
;; Final Definition:
;; perimeter : shape -> number
;; to compute the perimeter of a-shape
(define (perimeter a-shape)
(cond
[(circle? a-shape)
(* (* 2 (circle-radius a-shape)) pi)]
[(square? a-shape)
(* (square-length a-shape) 4)]))
;; Final Definitions:
;; perimeter : shape -> number
;; to compute the perimeter of a-shape
(define (perimeter a-shape)
(cond
[(circle? a-shape)
(perimeter-circle a-shape)]
[(square? a-shape)
(perimeter-square a-shape)]))
;; perimeter-circle : circle -> number
;; to compute the perimeter of a-circle
(define (perimeter-circle a-circle)
(* (* 2 (circle-length a-circle)) pi))
;; perimeter-square : square -> number
;; to compute the perimeter of a-square
(define (perimeter-square a-square)
(* (square-length a-square) 4))
If we follow this suggestion, we arrive at a collection of three functions,
one per data definition. The essential points of the program development
are summarized in the right column of figure
.
For a comparison, the left column contains the corresponding pieces of the
original program development. In each case, we have as many functions as
there are data definitions. Furthermore, the references between the
functions in the right column directly match the references among the
corresponding data definitions. While this symmetry between data
definitions and functions may seem trivial now, it becomes more and more
important as we study more complex ways of defining data.
Exercise 7.3.1
Modify the two versions of perimeter so that they also process
rectangles. For our purposes, the description of a rectangle includes its
upper-left corner, its width, and its height.
Solution