In the preceding section, we used posn structures with exactly two components to represent pixels. If many of the pixels are on the x axis, we can simplify the representation by using plain numbers for those pixels and posn structures for the remaining ones.
Figure
contains a sample collection of such points. Three
of the five points, namely, C, D, and E, are on the x axis. Only
two points require two coordinates for an accurate description: A
and B. Our new idea for representing points permits us to describe
this class of points succinctly: (make-posn 6 6) for A;
(make-posn 1 2) for B; and 1, 2, and 3 for C,
D, and E, respectively.
If we now wish to define the function distance-to-0, which consumes such point representations and produces their distance to the origin, we are confronted with a problem. The function may be applied to a number or a posn. Depending on the class to which the input belongs, distance-to-0 must employ a different method to calculate the distance to the origin. Thus we need to use a cond-expression to distinguish the two cases. Unfortunately, we don't have any operations to formulate the appropriate conditions.
The Varieties of Data
.5pt ![]()
To accommodate this kind of function, Scheme provides PREDICATES, which are operations that recognize a particular form of data. The predicates for the classes of data we know are:
(define-struct posn (x y))Then, Scheme also knows the following three predicates:(define-struct star (last first dob ssn))
(define-struct airplane (kind max-speed max-load price))
Exercise 7.1.1
Evaluate the following expressions by hand:
Now we can develop distance-to-0. Let's start with a data definition:
A pixel-2 is either
- a number, or
- a posn structure.
Stating the contract, purpose, and header is straightforward:
;; distance-to-0 : pixel-2 -> number ;; to compute the distance of a-pixel to the origin (define (distance-to-0 a-pixel) ...)
As mentioned before, the function must distinguish between its two kinds of inputs, which can be accomplished with a cond-expression:
(define (distance-to-0 a-pixel)
(cond
[(number? a-pixel) ...]
[(posn? a-pixel) ...]))
The two conditions match the two possible inputs of the new
distance-to-0 function. If the first one holds, the input is a
pixel on the x axis. Otherwise the pixel is a posn structure. For
the second cond-line, we also know that the input contains two
items: the x and y coordinates. To remind ourselves, we
annotate the template with two selector expressions:
(define (distance-to-0 a-pixel)
(cond
[(number? a-pixel) ...]
[(posn? a-pixel) ... (posn-x a-pixel) ... (posn-y a-pixel) ... ]))
Completing the function is easy. If the input is a number, it is the distance to the origin. If it is a structure, we use the old formula for determining the distance to the origin:
(define (distance-to-0 a-pixel)
(cond
[(number? a-pixel) a-pixel]
[(posn? a-pixel) (sqrt
(+ (square (posn-x a-pixel))
(square (posn-y a-pixel))))]))
Let us consider a second example. Suppose we are to write functions that deal with geometric shapes. One function might have to compute the area covered by a shape, another one the perimeter, and a third could draw the shape. For the sake of simplicity, let's assume that the class of shapes includes only squares and circles and that their description includes their location (a posn) and their size (a number).
Information about both shapes must be represented with structures, because both have several attributes. Here are the structure definitions:
(define-struct square (nw length)) (define-struct circle (center radius))and the matching data definition:
A shape is either
- a circle structure:
(make-circle p s)}
where p is a posn and s is a number; or- a square structure:
(make-square p s)}
where p is a posn and s is a number.
Together, the two classes make up the class of shapes:
The next step of our design recipe requires that we make up examples. Let's start with input examples:
Following the design recipe and the precedent of distance-to-0, we start with the following skeleton of the function:
;; perimeter : shape -> number
;; to compute the perimeter of a-shape
(define (perimeter a-shape)
(cond
[(square? a-shape) ... ]
[(circle? a-shape) ... ]))
because the function must first determine to which class a-shape
belongs.
Furthermore, each possible input is a structure, so we can also add two selector expressions to each cond-clause:
;; perimeter : shape -> number
;; to compute the perimeter of a-shape
(define (perimeter a-shape)
(cond
[(square? a-shape)
... (square-nw a-shape) ... (square-length a-shape) ...]
[(circle? a-shape)
... (circle-center a-shape) ... (circle-radius a-shape) ...]))
The selector expressions remind us of the available data.
Now we are ready to finish the definition. We fill the gaps in the two answers by translating the mathematical formulae into Scheme notation:
(define (perimeter a-shape)
(cond
[(square? a-shape) (* (square-length a-shape) 4)]
[(circle? a-shape) (* (* 2 (circle-radius a-shape)) pi)]))
Since the position of a shape does not affect its perimeter, the template's
selector expressions for nw and center disappear.
Exercise 7.1.2
Test perimeter with the examples. Solution
Exercise 7.1.3
Develop the function area, which consumes either a circle or a
square and computes the area. Is it possible to reuse the template for
perimeter by changing the name to area?
Solution