Sections 6.1 through 6.4 suggest that the design of functions for compound data proceeds in a regular manner. First, a programmer must recognize that structures are needed. We follow the simple rule of using structures whenever the description of some object specifies several pieces of information. If we don't use structures in these cases, we quickly lose track of which data belongs to which object, especially when we write large functions that process massive amounts of data.
Second, a programmer can use the structure and data definitions for the organization of a function. We use the term template when we design functions. As we will see in this and many future sections, the template matches the data definition, and the template is the essential step in the careful design of functions.
To emphasize this point, we modify our function design recipe from section 2.5 to accommodate compound data. Most importantly, working with compound data requires adjustments in a few of the basic design steps and two new steps: data analysis and template design:
;; Data Analysis & Definitions:
(define-struct student (last first teacher))
;; A student is a structure: (make-student l f t) where f, l, and t are symbols.
;; Contract: subst-teacher : student symbol -> student
;; Purpose: to create a student structure with a new
;; teacher name if the teacher's name matches 'Fritz
;; Examples:
;; (subst-teacher (make-student 'Find 'Matthew 'Fritz) 'Elise)
;; =
;; (make-student 'Find 'Matthew 'Elise)
;; (subst-teacher (make-student 'Find 'Matthew 'Amanda) 'Elise)
;; =
;; (make-student 'Find 'Matthew 'Amanda)
;; Template:
;; (define (process-student a-student a-teacher)
;; ... (student-last a-student) ...
[(symbol=? (student-teacher a-student) 'Fritz) (make-student (student-last a-student)
(student-first a-student) a-teacher)]
[else a-student])) ;; Tests:
(subst-teacher (make-student 'Find 'Matthew 'Fritz) 'Elise)
;; expected value:
(make-student 'Find 'Matthew 'Elise)
(subst-teacher (make-student 'Find 'Matthew 'Amanda) 'Elise)
;; expected value:
(make-student 'Find 'Matthew 'Amanda)
Figure 11: The design recipe for compound data: A complete example Data Analysis and Design:
Before we can develop a function, we must understand how to represent the information in our problem statement within our chosen programming language.
To do so, we search the problem statement for descriptions of (relevant) objects and then design a data representation based on the results of our analysis.
Until now we could use Scheme's classes of atomic data (numbers, symbols, images, etc.) to represent information. But they are not enough. If we discover that an object has N properties, we introduce a structure definition with N fields and supply a data definition that specifies what kind of data the fields may contain.
Let us consider functions that process student records at a school. If a student's interesting properties for a school are
1. the first name,
2. the last name, and
3. the name of the home-room teacher,
then we should represent information about a student as a structure:
(define-struct student (last first teacher))
Here is the data definition that specifies the class of student structures as precisely as possible:
A student is a structure:
(make-student l f t) where l, f, and t are symbols.
The corresponding data class contains structures like these:
(make-student 'findler 'kathi 'matthias) (make-student 'fisler 'sean 'matthias) (make-student 'flatt 'shriram 'matthias)
Contract:
For the formulation of contracts, we can use the names of the atomic classes of data, such as number and symbol, and those names that we introduced in data definitions, such as student.
Template:
A function that consumes compound data is likely to compute its result from the components of its input. To remind ourselves of the components, we first design a template. For compound data, a TEMPLATE consists of a header and a body that lists all possible selector expressions. Each selector expression is the application of an appropriate selector primitive to a parameter that stands for a structure.
In other words, a template expresses what we know about the inputs, and nothing about the outputs. We can therefore use the same template for all
functions that consume the same kind of structure. Also, because a template does not express anything about the purpose of the function, we can formulate it before or after we have developed examples.
Consider a function that consumes a student structure and a teacher name:
;; process-student : student symbol -> ???
(define (process-student a-student a-teacher) ...)
Then a-student is a parameter that stands for a structure and a-teacher stands for just a symbol. The template therefore has the following shape:
;; process-student : student symbol -> ???
(define (process-student a-student a-teacher) ... (student-last a-student) ...
... (student-first a-student) ...
... (student-teacher a-student) ...)
The ??? output reminds us that we don't assume anything about the output of the function. We design every function that consumes a student structure using this template.
Examples:
Let us study two examples of functions that consume student structures. The first function, check, is supposed to return the last name of the student if the teacher's name is equal to a-teacher and 'none otherwise:
(check (make-student 'Wilson 'Fritz 'Harper) 'Harper)
;; expected value:
'Wilson
(check (make-student 'Wilson 'Fritz 'Lee) 'Harper)
;; expected value 'none
The second function, transfer, is supposed to produce a student structure that contains the same information as a-student except for the teacher field, which should be a-teacher:
(transfer (make-student 'Wilson 'Fritz 'Harper) 'Lee)
;; expected value:
(make-student 'Wilson 'Fritz 'Lee)
(transfer (make-student 'Woops 'Helen 'Flatt) 'Fisler)
;; expected value:
(make-student 'Woops 'Helen 'Fisler)
Body:
The template provides as many clues for the definition of the function as the examples. As before, the goal of this step is to formulate an expression that computes the answer from the available data using other functions or Scheme's primitive. The template reminds us that the available data are the parameters and the data computed by the selector expressions. To determine what the selectors produce, we read the data definition for the structure.
Let us return to our first example, check:
(define (check a-student a-teacher) (cond
[(symbol=? (student-teacher a-student) a-teacher) (student-last a-student)]
[else 'none]))
This particular function uses two of the three selector expressions from the template. Specifically, it compares the result of the selector expression
(student-teacher a-student) with a-teacher and, if they are equal,
produces the result of (student-last a-student). Just naming the results of the selector expressions and reading the problem statement makes the definition obvious.
Similarly, the transfer function is easy to define using the template and the examples:
(define (transfer a-student a-teacher) (make-student (student-last a-student) (student-first a-student)
a-teacher))
This second function uses the same two selector expressions as the first example, but in a different way. The key observation, however, is that the template reminds us of all the information that we have available. When we define the function, we must use and combine the available information.
Figure 12 presents the recipe for compound data in tabular form. In practice, though, a function contains many functions that all work on the same class of input data. It is therefore normal to reuse the template many times, which means that examples are often constructed after the template is set up. Compare it with the design recipes in figures 4 and 6.
Phase Goal Activity Data
Analysis and Design
to formulate a
data definition determine how many pieces of data describe the
``interesting'' aspects of the objects mentioned in the problem statement add a structure definition and a data definition (for each class of problem object) Contract
name the function, the classes of input data, the class of output data, and specify its purpose:
;; name : in1 in2 ...--> out
search the problem statement for examples work through the examples validate the results, if possible make up examples
Template to formulate an
outline for those parameters that stand for compound values, annotate the body with selector expressions if the function is conditional, annotate all
appropriate branches Body
to define the function
develop a Scheme expression that uses Scheme's primitive operations, other functions, selector
apply the function to the inputs of the examples check that the outputs are as predicted
Figure 12: Designing a function for compound data (Refines the recipe in figure 4 (pg. 5))
Exercise 6.5.1. Develop templates for functions that consume the following structures:
1. (define-struct movie (title producer))
2. (define-struct boyfriend (name hair eyes phone))
3. (define-struct cheerleader (name number))
4. (define-struct CD (artist title price))
5. (define-struct sweater (material size producer)) . Solution
Exercise 6.5.2. Develop the function time->seconds, which consumes a time
structure (see exercise 6.4.2) and produces the number of seconds since midnight that the time structure represents.
Example:
(time->seconds (make-time 12 30 2))
;; expected value:
45002
Explain the example. Solution