[previous] [up] [next]     [index]
Next: Designing Functions for Mixed Up: The Varieties of Data Previous: The Varieties of Data

Mixing and Distinguishing Data

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 [cross-reference] 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 tex2html_wrap71875

Figure: A small collection of points


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:

number?, which consumes an arbitrary value and produces true if the value is a number and false otherwise;
boolean?, which consumes an arbitrary value and produces true if the value is a boolean value and false otherwise;
symbol?, which consumes an arbitrary value and produces true if the value is a symbol and false otherwise;
struct?, which consumes an arbitrary value and produces true if the value is a structure and false otherwise.

For each structure definition, Scheme also introduces a separate predicate so that we can distinguish between distinct classes of structures. Suppose the Definitions window contains the following structure definitions:[footnote]
(define-struct posn (x y))

(define-struct star (last first dob ssn))

(define-struct airplane (kind max-speed max-load price))

Then, Scheme also knows the following three predicates:
posn?, which consumes an arbitrary value and produces true if the value is a posn structure and false otherwise;
star?, which consumes an arbitrary value and produces true if the value is a star structure and false otherwise;
airplane?, which consumes an arbitrary value and produces true if the value is a airplane structure and false otherwise.

Hence a function can distinguish a structure from a number as well as a posn structure from an airplane structure.


Exercises

Exercise 7.1.1

Evaluate the following expressions by hand:

  1. (number? (make-posn 2 3))
  2. (number? (+ 12 10))
  3. (posn? 23)
  4. (posn? (make-posn 23 3))
  5. (star? (make-posn 23 3))
Check the answers in DrScheme. Solution

Now we can develop distance-to-0. Let's start with a data definition:

A pixel-2 is either
  1. a number, or
  2. 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
  1. a circle structure:
               (make-circle p s)}
    

    where p is a posn and s is a number; or
  2. 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:

  1. (make-square (make-posn 20 20) 3),
  2. (make-square (make-posn 2 20) 3), and
  3. (make-circle (make-posn 10 99) 1).
To make up examples of input-output relationships, we need to know the purpose of the function. So suppose we need the function perimeter, which computes the perimeter of a shape. From geometry, we know that the perimeter of a square is four times its side, and the perimeter of a circle is tex2html_wrap_inline71499 times the diameter, which is twice the radius.[footnote] Thus, the perimeter of the above three examples are: 12, 12, and (roughly) 6.28, respectively.

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.


Exercises

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



[previous] [up] [next]     [index]
Next: Designing Functions for Mixed Up: The Varieties of Data Previous: The Varieties of Data

PLT