On this page:
1 Arithmetic
1.1 The Arithmetic Of Numbers
1.2 The Arithmetic Of Strings
1.3 Mixing It Up
1.4 The Arithmetic Of Images
1.5 The Arithmetic Of Booleans
1.6 Mixing It Up With Booleans
1.7 Predicates:   Know Thy Data
2 Functions And Programs
2.1 Functions
2.2 Composing Functions
2.3 Programs
3 How To Design Programs
3.1 Designing Functions
3.2 Finger Exercises
3.3 Domain Knowledge
3.4 From Functions to Programs
3.5 On Testing
3.6 Designing World Programs
3.7 Virtual Pet Worlds
4 Intervals, Enumerations, Itemizations
4.1 Conditional Computations
4.2 How It Works
4.3 Enumerations
4.4 Intervals
4.5 Itemizations
4.6 Designing With Itemizations
4.7 A Bit More About Worlds
5 Adding Structure
5.1 Structures
5.2 Programming With Structures
5.3 The Universe Of Data
5.4 Designing With Structures
5.5 Structure In the World
5.6 A Graphical Editor
5.7 More Virtual Pets
6 Itemizations and Structures
6.1 Designing With Itemizations, Again
6.2 Mixing Up Worlds
6.3 Input Errors
7 Summary

I Fixed-Size Data

1 Arithmetic

When a programmer studies a new language, the first item of business is the language’s “arithmetic,” meaning its basic forms of data and the operations that a program can perform on this data. At the same time, we need to learn how to express data and how to express operations on data.

It is not necessary to understand all forms of data or all operations at once; it suffices to know what exists and where to find out more. Here we take a look at the “arithmetic” of BSL, the Beginning Student language. Thus far, we know how to use operations:
  • write "(",

  • write down the name of a primitive operation op,

  • write down the arguments, separated by some space, and

  • write down ")".

Recall what you are writing down is called an expression.

Just as a reminder, here is a primitive expression:

(+ 1 2)

It uses +, the operation for adding two numbers, followed by two arguments, which are plain numbers. But here is another, somewhat complex example:

(+ 1 (+ 1 (+ 1 1) 2) 3 4 5)

This second example exploits two points in our description that are open to interpretation. First, some primitive operations may consume more than two arguments. Second, the arguments don’t have to be numbers or data per se; they can be expressions, too.

Evaluating expressions is also straightforward. First, BSL evaluates all the arguments of a primitive operation. Second, it “feeds” the resulting pieces of data to the operation, which produces a result. Thus,
(+ 1 2)
=
3
and
(+ 1 (+ 1 (+ 1 1) 2) 3 (+ 2 2) 5)
=
(+ 1 (+ 1 2 2) 3 4 5)
=
(+ 1 5 3 4 5)
=
18
These calculations should look familiar, because they are the same kind of calculations that you performed in mathematics classes. You may have written down the steps in a different way; you may have never been taught how to write down a sequence of calculation steps. Yet, BSL performs calculations just like you do, and this should be a relief. It guarantees that you understand what it does with primitive operations and primitive data, so there is some hope that you can predict what your programs will compute. Generally speaking, it is critical for a programmer to know how the chosen language calculates, because otherwise a program’s computation may harm the people who use them or on whose behalf the programs calculate.

It is not necessary to read and understand the entire chapter in order to make progress. As soon as you sense that this chapter is slowing you down, move on to the next one. Keep in mind, though, that you may wish to return here and find out more about the basic forms of data in BSL when the going gets rough.

The rest of this chapter introduces four forms of data: numbers, strings, images, and Boolean values. It also illustrates how these forms of data are manipulated with primitive operations, often called built-in operations or primitives. Because of the nature of BSL, this book also refers to primitives as functions though you will need to read the remaining chapters of this first part to understand the reason for this terminology.

1.1 The Arithmetic Of Numbers

Most people think “numbers” and “operations on numbers” when they hear “arithmetic.” “Operations on numbers” means adding two numbers to yield a third; subtracting one number from another; or even determining the greatest common divisor of two numbers. If we don’t take arithmetic too literally, we may even include the sine of an angle, rounding a real number to the closest integer, and so on.

The BSL language supports Numbers and arithmetic in all these forms. As discussed in the Prologue, an arithmetic operation such as + is used like this:

(+ 3 4)

that is, in prefix notation form. Here are some of the operations on numbers that our language provides: +, -, *, /, abs, add1, ceiling, denominator, exact->inexact, expt, floor, gcd, log, max, numerator, quotient, random, remainder, sqr, and tan. We picked our way through the alphabet, just to show the variety of operations. Explore what these do in the interactions area, and then find out how many more there are and what they do.

If you need an operation on numbers that you know from grade school or high school, chances are that BSL knows about it, too. Guess its name and experiment in the interaction area. Say you need to compute the sin of some angle; try

> (sin 0)

0

and use it happily ever after. Or look in the HelpDesk. You will find there that in addition to operations, BSL also recognizes the names of some widely used numbers, for example, pi and e.You might not know e if you have not studied calculus. It’s a real number, close to 2.718, commonly called “Euler’s constant.”

When it comes to numbers, BSL programs may use natural numbers, integers, rational numbers, real numbers, and complex numbers. We assume that you have heard of the first four. The last one may have been mentioned in your high school. If not, don’t worry; while complex numbers are useful for all kinds of calculations, a novice doesn’t have to know about them.

A truly important distinction concerns the precision of numbers. For now, it is important to understand that BSL distinguishes exact numbers and inexact numbers. When it calculates with exact numbers, BSL preserves this precision whenever possible. For example, (/ 4 6) produces the precise fraction 2/3, which DrRacket can render as a proper fraction, an improper fraction, or as a mixed decimal. Play with your computer’s mouse to find the menu that changes the fraction into decimal expansion and other presentations.

Some of BSL’s numeric operations cannot produce an exact result. For example, using the sqrt operation on 2 produces an irrational number that cannot be described with a finite number of digits. Because computers are of finite size and BSL must somehow fit such numbers into the computer, it chooses an approximation: #i1.4142135623730951. As mentioned in the Prologue, the #i prefix warns novice programmers of this lack of precision. While most programming languages choose to reduce precision in this manner, few advertise it and fewer even warn programmers.

Exercise 1: The direct goal of this exercise is to create an expression that computes the distance of some specific Cartesian point (x,y) from the origin (0,0). The indirect goal is to introduce some basic programming habits, especially the use of the interactions area to develop expressions.

The values for x and y are given as definitions in the definitions area (top half) of DrRacket:
(define x 3)
(define y 4)
The expected result for these values is 5 but your expression should produce the correct result even after you change these definitions.

Just in case you have not taken geometry courses or in case you forgot the formula that you encountered there, the point (x,y) has the distance

from the origin. After all, we are teaching you how to design programs not how to be a geometer.

To develop the desired expression, it is best to hit RUN and to experiment in the interactions area. The RUN action tells DrRacket what the current values of x and y are so that you can experiment with expressions that involve x and y:
> x

3

> y

4

> (+ x 10)

13

> (* x y)

12

Once you have the expression that produces the correct result, copy it from the interactions area to the definitions area, right below the two variable definitions.

To confirm that the expression works properly, change the two definitions so that x represents 12 and y stands for 5. If you click RUN now, the result should be 13.

Your mathematics teacher would say that you computed the distance formula. To use the formula on alternative inputs, you need to open DrRacket, edit the definitions of x and y so they represent the desired coordinates, and click RUN. But this way of reusing the distance formula is cumbersome and naive. Instead, we will soon show you a way to define functions, which makes re-using formulas straightforward. For now, we use this kind of exercise to call attention to the idea of functions and to prepare you for programming with them.

1.2 The Arithmetic Of Strings

A wide-spread prejudice about computers concerns its innards. Many believe that it is all about bits and bytes—whatever those are—and possibly numbers, because everyone knows that computers can calculate. While it is true that electrical engineers must understand and study the computer as just such an object, programmers and everyone else need never (ever) succumb to this thinking.

Programming languages are about calculating with information, and information comes in all shapes and forms. For example, a program may deal with colors, names, business letters, or conversations between people. Even though we could encode this kind of information as numbers, it would be a horrible idea. Just imagine remembering large tables of codes, such as 0 means “red” and 1 means “hello,” etc.

Instead most programming languages provide at least one kind of data that deals with such symbolic information. For now, we use BSL’s strings. Generally speaking, a String is a sequence of the characters that you can enter on the keyboard enclosed in double quotes, plus a few others, about which we aren’t concerned just yet. In Prologue: How to Program, we have seen a number of BSL strings: "hello", "world", "blue", "red", etc. The first two are words that may show up in a conversation or in a letter; the others are names of colors that we may wish to use.

BSL includes only one operation that exclusively consumes and produces strings: string-append, which, as we have seen in Prologue: How to Program concatenates two given strings into one. Think of string-append as an operation that is just like +. While the latter consumes two (or more) numbers and produces a new number, the former consumes two or more strings and produces a new string:
> (string-append "what a " "lovely " "day" " for learning BSL")

"what a lovely day for learning BSL"

Nothing about the given numbers changes when + adds them up; and nothing about the given strings changes when string-append juxtaposes them into one big string.

Exercise 2: Add the following two lines to the definitions area:
(define prefix "hello")
(define suffix "world")
Then use string primitives to create an expression that concatenates prefix and suffix and adds "_" between them. When you run this program, you will see "hello_world" in the interactions area.

See exercise 1 for how to create expressions using DrRacket.

1.3 Mixing It Up

All other operations concerning strings consume or produce data other than strings. Here are some examples:
  • string-length consumes a string and produces a (natural) number;

  • string-ith consumes a string s together with a natural number i and then extracts the one-character substring located at the ith position (counting from 0); and

  • number->string consumes a number and produces a string.

Also look up substring and find out what it does.

If the documentation in HelpDesk appears confusing, experiment with the functions in the interaction area. Give them appropriate arguments, find out what they compute. Also use inappropriate arguments for some operations just to find out how BSL reacts:
> (string-length 42)

string-length: expects a string, given 42

As you can see, BSL reports an error. The first part “string-length” informs you about the operation that is misapplied; the second half states what is wrong with the arguments. In this specific example, string-length is supposed to be applied to a string but is given a number, specifically 42.

Naturally, it is possible to nest operations that consume and produce different kinds of data as long as you keep track of what is proper and what is not. Consider this expression from the Prologue:

(+ (string-length "hello world") 60)

The inner expression applies string-length to our favorite string, "hello world". The outer expression has + consume the result of the inner expression and 60. Let us calculate out the result:
(+ (string-length "hello world") 60)
=
(+ 11 60)
=
71
Not surprisingly, calculating with such nested expressions that deal with a mix of data is no different from calculating with numeric expressions. Here is another example:
(+ (string-length (number->string 42)) 2)
=
(+ (string-length "42") 2)
=
(+ 2 2)
=
4
Before you go on, construct some nested expressions that mix data in the wrong way, e.g.,

(+ (string-length 42) 1)

Run them in DrRacket. Study the red error message but also watch what DrRacket highlights in the definitions area.

Exercise 3: Add the following two lines to the definitions area:
(define str "helloworld")
(define i 5)
Then create an expression using string primitives that adds "_" at position i. In general this means the resulting string is longer than the original one; here the expected result is "hello_world".

Position means i characters from the left of the string—but computer scientists start counting at 0. Thus, the 5th letter in this example is "w", because the 0th letter is "h". Hint: when you encounter such “counting problems” you may wish to add a string of digits below str to help with counting:
(define str "helloworld")
(define ind "0123456789")
(define i 5)

See exercise 1 for how to create expressions in DrRacket.

Exercise 4: Use the same setup as in exercise 3. Then create an expression that deletes the ith position from str. Clearly this expression creates a shorter string than the given one; contemplate which values you may choose for i.

1.4 The Arithmetic Of Images

Images represent symbolic data somewhat like strings. To work with images, use the "2htdp/image" teachpack. Like strings, you used DrRacket to insert images wherever you would insert an expression into your program, because images are values just like numbers and strings.

Your programs can also create an image with primitive operations on images. These primitive operations come in three flavors. The first kind concerns the creation of basic images:
  • circle produces a circle image from a radius, a mode string, and a color string;

  • ellipse produces an ellipse from two radii, a mode string, and a color string;

  • line produces a line from two points and a color string;

  • rectangle produces a rectangle from a width, a height, a mode string, and a color string;

  • text produces a text image from a string, a font size, and a color string; and

  • triangle produces an upward-pointing equilateral triangle from a size, a mode string, and a color string.

The names of these operations mostly explain what kind of image they create. All you need to know is that mode strings means either "solid" or "outline" and color strings are strings such as "orange", "black", etc. Play with these operations; enter (ellipse 10 20 "solid" "green") into DrRacket’s interaction window and see what happens. Also use your mouse to save the resulting images.To save, (right)click on the image and use the pop-up menu to save the image.

BSL comes with many more image creation operations. Here is one that creates star images:
> (star 12 "solid" "green")

image

Look up the documentation for regular-polygon and experiment.

The second kind of functions on images concern image properties:
  • image-width determines the width of a given image in terms of pixels;

  • image-height determines the height of an image;

A proper understanding of the third kind of image primitives—functions that compose images—requires the introduction of one new idea: the anchor point. An image isn’t just a single pixels; it consists of many pixels. Specifically, each image is like a photograph, that is, a rectangle of pixels. One of these pixels—the upper left one—is the designated anchor point. When you use an image primitive to compose two images, the composition happens with respect to the anchor points unless you specify some other point explicitly:

  • overlay places all the images to which it is applied on top of each other, using the default anchor point for each. You encountered this function in the Arithmetic And Arithmetic.

  • overlay/xy is like overlay but accepts two numbers—x and ybetween two image arguments. It shifts the second image by x pixels to the right and y pixels down —all with respect to the images’ anchor points. Of course, the image is shifted left for a negative x and up for a positive y.

  • overlay/align is like overlay but accepts two strings that shift the anchor points to other parts of the rectangles. There are nine different positions overall; experiment with all possibilities!

The "2htdp/image" teachpack comes with many other primitive functions for combining images. As you get familiar with image processing, you will want to read up on those. For now, we introduce three more because they are important for creating animated scenes and images for games:
  • empty-scene creates an framed rectangle of a specified width and height;

  • place-image places an image into a scene at a specified position. If the image doesn’t fit into the given scene, it is appropriately cropped.

  • add-line consumes an scene, four numbers, and a color to draw a line of that color into the given image. Again, experiment with it to find out how the four arguments work together.

Copy and paste the image into your DrRacket.

Exercise 5: Add the following line to the definitions area:

(define cat )

Create an expression that computes the area of the image. See exercise 1 for how to create expressions in DrRacket.

Exercise 6: Use the picture primitives to create the image of a simple automobile.

Exercise 7: Use the picture primitives to create the image of a simple boat.

Exercise 8: Use the picture primitives to create the image of a simple tree.

1.5 The Arithmetic Of Booleans

We need one last kind of primitive data before we can design programs: Boolean values. There are only two kinds of Boolean values: true and false. Programs use Boolean values for representing decisions or the status of switches.

Calculating with Boolean values is simple, too. In particular, BSL programs get away with three operations: or, and, and not. These operations are kind of like addition, multiplication, and negation for numbers. Of course, because there are only two Boolean values, it is actually possible to demonstrate how these functions work in all possible situations:
It shouldn’t come as a surprise that or and and may be used with more than two Boolean values.

Finally, there is more to or and and than these explanations suggest, but to explain the extra bit requires another look at mixing up data in nested expressions.

Exercise 9: Add the following two lines to the definitions area of DrRacket:
(define b1 true)
(define b2 false)
Create an expression that computes whether b1 is false or b2 is true. So in this particular case, the answer is false. (Why?)

See exercise 1 for how to create expressions in DrRacket. How many possible combinations of true and false can you think of for associating with b1 and b2?

1.6 Mixing It Up With Booleans

One important use of Boolean values concerns calculations with many different kinds of data. We know from the prologue that BSL programs may name values via definitions. For example, we could start a program like this

(define x 2)

and then compute its inverse:

(define inverse-of-x (/ 1 x))

This works fine, as long as we don’t edit the program and change x to 0.

This is where Boolean values come in, in particular conditional calculations. First, the primitive function = determines whether two (or more) numbers are equal. If so, it produces true, otherwise false. Second, there is kind of BSL expression that we haven’t mentioned so far: the if expression. It uses the word “if” as if it were a primitive function; it isn’t. The word “if” is followed by three expressions, separated by blank spaces (that includes tabs, line breaks, etc). Naturally the entire expression is enclosed in parentheses. Here is an example:

(if (= x 0) 0 (/ 1 x))

This if expression contains the subexpressions (= x 0), 0, and (/ 1 x). The evaluation of this expression proceeds in two steps:
  1. The first expression is always evaluated. Its result must be a Boolean.

  2. If the result of the first expression is true, then the second expression is evaluated; otherwise the third one. Whatever their results are they are also the result of the entire if expression.

You can experiment with if expressions in the interactions area:
> (if (= x 0) 0 (/ 1 x))

0.5

And you can reason out the result of this interaction yourself. Since x is 2, it is not equal to 0. Hence, (= x 0) produces the result false, meaning the third expression is evaluated and its result becomes the result of the entire if expression.

Now imagine you edit the definition so that it looks like this:

(define x 0)

What do you think

(if (= x 0) 0 (/ 1 x))

evaluates to in this context? Why?

In addition to =, BSL provides a host of other comparison primitives. Explain what the following four comparison primitives determine about numbers: <, <=, >, >=.

Strings aren’t compared with = and its relatives. Instead, you must use string=? or string<=? or string>=? if you are ever in a position where you need to compare strings. While it is obvious that string=? checks whether the two given strings are equal, the other two primitives are open to interpretation. Look up their documentation, or experiment with them, guess, and then check in the documentation whether you guessed right.

Images also come with one comparison operation: image=?. Like string=?, image=? compares two images for equality. You may wonder why it is ever necessary to compare strings with each other. So imagine a program that deals with traffic lights. It may use the strings "green", "yellow", and "red". This kind of program may contain a fragment such as this:
(define current-color ...)
 
(define next-color (if (string=? "green" current-color) "yellow" ...))
The dots here aren’t a part of the program of course. Replace them with a string that refers to a color. It should be easy to imagine that this fragment deals with the computation that determines which light bulb is to be turned on next and which one should be turned off.

The next few chapters introduce better expressions than if to express conditional computations and, most importantly, systematic ways for designing them.

Exercise 10: Add the following line to the definitions area:

(define cat )

Create an expression that computes whether the image is "tall" or "wide". An image should be labeled "tall" if its height is larger or equal to its width; otherwise it is "wide". See exercise 1 for how to create expressions in DrRacket; as you experiment, replace the image of the cat with rectangles of your choice to ensure you know the expected answer.

Now try the following modification. Create an expression that computes whether a picture is "tall", "wide", or "square".

1.7 Predicates: Know Thy Data

Remember the expression (string-length 42) and its result. Actually, the expression doesn’t have a result; it signals an error. DrRacket lets you know about errors via red text in the interactions area and high-lighting of the expression. This way of marking errors is particularly helpful when you use this expression (or its relatives) deeply nested within some other expression:

(* (+ (string-length 42) 1) pi)

Experiment with this expression by entering it into both DrRacket’s interactions area and in the definitions area (plus clicking on RUN).

Of course, you really don’t want such error-signaling expressions in your program. And usually, you don’t make such obvious mistakes as adding true to "hello world". It is quite common, however, that programs deal with variables that may stand for either a number or a string:
(define in ...)
 
(string-length in)
A variable such as in can be a place holder for any value, including a number, and this value then shows up in the string-length expression.

One way to prevent such accidents is to use a predicate, which is a function that consumes a value and determines whether or not it belongs to some class of data. For example, the predicate number? determines whether the given value is a number or not:
> (number? 4)

true

> (number? pi)

true

> (number? true)

false

> (number? "fortytwo")

false

As you see, the predicates produce Boolean values. Hence, when predicates are combined with conditional expressions, programs can protect expressions from misuse:
(define in ...)
 
(if (string? in) (string-length in) ...)

Every class of data that we introduced in this chapter comes with a predicate: string?, image?, and boolean?. Experiment with them to ensure you understand how they work.

In addition to predicates that distinguish different forms of data, programming languages also come with predicates that distinguish different kinds of numbers. In BSL, numbers are classified in two ways: by construction and by their exactness. Construction refers to the familiar sets of numbers: integer?, rational?, real?, and complex?. You may know these terms from secondary school.Evaluate (sqrt -1) in the interactions area and take a close look at the result. The result you see is the first so-called complex number anyone encounters. While your mathematics teacher may have told you that one doesn’t compute the square root of negative numbers, truth is that real mathematicians and programmers find it acceptable and useful to do so anyway. But don’t worry: understanding complex numbers is not essential to being a programmer. As for exactness, we have mentioned the idea before. For now, experiment with exact? and inexact? to make sure they perform the checks that their names suggest. Later we are going to discuss the nature of numbers in some detail.

Exercise 11: Add the following line to the definitions area of DrRacket:

(define in "hello")

Then create an expression that converts whatever in represents to a number. For a string, it determines how long the string is; for an image, it uses the area; for a number, it decrements the number, unless it is already 0 or negative; for true it uses 10 and for false 20.

See exercise 1 for how to create expressions in DrRacket.

Exercise 12: Now relax, eat, sleep, and then tackle the next chapter.

2 Functions And Programs

As far as programming is concerned, arithmetic is half the game. The other half is “algebra.” Of course, our notion of “algebra” relates to the school notion of algebra just as much as the notion of “arithmetic” from the preceding chapter relates to the ordinary notion of grade-school arithmetic. The creation of real programs involves variables and thus functions, meaning basic notions from algebra. Once we can deal with functions, we can move on to programs, which “compose” functions to achieve their overall purpose.

2.1 Functions

From a high-level perspective, a program is a function. A program, like a function in mathematics, consumes inputs, and it produces outputs. In contrast to mathematical functions, programs work with a whole variety of data: numbers, strings, images, and so on. Furthermore, programs may not consume all of the data at once; instead a program may incrementally request more data or not, depending on what the computation needs. Last but not least, programs are triggered by external events. For example, a scheduling program in an operating system may launch a monthly payroll program on the last day of every month. Or, a spreadsheet program may react to certain events on the keyboard with filling some cells with numbers.

Definitions: While many programming languages obscure the relationship between programs and functions, BSL brings it to the fore. Every BSL programs consists of definitions, usually followed by an expression that involves those definitions. There are two kinds of definitions:

Like expressions, function definitions in BSL come in a uniform shape. We write down
  • “(define (”,

  • the name of the function,

  • followed by one or more variables, separated by space and ending in “)”,

  • and an expression followed by “)”.

And that is all there is to function definitions. Here are some silly examples:

Before we explain why these examples are silly, we need to explain what function definitions mean. Roughly speaking, a function definition introduces a new operation on data; put differently, it adds an operation to our vocabulary if we think of the primitive operations as the ones that are always available. Like a primitive function, a defined function consumes inputs. The number of variables determines how many inputs—also called arguments or parametersa function consumes. Thus, f is a one-argument function, which we also refer to as a unary function. In contrast, g is a two-argument function, also dubbed binary, and h is a ternary or three-argument function. The expression—often referred to as the function bodydetermines the output.

The examples are silly because the expressions inside the functions do not involve the variables. Since variables are about inputs, not mentioning them in the expressions means that the function’s output is independent of their input. We don’t need to write functions or programs if the output is always the same.

Variables aren’t data, but they represent data. For example, a constant definition such as

(define x 3)

says that x always stands for 3. The variables in a function header, i.e., the variables that follow the function name, are placeholders for unknown pieces of data in the function body. Consider the following fragment of a definition:

(define (ff a) ...)

Its function header is (ff a), meaning ff consumes one piece of input, and the variable a is a placeholder for this input. Of course, at the time we define a function, we don’t know what its input(s) will be. Indeed, the whole point of defining a function is that we can use the function many times on many different inputs.

Useful function bodies refer to the function parameters. A reference to a function parameter is really a reference to the piece of data that is the input to the function. If we complete the definition of ff like this
(define (ff a)
  (* 10 a))
we are saying that the output of a function is ten times its input. Presumably this function is going to be supplied with numbers as inputs, because it makes no sense to multiply images or Boolean values or strings by 10.

For now, the only remaining question is how a function obtains its inputs. And to this end, we turn to the notion of applying a function.

Applications: A function application puts defined functions to work and it looks just like the applications of a primitive operation:
  • write “(”,

  • write down the name of a defined function f,

  • write down as many arguments as f consumes, separated by some space, and

  • write down “)”.

With this bit of explanation, you can now experiment with functions in the interactions area just as we suggested you experiment with primitives to find out what they compute. The following four experiments, for example, confirm that f from above produces the same value no matter what input it is applied to:
> (f 1)

1

> (f 2)

1

> (f "hello world")

1

> (f true)

1

Evaluate (f (circle 3 "solid" "red")) in the interactions area to show that not even images as inputs change f’s behavior. Here is what happens when the function is applied to too few or too many arguments:
> (f)

f: expects 1 argument, but found none

> (f 1 2 3 4 5)

f: expects only 1 argument, but found 5

It signals an error that is just like those you see when you apply a primitive to the wrong number of arguments:
> (+)

+: expects at least 2 arguments, but found none

Evaluating a function application proceeds in three steps. First, DrRacket determines the values of the argument expressions. Second, it checks that the number of arguments and the number of function parameters (inputs) are the same. If not, it signals an error. Finally, if the number of actual inputs is the number of expected inputs, DrRacket computes the value of the body of the function, with all parameters replaced by the corresponding argument values. The value of this computation is the value of the function application.

Here is a sample calculation for f:
(f (+ 1 1))
=
(f 2)
=
1
It is time to explore the stepper in DrRacket. Enter the definition of ff into the definitions area, followed by the expression (ff (+ 1 1)). Then click the STEPPER. When the stepper window comes up, use the NEXT STEP and watch how the stepper performs the same calculations as we do. In general, the interactions area computes what an expression evaluates to while the stepper displays how this evaluation proceeds. Of course, because x does not occur in the body of f, replacing all occurrences of x with 2 produces just the function body, which is 1. For ff, whose parameter is a and whose body is (* 10 a), we get a very different kind of calculation:
(ff (+ 1 1))
=
(ff 2)
=
(* 10 2)
=
20

Functions don’t have to be applied at the prompt in the interactions area. It is perfectly acceptable to use function applications nested within an expression:
> (+ (ff 3) 2)

32

> (* (ff 4) (+ (ff 3) 2))

1280

Using our rules of computation, these results are predictable. Here is the calculation for the first expression:
(+ (ff 3) 2)
=
(+ (* 10 3) 2)
=
(+ 30 2)
=
32
Naturally, we can reuse the result of this calculation to determine the result of the second expression:
(* (ff 4) (+ (ff 3) 2))
=
(* (* 10 4) (+ (ff 3) 2))
=
(* 40 (+ (ff 3) 2))
=
(* 40 32)
=
1280
In short, programs compute in the same way as students in middle school and high school: via substitution and arithmetic.

This description also generalizes to functions that process strings, Boolean values, images, and other forms of data. Recall the following “law” of string arithmetic:
(string-append "hello" " " "world")
=
"hello world"
Now suppose we define a function that creates the opening of a letter:
(define (opening first last)
  (string-append "Dear " first ","))
An experiment in the interactions area yields the expected result:
> (opening "Matthew" "Krishnamurthi")

"Dear Matthew,"

Most importantly, though, our laws of evaluating expressions explain why this result is the proper one:
(opening "Matthew" "Krishnamurthi")
=
(string-append "Dear " "Matthew" ",")
=
"Dear Matthew,"

To summarize, this section introduces the notation for function applications—the way of putting functions to work—and the mechanism for evaluating function applications and expressions in general. Our key insight is that substitution of argument values for parameters plus basic laws about primitive operations empower us to predict how a function or an expression compute their results—a critical ability if we wish to understand what our programs really do and not just blindly hope for the best.

Exercise 13: Define a function that consumes two numbers, x and y, and that computes the distance of point (x,y) to the origin.

In exercise 1 you developed the right-hand side for this function. All you really need to do is add a function header. Remember this idea in case you are ever stuck with a function. Use the recipe of exercise 1 to develop the expression in the interactions area, and then write down the function definition.

Exercise 14: Define the function cube-volume, which accepts the length of a side of a cube and computes its volume. If you have time, consider defining cube-surface, too.

Exercise 15: Define the function string-first, which extracts the first character from a non-empty string. Don’t worry about empty strings.

Exercise 16: Define the function string-last, which extracts the last character from a non-empty string. Don’t worry about empty strings.

Exercise 17: Define the function bool-imply. It consumes two Boolean values, call them b1 and b2. The answer of the function is true if b1 is false or b2 is true. Note: Logicians call this Boolean operation implication and often use the symbol =>, pronounced “implies,” for this purpose. While BSL could define a function with this name, we avoid that name because it is too similar to the comparison operations for numbers <= and >=, and it would thus easily be confused. See exercise 9.

Exercise 18: Define the function image-area, which computes the area of a given image. Note: The area is also the number of pixels in the picture. See exercise 5 for ideas.

Exercise 19: Define the function image-classify, which consumes an image and produces "tall" if the image is taller than it is wide, "wide" if it is wider than it is tall, or "square" if its width and height are the same. See exercise 10 for ideas.

Exercise 20: Define the function string-join, which consumes two strings and appends them with "_" in between. See exercise 2 for ideas.

Exercise 21: Define the function string-insert, which consumes a string and a number i and which inserts "_" at the ith position of the string. Assume i is a number between 0 and the length of the given string (inclusive). See exercise 3 for ideas. Also ponder the question how string-insert ought to cope with empty strings.

Exercise 22: Define the function string-delete, which consumes a string and a number i and which deletes the ith position from str. Assume i is a number between 0 (inclusive) and the length of the given string (exclusive). See exercise 4 for ideas. Can string-delete deal with empty strings?

2.2 Composing Functions

A program rarely consists of a single function definition and an application of that function. Instead, a typical program consists of a “main” function or a small collection of “main event handlers.” All of these use other functions—built-in primitives as well as functions that you define—and turn the result of one function application into the input for another. In analogy to middle school mathematics, we call this way of defining functions composition, and we call these additional functions auxiliary functions or helper functions.

Consider a program for filling in letter templates. It consumes the first and last name of the addressee, an opening with the letter body, and a closing:
(define (letter fst lst signature-name)
  (string-append
    (opening fst)
    "\n\n"
    (body fst lst)
    "\n\n"
    (closing signature-name)))
 
(define (opening fst)
  (string-append "Dear " fst ","))
 
(define (body fst lst)
  (string-append
   "We have discovered that all people with the last name " "\n"
   lst " have won our lottery. So, " fst ", " "\n"
   "hurry and pick up your prize."))
 
(define (closing signature-name)
  (string-append
   "Sincerely,"
   "\n\n"
   signature-name))
Now enter these four definitions into the definitions area and click on RUN. You can then evaluate expressions in the interactions area and create small letters:
> (letter "Matthew" "Krishnamurthi" "Felleisen")

"Dear Matthew,\n\nWe have discovered that all people with the last name \n\nKrishnamurthi have won our lottery. So, Matthew, hurry \n\nand pick up your prize.\n\nSincerely,\n\nFelleisen"

Note how the result is a long string that contains occurrences of "\n". These substrings represent the newline character within a string. Once the string is written to a file, it is broken into actual lines wherever "\n" occurs.

In general, when a problem refers to distinct tasks of computation, a program should consist of one function per task and a main function that puts it all together. We formulate this idea as a simple slogan:

Define one function per task.

The advantage of following this slogan is that you get reasonably small functions, each of which is easy to comprehend, and whose composition is easy to understand. Later, we see that creating small functions that work correctly is much easier than creating one large function. Better yet, if you ever need to change a part of the program due to some change to the problem statement, it tends to be much easier to find the relevant program parts when it is organized as a collection of small functions.

Here is a small illustration of this point with a sample problem:

Sample Problem: Imagine the owner of a monopolistic movie theater. He has complete freedom in setting ticket prices. The more he charges, the fewer the people who can afford tickets. The less he charges, the more it costs to run a show because attendance goes up. In a recent experiment the owner determined a relationship between the price of a ticket and average attendance.

At a price of $5.00 per ticket, 120 people attend a performance. For each 10-cent change in the ticket price, the average attendance changes by 15 people. That is, if the owner charges $5.10, some 105 people attend on the average; if the prices goes down to $4.90, average attendance increases to 135. Let us translate this idea into a mathematical formula:

avg. attendance = 120 - (change in price * (15 people / 0.10))

Stop! Explain the minus sign before you proceed.

Unfortunately, the increased attendance also comes at an increased cost. Every performance comes at a fixed costs of $180 to the owner plus a variable cost of $0.04 per attendee.

The owner would like to know the exact relationship between profit and ticket price so that he can determine the price at which he can make the highest profit.

While the task is clear, how to go about it is not. All we can say at this point is that several quantities depend on each other.

When we are confronted with such a situation, it is best to tease out the various dependencies, one by one:
  1. The problem statement specifies how the number of attendees depends on the ticket price. Computing this number is clearly a separate task and thus deserves its own function definition:

    (define (attendees ticket-price)
      (- 120 (* (- ticket-price 5.0) (/ 15 0.1))))
    The function mentions the four constants—120, 5.0, 15, and 0.1determined by the owner’s experience.

  2. The revenue is exclusively generated by the sale of tickets, meaning it is exactly the product of ticket price and number of attendees:

    (define (revenue ticket-price)
      (* ticket-price (attendees ticket-price)))
    The number of attendees is calculated by the attendees function of course.

  3. The costs consist of two parts: a fixed part ($180) and a variable part that depends on the number of attendees. Given that the number of attendees is a function of the ticket price, a function for computing the cost of a show also consumes the price of a ticket and uses it to compute the number of tickets sold with attendees:

    (define (cost ticket-price)
      (+ 180 (* 0.04 (attendees ticket-price))))
    Again, this function also uses attendees to determine the number of attendees.

  4. Finally, profit is the difference between revenue and costs:

    (define (profit ticket-price)
      (- (revenue ticket-price)
         (cost ticket-price)))

    Even the definition of profit suggests that we use the functions revenue and cost. Hence, the profit function must consume the price of a ticket and hand this number to the two functions it uses.

These four functions are all there is to the computation of the profit, and we can now use the profit function to determine a good ticket price.

Exercise 23: Our solution to the sample problem contains several constants in the middle of functions. As One Program, Many Definitions already points out, it is best to give names to such constants so that future readers understand where these numbers come from. Collect all definitions in DrRacket’s definitions area and change them so that all magic numbers are refactored into constant definitions.

Exercise 24: Determine the potential profit for the following ticket prices: $1, $2, $3, $4, and $5. Which price should the owner of the movie theater choose to maximize his profits? Determine the best ticket price down to a dime.

Here is an alternative version of the same program, given as a single function definition:
(define (profit price)
  (- (* (+ 120
           (* (/ 15 0.1)
              (- 5.0 price)))
        price)
     (+ 180
        (* 0.04
           (+ 120
              (* (/ 15 0.1)
                 (- 5.0 price)))))))
Enter this definition into DrRacket and ensure that it produces the same results as the original version for $1, $2, $3, $4, and $5. A single look should suffice to show how much more difficult it is to comprehend this one function compared to the above four.

Exercise 25: After studying the costs of a show, the owner discovered several ways of lowering the cost. As a result of his improvements, he no longer has a fixed cost. He now simply pays $1.50 per attendee.

Modify both programs to reflect this change. When the programs are modified, test them again with ticket prices of $3, $4, and $5 and compare the results.

2.3 Programs

You are ready to create simple programs. From a coding perspective, a program is just a bunch of function definitions with one distinct function that composes them. From the perspective of launching a program, however, there are two distinct kinds:
  • a batch program consumes all of its inputs at once and computes its result. Its main function composes auxiliary functions, which may refer to additional auxiliary functions, and so on. When we launch a batch program, the operating system calls the main function on its inputs and waits for the program’s output.

  • an interactive program consumes some of its inputs, computes, produces some output, consumes more input, and so on. We call the appearance of an input an event, and we create interactive programs as event-driven programs. The main function of such an event-driven program uses an expression to describe which functions to call for which kinds of events. When we launch an interactive program, the main function informs the operating system of this description, then the operating system calls the matching functions when the next input shows up. The operating system also knows how to present the result of these function calls as output.

This book focuses mostly on programs that interact via graphical user interfaces (GUI); there are other kinds of interactive programs, and you will get to know those as you continue to study computer science.

In this section we present simple examples of both batch and interactive programs. Before we do so, however, we need one more ingredient: constant definitions.

Global Constants: Prologue: How to Program demonstrates that good programmers define constants in addition to functions. To define a constant means to give a name to a value. In BSL, such a definition has the following shape:
  • write “(define ”,

  • write down the name of the variable ...

  • followed by a space and an expression, and

  • write down “)”.

The name of a constant is a global variable while the definition is called a constant definition. We tend to call the expression in a constant definition (the) right-hand side (of the definition).

As illustrated in Prologue: How to Program, constants come in all shapes and forms: numbers, images, strings, and so on. Here are some simple examples,
; temperature (in deg F) when water freezes:
(define FREEZING 32)
 
; useful to compute the area of a disk:
(define ALMOST-PI 3.14)
 
; a blank line:
(define NL "\n")
 
; an empty scene:
(define MT (empty-scene 100 100))
The first two are numeric constants, the last two are a string and an image. By convention, we use upper-case letters for global constants, because it ensures that no matter how large the program is, the readers of our programs can easily distinguish such variables from others.

All functions in a program may refer to these global variables. A reference to a variable is just like using the corresponding constants. The advantage of using variable names instead of constants is that a single edit of a constant definition affects all uses. For example, we may wish to add digits to ALMOST-PI or enlarge an empty scene:
(define ALMOST-PI 3.14159)
 
; an empty scene:
(define MT (empty-scene 200 800))

Most of our sample definitions employ literal constants on the right hand side, but the last one uses an expression. And indeed, a programmer can use arbitrary expressions to compute constants. Suppose a program needs to deal with an image of some size and its center:
(define WIDTH 100)
(define HEIGHT 200)
 
(define MID-WIDTH (/ WIDTH 2))
(define MID-HEIGHT (/ HEIGHT 2))
It can use two definitions with literal constants on the right-hand side and two computed constants, that is, variables whose values are not just literal constants but the results of calculating the location of the center.

Batch Programs: As mentioned, a batch program consumes all of its inputs at once and computes the result from these inputs. Its main function may expect the arguments themselves or the names of files from which to retrieve the inputs; similarly, it may just return the output or it may place it in a file.

Once programs are created, we want to use them. In DrRacket, we launch batch programs in the interactions area so that we can watch the program at work.

Programs are even more useful if they can retrieve the input from some file and deliver the output to some other file. The name batch program originated from the early days of computing when a program read an entire file (or several files) and placed the result in some other file(s), without any intervention after the launch. Conceptually, we can think of the program as reading an entire file at once and producing the result file(s) all at once.

We create such file-based batch programs with the "batch-io" teachpack, which adds two functions to our vocabulary (among others):
  • read-file, which reads the content of an entire file as a string, and

  • write-file, which creates a file from a given string.

Before you evaluate these expressions in DrRacket, save the definitions area in a file. For example, (write-file "sample.dat" "212") creates a file containing

212

Conversely, evaluating (read-file "sample.dat") afterward produces "212":
> (read-file "sample.dat")

"212"

The result of write-file (a representation of the name of the output location) is an acknowledgment that it has placed the string in the file. If the file already exists, it replaces its content with the given string; otherwise, it creates a file and makes the given string its content.

The names 'stdout and 'stdin are short for standard output and input device, respectively. For historical and pragmatic reasons, write-file also accepts 'stdout, a special kind of token, as the first argument. It then displays the resulting file content in the current interactions area, for example:
> (write-file 'stdout "212")

212

'stdout

By analogy, read-file accepts 'stdin in lieu of a file name and then reads input from the current interactions area.

Let us illustrate the creation of a batch program with a simple example. Suppose we wish to create a program that converts a temperature measured on a Fahrenheit thermometer into a Celsius temperature. Don’t worry, this question isn’t a test about your physicsThis book is not about memorizing facts, but we do expect you to know where to find them. Do you know where to find out how temperatures are converted? knowledge; here is the conversion formula:

Naturally in this formula f is the Fahrenheit temperature and c is the Celsius temperature. Translating this into BSL is straightforward:

(define (f2c f)
  (* 5/9 (- f 32)))

Recall that 5/9 is a number, a rational fraction to be precise, and more importantly, that c depends on the given f, which is what the function notation expresses.

Launching this batch program in the interactions area works as usual:
> (f2c 32)

0

> (f2c 212)

100

> (f2c -40)

-40

But suppose we wish to use this function as part of a program that reads the Fahrenheit temperature from a file, converts this number into a Celsius temperature, and then creates another file that contains the result.

From here, the rest is about composing functions. Here is the main function:
(define (convert in out)
  (write-file out
    (number->string
      (f2c
        (string->number
          (read-file in))))))
It consumes two filenames: in for the file where the Fahrenheit temperature is found and out for where we want the Celsius result. Then it composes five auxiliary functions to compute its result. Let us step through convert’s body carefully to understand how this works:
  1. (read-file in) retrieves the content of the file called in as a string;

  2. string->number turns this string into a number;

  3. f2c interprets the number as a Fahrenheit temperature and converts it into a Celsius temperature;

  4. number->string consumes this Celsius temperature and turns it into a string;

  5. and (write-file out ...) places this string into the file named out.

This long list of steps might look overwhelming. The average function composition in a pre-algebra course involves two functions, possibly three. Keep in mind, though, that programs accomplish a real-world purpose while exercises in algebra merely illustrate this idea.

Since we created the file named "sample.dat" above, we can now launch convert on this file: You can also create or edit the file "sample.dat" in a file editor. Just be careful that the editor doesn’t add a newline or any other invisible characters.
> (convert "sample.dat" 'stdout)

100

'stdout

> (convert "sample.dat" "out.dat")

"out.dat"

> (read-file "out.dat")

"100"

For the first interaction, we use 'stdout so that we can view what convert outputs in DrRacket’s interactions area. For the second one, convert is given the name "out.dat". As expected, the call to convert returns this string; from the description of write-file we also know that it deposited a Fahrenheit temperature in the file. Here we read the content of this file with read-file, but you could also use another text editor to look at the file and to view the result.

(define (convert in out)
  (write-file out
    (number->string
      (f2c
        (string->number (read-file in))))))
 
(define (f2c f)
  (* 5/9 (- f 32)))
 
(convert "sample.dat" "out.dat")

Figure 6: Converting Fahrenheit temperatures into Celsius

In addition to running the batch program, it is also instructive to step through the computation. Make sure that the file "sample.dat" exists and contains just a number, then click the STEP button in DrRacket. Doing so opens another window in which you can peruse the computational process that the call to the main function of a batch program triggers. You will see that the process follows the above outline.

Exercise 26: Recall the letter program from Composing Functions. We launched this program once, with the inputs "Matthew", "Krishnamurthi", and "Felleisen". Here is how to launch the program and have it write its output to the interactions area:
> (write-file 'stdout (letter "Matthew" "Krishnamurthi" "Felleisen"))

Dear Matthew,

 

We have discovered that all people with the last name

Krishnamurthi have won our lottery. So, Matthew,

hurry and pick up your prize.

 

Sincerely,

 

Felleisen

'stdout

Of course, programs are useful because you can launch them for many different inputs, and this is true for letter, too. Stop! Run letter on the following three inputs:
  • "Robby" "Flatt" "Felleisen"

  • "Christopher" "Columbus" "Felleisen"

  • "ZC" "Krishnamurthi" "Flatt"

From here it is a short step to a simple batch program that reads names from three files and writes a letter to one:
(define (main in-fst in-lst in-signature out)
  (write-file out
              (letter (read-file in-fst)
                      (read-file in-lst)
                      (read-file in-signature))))
The function consumes four strings: the first three are the names of input files and the last one serves as output file. It uses the first three to read one string each from the three named files, hands these strings to letter, and eventually writes the result of this function call into the file named by out, the fourth argument to main.

Create appropriate files, launch main, and check whether it delivers the expected letter.

Note: Once you understand Programming With Lists, you will be able to use other functions from "batch-io", and then you will have no problem writing letters for tens of thousands of lucky lottery winners.

Interactive Programs: No matter how you look at it, batch programs are old-fashioned. Even if businesses have used them for decades to automate useful tasks, people prefer interactive programs. Indeed, in this day and age, people mostly interact with programs via a keyboard and a mouse generating input events such as key presses or mouse clicks. Furthermore, interactive programs can also react to computer-generated events, for example, clock ticks or the arrival of a message from some other computer.

Designing an interactive program requires more work than designing a batch program. Specifically, an interactive program designates some function as the one that takes care of keyboard events, another function for dealing with clock tick, a third one for presenting the current state as an image, and so forth. It is the task of an interactive program’s main function to communicate these designations to the operating system, that is, the software platform on which the program is launched.

In BSL, the "universe" teachpack provides this communication mechanisms. For now, the key mechanism is the big-bang expression. It describes to DrRacket how the interactive program deals with clock ticks, key presses, mouse clicks, and so on. A big-bang expression consists of one required subexpression and one required clause. The subexpression evaluates to the initial state of the program, and the required clause tells DrRacket how to render the current state as a program.

Let us understand this idea step-by-step, starting with this function definition:
(define (number->square s)
  (square s "solid" "red"))
The function consumes a positive number and produces solid red square of that size. After clicking RUN, experiment with the function, like this:
> (number->square 5)

image

> (number->square 10)

image

> (number->square 20)

image

It behaves like a batch program, consuming a number and producing an image, which DrRacket renders for you.

Now try the following big-bang expression in the interactions area:
> (big-bang 100 [to-draw number->square])
A separate window appears, and it displays a 100 x 100 red square. In addition, the DrRacket interactions area does not display another prompt; it is as if the program keeps running and indeed, this is the case. To stop the program, click on DrRacket’s STOP button:
> (big-bang 100 [to-draw number->square])

100

When DrRacket stops the evaluation of a big-bang expression, it returns the current state, which in this case is just the initial state: 100.

Here is an interesting big-bang expression:
> (big-bang 100
            [on-tick sub1]
            [stop-when zero?]
            [to-draw number->square])
This big-bang expression contains two optional clauses: one that tells DrRacket how to deal with clock ticks and another one that explains when to stop the program. We read it as follows, starting with 100 as the initial state:
  1. every time the clock ticks, subtract 1 from the current state;

  2. then check whether zero? is true of the new state and if so, stop; and

  3. every time the state changes, use number->square to render it as an image.

Now hit the return key and observe what happens. Eventually the evaluation of the expressions terminates and DrRacket displays 0. Why?

In order to understand the evaluation of big-bang expressions in general, let us look at a schematic one:
(big-bang cw0
          (on-tick tock)
          (to-draw render)
          (stop-when end?))
The evaluation of this big-bang expression starts with cw0, which is usually an expression. DrRacket, our operating system, installs its value as the current state of the world, for short current world. It also uses render to translate the current world into an image, which is then displayed in a separate window. Every time the clock ticks, DrRacket applies tock to the current world and receives a value in response. It treats this value as the next current world.

The following table concisely summarizes this process:

clock tick

1

2

3

4

5

...

current world

cw0

cw1

cw2

cw3

cw4

...

(render cw)

im0

im1

im2

im3

im4

...

(tock cw)

cw1

cw2

cw3

cw4

cw5

...

In the first row, it specifies the current time: the first clock tick, the second, and so forth. The second row associates the current time with a current world. With render, this series of current worlds is mapped to a series of images, which is displayed in the separate window. The last row specifies the result of applying tock to the current world; we see that the result of this function call appears in the next column in row 1.

With this explanation, you may wish to re-examine Prologue: How to Program because it shows several interactive programs. Like a batch program, an interactive program comes with a main function; unlike a batch program, the main program of an interactive program almost always uses a big-bang expression to install event handling functions, which are defined separately.

In addition to to-draw, on-tick, and stop-when clauses, a world program may also specify on-key and on-mouse clauses. With on-key an interactive program can react to key strokes, and with on-mouse it can treat mouse clicks. For now, all you need to know that on-key needs a function that takes two inputs: the current world and a string, which represents the key stroke.

Here is a simple interactive program that makes use of key presses:
(define (main y)
  (big-bang y
            [on-tick sub1]
            [stop-when zero?]
            [to-draw place-dot-at]
            [on-key stop]))
 
(define (place-dot-at y)
  (place-image (circle 3 "solid" "red") 50 y (empty-scene 100 100)))
 
(define (stop y ke)
  0)
The program consists of three functions: a main function launches a big-bang interactive program; place-dot-at translates the current world into an image; and stop throws away its inputs and produces 0.

After clicking RUN, we can use each of this functions at DrRacket’s interaction prompt to confirm their workings. Let’s start with the last two clauses:
> (place-dot-at 89)

image

> (place-dot-at 22)

image

> (stop 89 "q")

0

Stop! Try to understand how main reacts when you press a key.

One way to find out is to launch main:
> (main 90)
Its on-tick clause tells us that main must consume a number because sub1 subtracts 1 from the current world for every clock tick. When a key is pressed on the keyboard, DrRacket invokes stop on the current world and a string that represents the key press. Since stop always produces 0 no matter what its inputs, the next state of the world is 0meaning the stop-when clause tells DrRacket to stop the program.

By now, you may feel that these first two chapters are somewhat overwhelming and that they introduced just too many new concepts. That is correct. To overcome this feeling, the next chapter takes a step back and explains how to design programs systematically from scratch, especially interactive programs. So take a breather and continue when ready.

3 How To Design Programs

The first few chapters of this book show that learning to program requires some mastery of many concepts. On the one hand, programming needs some language, a notation for communicating what we wish to compute. The languages for formulating programs are artificial constructions, though acquiring a programming language shares some elements with acquiring a natural language: we need to learn the vocabulary of the programming language, to figure out its grammar, and to understand what its "phrases" mean.

On the other hand, it is critical to learn how to get from a problem statement to a program. We need to determine what is relevant in the problem statement and what can be ignored. We need to tease out what the program consumes, what it produces, and how it relates inputs to outputs. We have to know, or find out, whether the chosen language and its libraries provide certain basic operations for the data that our program is to process. If not, we might have to develop auxiliary functions that implement these operations. Finally, once we have a program, we must check whether it actually performs the intended computation. And this might reveal all kinds of errors, which we need to be able to understand and fix.

In his book “The Mythical Man-Month” Fred Brooks describes and contrasts these forms of programming on the first pages. In addition to “garage programming” and a “programming product,” he also recognizes “component programming” and “systems programming.” This book is about the “programming products;” our next two books will cover “components” and “systems” design. All this sounds rather complex and you might wonder why we don’t just muddle our way through, experimenting here and there, leaving well enough alone when the results look decent. This approach to programming, often dubbed “garage programming,” is common and succeeds on many occasions; sometimes it is the launching pad for a start-up company. Nevertheless, the start-up cannot sell the results of the “garage effort” because only the original programmers and their friends can use them.

A good program comes with a short write-up that explains what it does, what inputs it expects, and what it produces. Ideally, it also comes with some assurance that it actually works. In the best circumstances, the program’s connection to the problem statement is evident so that a small change to the problem statement is easy to translate into a small change to the program. Software engineers call this a “programming product.”

The word “other” also includes older versions of the programmer who usually cannot recall all the thinking that the younger version put into the production of the program.

All this extra work is necessary because programmers don’t create programs for themselves. Programmers write programs for other programmers to read, and on occasion, people run these programs to get work done. Most programs are large, complex collections of collaborating functions, and nobody can write all these functions in a day. Programmers join projects, write code, leave projects, and others take over their work. Another difficulty is that the programmer’s clients tend to change their mind about what problem they really want solved. They usually have it almost right, but more often than not, they get some details wrong. Worse, complex logical constructions such as programs almost always suffer from human errors; in short, programmers make mistakes. Eventually someone discovers these errors and programmers must fix them. They need to re-read the programs from a month ago, a year ago, or twenty years ago and change them.

Exercise 27: Research the “year 2000” problem and what it meant for programmers.

In this book, we present a design recipe that integrates a step-by-step process with a way of organizing programs around problem data. For the readers who don’t like to stare at blank screens for a long time, this design recipe offers a way to make progress in a systematic manner. For those of you who teach others to design programs, the recipe is a device for diagnosing a novice’s difficulties. For others, our recipe might be something that they can apply to other areas, say medicine, journalism, or engineering. For those who wish to become real programmers, the design recipe also offers a way to understand and work on existing programs—though not all programmers use a method like this design recipe to come up with programs. The rest of this chapter is dedicated to the first baby steps into the world of the design recipe; the following chapters refine the recipe in one way or another.

3.1 Designing Functions

Information and Data: The purpose of a program is to describe a computation, a process that leads from one collection of information to another. In a sense, a program is like the instruction a mathematics teacher gives to grade school students. Unlike a student, however, a program works with more than numbers; it calculates with navigation information, looks up a person’s address, turns on switches, or processes the state of a video game. All this information comes from a part of the real world—often called the program’s domainand the results of a program’s computation represents more information in this domain.

“Information” plays a central role in our description. Think of information as facts about the program’s domain. For a program that deals with a furniture catalog, a “table with five legs” or a “square table of two by two meters” are pieces of information. A game program deals with a different kind of domain, where “five” might refer to the number of pixels per clock tick that some objects travels on its way from one part of the screen to another. Or, a payroll program is likely to deal with “five deductions.”

For a program to process information, it must turn it into some form of data, also called values, in the programming language; then it processes the data; and once it is finished, it turns the resulting data into information again. An interactive program may even intermingle these steps, acquiring more information from the world as needed and delivering information in between.

We use BSL and DrRacket so that you do not have to worry about the translation of information into data. In DrRacket’s BSL you can apply a function directly to data and observe what it produces. As a result, we avoid the serious chicken-and-egg problem of writing functions that convert information into data and vice versa. For simple kinds of information, designing such program pieces is trivial; for anything other than simple information, you need to know about parsing—the technology of analyzing text and separating it into its grammatical pieces—and that immediately requires a lot of expertise in program design.

Software engineers use the slogan model-view-control (MVC) for the way BSL and DrRacket separate data processing from parsing information into data and turning data into information. Indeed, it is now accepted wisdom that well-engineered software systems enforce this separation, even though most introductory books still co-mingle them. Thus, working with BSL and DrRacket allows you to focus on the design of the core of programs and, when you have enough experience with that, you can learn to design the information-data translation parts.

Here we use the "batch-io" and "universe" teachpacks to demonstrate how complete programs are designed. That is, this book—starting with this chapter—provides a design recipe for batch and interactive programs to give you an idea of how complete programs are designed. But, keep in mind that the libraries of full-fledged programming languages offer many more tools than these teachpacks.

Figure 7: From information to data, and back

Given the central role of information and data, program design must clearly start with the connection between them. Specifically, we—the programmers—must decide how to use our chosen programming language to represent the relevant pieces of information as data and how we should interpret data as information. Figure 7 displays this idea abstractly, but let us also make it concrete.

Suppose you are designing a program that consumes information in the form of numbers and that also produces numbers. Then choosing a representation and an interpretation requires understanding what a piece of data such as 42 means in the domain:
  • 42 may refer to the number of pixels from the top margin in the domain of images;

  • 42 may denote the number of pixels per clock tick that a simulation or game object moves;

  • 42 may mean a temperature, on the Fahrenheit, Celsius, or Kelvin scale for the domain of physics;

  • 42 may specify the size of some table if the domain of the program is a furniture catalog; or

  • 42 could just count the number of chars a batch program has read.

The key is to know how to go from numbers as information to numbers as data and vice versa.

The word “class” is a popular computer science substitute for the word “set.” In analogy to other set theory mathematics, we also say a value is some element of a class.

Since this knowledge is so important for everyone who reads the program, we often write it down in the form of comments, which we call data definitions. The purpose of a data definition is two-fold. On the one hand, it names a class or a collection of data, typically using a meaningful word. On the other hand, it informs readers how to create elements of this class of data and how to decide whether some random piece of data is an element of this collection.

Here is a data definition that could be useful for one of the examples from above:
; Temperature is a Number.
; interp. degrees Celsius
The first line introduces the name of the data collection, Temperature, and tells us that the class consists of all Numbers. So, for example, if we ask whether 102 is a temperature, you can respond with “yes” because 102 is a number and all numbers are temperatures. Similarly, if we ask whether "cold" is a Temperature, you will say “no” because no string belongs to Temperature. And, if we asked you to make up a sample Temperature, you might come up with something like -400.

If you happen to know that the lowest possible temperatures is approximately -274, you may wonder whether it is possible to express this knowledge in a data definition. Since data definitions in BSL are really just English descriptions of classes, you may indeed define the class of temperatures in a much more accurate manner than shown above. In this book, we use a stylized form of English for such data definitions, and the next chapter introduces the style for imposing constraints such as “larger than -274.”

At this point, you have encountered the names of some data: Number, String, Image, and Boolean values. With what you know right now, formulating a new data definition means nothing more than introducing a new name for an existing form of data, for example, “temperature” for numbers. Even this limited knowledge, though, suffices to explain the outline of our design process.

The Process: Once you understand how to represent input information as data and to interpret output data as information, the design of an individual function proceeds according to a straightforward process:
  1. Express how you wish to represent information as data. A one-line comment suffices, for example,

    ; We use plain numbers to represent temperatures.

    Formulate data definitions, like the one for Temperature above for the classes of data you consider critical for the success of your program.

  2. Write down a signature, a purpose statement, and a function header.

    A function signature (but always shortened to signature here) is a BSL comment that tells the readers of your design how many inputs your function consumes, from what collection of data they are drawn, and what kind of output data it produces. Here are three examples:

    • for a function that consumes one string and produces a number:

    • for a function that consumes a temperature and that produces a string:
      As this signature points out, introducing a data definition as an alias for an existing form of data makes it easy to read the intention behind signatures.

      Nevertheless, we recommend to stay away from aliasing data definitions for now. A proliferation of such names can cause quite some confusion. It takes practice to balance the need for new names and the readability of programs, and there are more important ideas to understand for now.

    • for a function that consumes a number, a string, and an image and that produces an image:

    A purpose statement is a BSL comment that summarizes the purpose of the function in a single line. If you are ever in doubt about a purpose statement, write down the shortest possible answer to the question

    what does the function compute?

    Every reader of your program should understand what your functions compute without having to read the function itself.

    A multi-function program should also come with a purpose statement. Indeed, good programmers write two purpose statements: one for the reader who may have to modify the code and another one for the person who wishes to use the program but not read it.

    Finally, a header is a simplistic function definition, also called a stub. Pick one parameter for each input data class in the signature; the body of the function can be any piece of data from the output class. The following three function headers match the above three signatures:
    Our parameter names reflect what kind of data the parameter represents. Sometimes, you may wish to use names that suggest the purpose of the parameter.

    When you formulate a purpose statement, it is often useful to employ the parameter names to clarify what is computed. For example,
    ; Number String Image -> Image
    ; add s to img, y pixels from top, 10 pixels to the left
    (define (add-image y s img)
      (empty-scene 100 100))

    At this point, you can click the RUN button and experiment with the function. Of course, the result is always the same value, which makes these experiments quite boring.

  3. Illustrate the signature and the purpose statement with some functional examples. To construct a functional example, pick one piece of data from each input class from the signature and determine what you expect back.

    Suppose you are designing a function that computes the area of a square. Clearly this function consumes the length of the square’s side, and that is best represented with a (positive) number. The first process step should have produced something like this:

    ; Number -> Number
    ; compute the area of a square whose side is len
    (define (area-of-square len) 0)

    Add the examples between the purpose statement and the function header:

    ; Number -> Number
    ; compute the area of a square whose side is len
    ; given: 2, expect: 4
    ; given: 7, expect: 49
    (define (area-of-square len) 0)
  4. The next step is to take inventory,The term “inventory” in this context is due to Dr. Stephen Bloch who uses it in his writings on program design. We use “template” for historical reasons. to understand what are the givens and what we do need to compute. For the simple functions we are considering right now, we know that they are given data via parameters. While parameters are placeholders for values that we don’t know yet, we do know that it is from this unknown data that the function must compute its result. To remind ourselves of this fact, we replace the function’s body with a template.

    For now, the template contains just the parameters, e.g.,
    ; Number -> Number
    ; compute the area of a square whose side is len
    ; given: 2, expect: 4
    ; given: 7, expect: 49
    (define (area-of-square len)
      (... len ...))
    The dots remind you that this isn’t a complete function, but a template, a suggestion for an organization.

    The templates of this section look boring. Later, when we introduce complex forms of data, templates become interesting, too.

  5. It is now time to code. In general, to code means to program, though often in the narrowest possible way, namely, to write executable expressions and function definitions.

    To us, coding means to replace the body of the function with an expression that attempts to compute from the pieces in the template what the purpose statement promises. Here is the complete definition for area-of-square:
    ; Number -> Number
    ; compute the area of a square whose side is len
    ; given: 2, expect: 4
    ; given: 7, expect: 49
    (define (area-of-square len)
      (sqr len))

    To complete the add-image function takes a bit more work than that:
    ; Number String Image -> Image
    ; add s to img, y pixels from top, 10 pixels to the left
    ; given:
    ; 5 for y,
    ; "hello" for s, and
    ; (empty-scene 100 100) for img
    ; expected:
    ; (place-image (text "hello" 10 "red") 10 5 (empty-scene 100 100))
    (define (add-image y s img)
      (place-image (text s 10 "red") 10 y img))
    In particular, the function needs to turn the given string s into an image, which is then placed into the given scene.

  6. The last step of a proper design is to test the function on the examples that you worked out before. For now, click the RUN button and enter function applications that match the examples in the interactions area:
    > (area-of-square 2)

    4

    > (area-of-square 7)

    49

    The results must match the output that you expect; you must inspect each result and make sure it is equal to what is written down in the example portion of the design. If the result doesn’t match the expected output, consider the following three possibilities:
    1. You miscalculated and determined the wrong expected output for some of the examples.

    2. Alternatively, the function definition computes the wrong result. When this is the case, you have a logical error in your program, also known as a bug.

    3. Both the examples and the function definition are wrong.

    When you do encounter a mismatch between expected results and actual values, we recommend that you first re-assure yourself that the expected results are correct. If so, assume that the mistake is in the function definition. Otherwise, fix the example and then run the tests again. If you are still encountering problems, you may have encountered the third, rather rare situation.

3.2 Finger Exercises

The first few of the following exercises are almost copies of previous ones. The difference is that this time they used the word “design” not “define,” meaning you should use the design recipe to create these functions and your solutions should include the relevant pieces. (Skip the template; it is useless here.) Finally, as the title of the section suggests these exercises are practice exercises to help you internalize the process. Until the steps become second nature, never skip one; that leads to easily avoided errors and unproductive searches for the causes of those flaws. There is plenty of room left in programming for complicated errors. We have no need to waste our time on silly mistakes.

Exercise 28: Design the function string-first, which extracts the first character from a non-empty string. Don’t worry about empty strings.

Exercise 29: Design the function string-last, which extracts the last character from a non-empty string.

Exercise 30: Design the function image-area, which computes the area of a given image. Note: The area is also the number of pixels in the picture.

Exercise 31: Design the function string-rest, which produces a string like the given one with the first character removed.

Exercise 32: Design the function string-remove-last, which produces a string like the given one with the last character removed.

3.3 Domain Knowledge

It is natural to wonder what knowledge it takes to code up the body of a function. A little bit of reflection tells you that this step demands an appropriate grasp of the domain of the program. Indeed, there are two forms of such domain knowledge:
  1. Knowledge from external domains such as mathematics, music, biology, civil engineering, art, etc. Because programmers cannot know all of the application domains of computing, they must be prepared to understand the language of a variety of application areas so that they can discuss problems with domain experts. This language is often that of mathematics, but in some cases, the programmers must learn a language as they work through problems with domain experts.

  2. And knowledge about the library functions in the chosen programming language. When your task is to translate a mathematical formula involving the tangent function, you need to know or guess that your chosen language comes with a function such as BSL’s tan. When, however, you need to use BSL to design image-producing functions, you will benefit from understanding the possibilities of the "2htdp/image" teachpacks.

Since you can never anticipate in which area you work or with which programming language you must work, it is imperative that you have a solid understanding of the full possibilities of whatever computer languages are around, good, and popular. Otherwise some domain expert with half-baked programming knowledge will take over your job.

You can recognize problems that demand domain knowledge from the data definitions that you work out. As long as the data definitions use classes that exist in the chosen programming language, the definition of the function body (and program) mostly relies on expertise in the domain. Later, when we introduce complex forms of data, the design of functions demands deep knowledge of computer science.

3.4 From Functions to Programs

Not all programs consist of a single function definition. Some require several functions, many also use constant definitions. No matter what, it is always important to design each function of a program systematically, though both global constants and the presence of auxiliary functions change the design process a bit.

When you have defined global constants, your functions may use those global constants to compute results. You may wish to add these constants when you create templates as if they were parameters; after all, they belong to the inventory of things that may contribute to the function definition. Adding global constants to templates, however, can quickly make those templates look messy. In short, keep global constants in mind when you define functions.

Multi-function programs are complex. On the one hand, the design of interactive functions automatically demands the design of several functions. On the other hand, even the design of batch programs may require dealing with several different tasks. Sometimes the problem statement itself suggests different tasks; other times you will discover the need for auxiliary functions as you are in the middle of designing some function.

For all cases, we recommend keeping around a list of needed functions or a wish list.The term “wish list” in this context is due to Dr. John Stone. Each entry on a wish list should consist of three things: a meaningful name for the function, a signature, and a purpose statement. For the design of a batch program, put the main function on the wish list and start designing it. For the design of an interactive program, you can put the event handlers, the stop-when function, and the scene-rendering function on the list. As long as the list isn’t empty, pick a wish and design the function. If you discover during the design that you need another function, put it on the list. When the list is empty, you are done.

3.5 On Testing

Testing quickly becomes a labor-intensive chore. While it is easy to run tests and discover syntactic errors (clicking the RUN button does this) and run-time errors (the application of a primitive operation to the wrong kind of data), comparing the result of an interaction with the expected result is tiresome. For complex programs, you will tend to write lots of examples and tests and you will have to compare complex (large) values. If you haven’t thought so, you will soon think that this is burdensome and perform sloppy comparisons.

At the same time, testing is a major step to discover the flaws in a program. Sloppy testing quickly leads to functions with hidden problems—the bugs. Buggy functions retard progress on large systems, often in multiple ways.

For these reasons—and others to be discussed—it is critical to mechanize tests and not perform them manually in the interactions area. BSL in the DrRacket environment includes a testing facility, as do many programming systems.

To introduce this testing facility, we take a second look at the function that converts temperatures in Fahrenheit to Celsius temperatures from Programs. Here is the definition, reformulated according to the design recipe:
; Number -> Number
; convert Fahrenheit temperatures to Celsius temperatures
; given 32, expected 0
; given 212, expected 100
; given -40, expected -40
(define (f2c f)
  (* 5/9 (- f 32)))
Testing the function’s examples requires three computations and three comparisons between two numbers each. You can formulate these tests and add them to the definitions area in DrRacket:
(check-expect (f2c -40) -40)
(check-expect (f2c 32) 0)
(check-expect (f2c 212) 100)
When you now click the RUN button, you see a report from BSL that the program passed all three tests—and you had nothing else to do.

In addition to getting tests to run automatically, the check-expect forms show another advantage when tests fail. To see how this works, change one of the above tests so that the result is wrong, for example

(check-expect (f2c -40) 40)

When you now click the RUN button, an additional window pops up. The window’s text explains that one of three tests failed. For the failed test, the window displays three pieces: the computed value, the result of the function call (-40); the expected value (40); and a hyperlink to the text of the failed test case.

; Number -> Number
; convert Fahrenheit temperatures to Celsius temperatures
 
(check-expect (f2c -40) -40)
(check-expect (f2c 32) 0)
(check-expect (f2c 212) 100)
 
(define (f2c f)
  (* 5/9 (- f 32)))

Figure 8: Testing in BSL

You can place check-expect specifications above or below the function definition that they test. When you click RUN, DrRacket collects all check-expect specifications and evaluates them after all function definitions have been added to the “vocabulary” of operations. The above figure shows how to exploit this freedom to combine the example and test step. Instead of writing down the examples as comments, you can translate them directly into tests. When you’re all done with the design of the function, clicking RUN performs the test. And if you ever change the function for some reason—to improve its look or to add a piece of functionality—the next click re-tests the function.

Last but not least, check-expect also works for images. That is, you can test image-producing functions. Say you wish to design the function render, which places the image of a car, dubbed CAR, into a background scene, named BACKGROUND. For the design of this function, you may formulate the following tests:
(check-expect (render 50) )
(check-expect (render 200) )
One approach is to use an image editor to create images of the expected results. Alternatively, you could formulate expressions—possibly in the interactions area of DrRacket—that compute the expected results:
(check-expect (render 50) (place-image CAR 50 Y-CAR BACKGROUND))
(check-expect (render 200) (place-image CAR 200 Y-CAR BACKGROUND))
Doing so also helps you figure out how to express the function. Both styles are useful, though in this book we prefer the second one.

Because it is so useful to have DrRacket conduct the tests and not to check everything yourself manually, we immediately switch to this style of testing for the rest of the book. This form of testing is dubbed unit testing, and BSL’s unit testing framework is especially tuned for novice programmers. One day you will switch to some other programming language; one of your first tasks will be to figure out its unit testing framework.

3.6 Designing World Programs

The "universe" teachpack supports the construction of some interactive programs. Specifically, you can use the "universe" teachpack to construct so-called world programs; interactive programs that deal with clock ticks, mouse clicks, and key strokes. In order to interact with people, world programs also create images that DrRacket displays in a graphical canvas.

While the previous chapter introduces the "universe" teachpack in an ad hoc way, this section demonstrates how the design recipe helps you create world programs. The first section provides some basic knowledge about big-bang, the major construct for wiring up world programs. The second section extends this knowledge to deal with mouse clicks and key strokes. Once you have digested this terminology, you are ready to design world programs. The last section starts a series of exercises, which run through a couple of chapters in this book; take a close look and create your own favorite virtual pet.

Describing Worlds: A raw computer is a nearly useless piece of physical equipment; it is called hardware because you can touch it. This equipment becomes useful once you install software, and the first piece of software is usually installed on a computer is an operating system. It has the task of managing the computer for you, including connected devices such as the monitor, the keyboard, the mouse, the speakers, and so on. When you press a key on the keyboard, the operating system runs a function that processes the key stroke. We say that the key stroke is an key event, and the function is an event handler. Similarly, when the clock ticks, the operating system runs an event handler for clock ticks and, when you perform some action with the mouse, the operating system launches the event handler for mouse clicks.

Naturally, different programs have different needs. One program may interpret key strokes as signals to control a nuclear reactor; another passes them to a word processor. To make one and the same computer work on these radically different tasks, programs install event handlers. That is, they tell the operating system to use certain functions for dealing with clock ticks and other functions for dealing with mouse clicks. If a program doesn’t need to handle key strokes, the program says nothing about key events and the operating system ignores them.

DrRacket is a small operating system and the big-bang form from the "universe" teachpack is your means to install event handlers. Specifically, the various clauses in a big-bang expression inform DrRacket which function should deal with which kind of event. Take this expression, for example:
(big-bang w0
          (on-tick cth)
          (on-key keh)
          (on-mouse meh)
          ...)
The three clauses inform DrRacket that
  • the function cth mentioned in the on-tick clause is for handling clock ticks;

  • keh, specified in the on-key clause, deals with events triggered by key strokes; and

  • meh is for processing mouse events.

Once the big-bang expression is evaluated, DrRacket acts just like a real operating system, watching the clock, the key board, and the mouse for events and dispatching to your designated BSL functions.

The key questions are what arguments DrRacket supplies to your event handlers for key strokes, mouse clicks, and clock ticks and what kind of results it expects from these event handlers. Like a real operating system, DrRacket gives these functions access to the current state of the world. For key events and mouse events, DrRacket also supplies information about these events.

The initial state is the value of w0, because our big-bang expression says so. It also is the state that DrRacket hands to the first event handling function that it uses. DrRacket expects that this event handling function produces a new state of the world, let us call it w1. The point is that DrRacket keeps this result around until the second event happens. Here is a table that describes this relationship among worlds and event handling functions:

event:

big-bang

e1

e2

e3

e4

current world:

w0

w1

w2

w3

w4

clock tick:

--

(cth w0)

(cth w1)

(cth w2)

(cth w3)

key stroke:

--

(keh w0 ...)

(keh w1 ...)

(keh w2 ...)

(keh w3 ...)

mouse click:

--

(meh w0 ...)

(meh w1 ...)

(meh w2 ...)

(meh w3 ...)

The first row enumerates all events that have taken place since DrRacket started evaluating the big-bang expression, starting with the “big bang” event itself.Although you might imagine that two events happen at the same time, DrRacket arranges all events in a linear sequence. The second row is the current world. As the table says, big-bang installs the given world, dubbed w0, as the first world. The remaining worlds in this row are the results of processing the current event and the previous world. For example, w1 is the world that results from processing event e1 in world w0, which could be (cth w0), (keh w0 ...), or (meh w0 ...). While the remaining cells indicate how to compute all possible future worlds from a world and an event, only one of these results is chosen as the next world, because only one event happens at a time.

Let us make this concrete. Suppose e1 is the key stroke "a", e2 and e3 are clock ticks, and e4 is a “button down” mouse event taking place at the position (90,100). Then
  1. w1 is the result of (keh w0 "a"), i.e., the fourth cell in the e1 column;

  2. w2 is the result of (cth w1), i.e., the third cell in the e2 column;

  3. w3 is the result of (cth w2), i.e., again the third cell in the e3 column; and

  4. w4 is the result of (meh w3 90 100 "button-down").

We can actually express these four steps as a sequence of four definitions:
(define w1 (keh w0 "a"))
(define w2 (cth w1))
(define w3 (cth w2))
(define w4 (meh w3 "button-down" 90 100))
and even as a function composition:Composing event handlers is truly important when it comes to testing world programs and removing the occasional error.

(define w4 (meh (cth (cth (keh w0 "a"))) 90 100 "button-down"))

Event handlers really are functions that consume worlds and events, and they produce worlds.

In short, the sequence of events determines in which order you traverse the above tables of possible worlds to arrive at the one and only one current world for each time slot. Note that DrRacket does not touch the current world; it merely safeguards it and passes it to event handling functions when needed.

Designing Worlds: Now that you understand how big-bang works, you can focus on the truly important problem of designing world programs. As you might guess, the design starts with the data definition for the states of the world. To this end we assume that you have a problem statement and that you are able to imagine what the world program may display in various situations.

Let us make this concrete with a sample problem:

Sample Problem: Design a program that moves a car across the world canvas, from left to right, at the rate of three pixels per clock tick.

For this problem statement, it is easy to imagine a series of images that the program would display:

Pay special attention as to what changes from image to image.

Assuming you have read the problem statement carefully and understand how the world changes and what stays the same, the design of the world program proceeds in three big steps:
  1. For all those properties of the world that remain the same, introduce constant definitions. In BSL, we capture constants via global constant definitions. For the purpose of designing worlds, we distinguish between two kinds of constants:
    1. “physical” constants, which describe general attributes of objects in the domain, such as the speed or velocity of an object, its color, its height, its width, its radius, and so forth. Of course these constants don’t really refer to physical facts, but many are analogous to physical aspects of the real world.

      In the context of the car animation from the previous section, the WHEEL-RADIUS constant is a “physical” constant, and its definition may look like this:
      (define WHEEL-RADIUS 5)
      (define WHEEL-DISTANCE (* WHEEL-RADIUS 5))
      (define BODY-LENGTH (+ WHEEL-DISTANCE (* 6 WHEEL-RADIUS)))
      (define BODY-HEIGHT (* WHEEL-RADIUS 2))
      Note how some constants are computed from others.

    2. graphical constants, which are images that the program uses to create the scenes that appear on the canvas.

      Here are some graphical constant definitions:
      (define WHL (circle WHEEL-RADIUS "solid" "black"))
      (define BDY
        (above
          (rectangle (/ BODY-LENGTH 2) (/ BODY-HEIGHT 2)
                     "solid" "red")
          (rectangle BODY-LENGTH BODY-HEIGHT "solid" "red")))
      (define SPC (rectangle WHEEL-DISTANCE 1 "solid" "white"))
      (define WH* (beside WHL SPC WHL))
      (define CAR (underlay/xy BDY WHEEL-RADIUS BODY-HEIGHT WH*))
      Graphical constants are usually computed, and the computations tend to involve the physical constants. To create good looking images, you need to experiment. But, keep in mind that good images are not important to understand this book; if you have fun creating them, feel free to spend time on the task. We are happy with simple images.

  2. Those properties that change over time or in reaction to other events make up the current state of the world. Your task is to render the possible states of the world, to develop a data definition that describes all possible states of the world. As before, you must equip this data definition with a comment that tells readers how to represent world information as data and how to interpret data as information in the world.

    For the running example of an animated car, the only thing that changes is its distance to the left (or right) border of the canvas. A distance is measured in numbers, so the following is an adequate data definition in this example.

    ; CarState is a Number
    ; the number of pixels between the left border and the car
    An alternative is to count the number of clock ticks that have passed and to use this number as the state of the world. We leave this design variant as an exercise.

  3. Once you have a data representation for the state of the world, you need to decide which kind of interactions you wish to use for which kind of transitions from one world state to another. Depending on your decisions, you need to design several or all of the following four functions:

    1. if your world reacts to clock ticks:

    2. if your world reacts to key strokes:

    3. if your world reacts to mouse clicks:

    4. if you want the world to be rendered to the canvas:

  4. Last but not least, you need to define a main function that puts it all together. Unlike all other functions, a main function for world programs doesn’t demand design. As a matter of fact, it doesn’t require testing. Its sole reason for existing is that you can run your world program conveniently once all tests for the event handling functions are completed.

    Here is our proposed main function for the sample problem:
    ; main : CarState -> CarState
    ; launch the program from some initial state
    (define (main ws)
       (big-bang ws (on-tick tock) (to-draw render)))
    The definition assumes that you have named the clock tick handler tock and the draw function render.

    In other words, the desire to design an interactive program dictates several initial entries for your wish list. Later we introduce additional entries so that you can also design world programs that deal with key presses and mouse events. After you have designed the event handling functions, you can launch an interactive program with a big-bang expression:

    (main SomeCarState)

When the evaluation of the big-bang expression is terminated (for now, stop it by closing the world canvas), it returns the current state of the world.

A note on names: Naturally, you don’t have to use the name “CarState” for the class of data that represents the states of the world; any name will do as long as you use it consistently for the signatures of the big-bang functions. Also, you don’t have to use the names tock, render, or end?; you can name these functions whatever you like, as long as you use the same names when you write down the clauses of the big-bang expression.

A note on design: Even after settling on the data definition, a careful programmer shouldn’t be completely happy. The image of the car (and a car itself) isn’t just a mathematical point without width and height. Thus, “the number of pixels from the left margin” is ambiguous. Does this statement measure the distance between the left margin and the left end of the car? Its center point? Or even its right end? While this kind of reflection may seem far-fetched, it becomes highly relevant and on occasion life-critical in some programming domains. We ignore this issue for now and leave it to BSL’s image primitives to make the decision for us.

Good programs establish a single point of control for all aspects, not just the graphical constants. Several chapters deal with this issue.

Exercise 33: Good programmers ensure that an image such as CAR can be enlarged or reduced via a single change to a constant definition. We started the development of our car image with a single plain definition:

(define WHEEL-RADIUS 5)

All other dimensions of the car and its pieces are based on the wheel’s radius. Changing WHEEL-RADIUS from 5 to 10 doubles the size of the car image, and setting it to 3 reduces it. This kind of program organization is dubbed single point of control, and good design employs single point of control as much as possible.

Now develop your favorite image of a car; name your image CAR. Remember to experiment and make sure you can re-size the image easily.

The rest of this section demonstrates how to apply the third design step—template creation—to our sample problem. Since the car is supposed to move continuously across the canvas, and since the problem statement doesn’t mention any other action, we have two functions on our wish list: tock for dealing with a clock tick and render for creating an image from the state of the world.

As mentioned in the design recipe, the signature for the clock-tick handler is dictated by what we named the class of data that represents the state of the world. It does need a purpose statement and a header:
; CarState -> CarState
; the clock ticked; move the car by three pixels
(define (tock ws) ws)
Since the state of the world represents the distance between the left margin of the canvas and the car, and since the car moves at three pixels per clock tick, a concise purpose statement combines these two facts into one. This makes it also easy to create examples and to define the function:
; CarState -> CarState
; the clock ticked; move the car by three pixels
; example:
; given: 20, expected 23
; given: 78, expected 81
(define (tock ws) (+ ws 3))

The last design step calls for tests to confirm that the examples work as expected. So we click the RUN button and test:
> (tock 20)

23

> (tock 78)

81

Since the results are as expected, the design of tock is finished.

Our second entry on the wish list specifies a function that translates the state of the world into a scene:
; CarState -> Image
; place the car into a scene, according to the given world state
(define (render ws) (empty-scene 300 50))
As for tock, the signature is dictated by the choice of CarState in our data definition. The purpose statement tells us what the function produces from the given world state, namely, a scene with a car. The function header is obviously not computing what the purpose statement demands, but this is acceptable for a header.

To make examples for a rendering function, we suggest arranging a table like this:

ws =

50

ws =

100

ws =

150

ws =

200

It lists the given world states in the first column, and in the second column you see the desired scenes. For your first few rendering functions, you may just wish to draw these images by hand.

Even though this kind of image table is intuitive and explains what the running function is going to display—a moving car—it does not explain how the function creates this result. To get from here to there, we recommend writing down expressions that create the images in a third column of the table:

ws =

50

(place-image
  CAR
  50 Y-CAR
  BACKGROUND)

ws =

100

(place-image
  CAR
  100 Y-CAR
  BACKGROUND)

ws =

150

(place-image
  CAR
  150 Y-CAR
  BACKGROUND)

ws =

200

(place-image
  CAR
  200 Y-CAR
  BACKGROUND)
The capitalized names refer to the obvious constants: the image of a car, its fixed y coordinate, and the background scene, which is currently empty.

This extended table suggests a pattern for the formula that goes into the body of the render function:
; CarState -> Image
; place the car into a scene, according to the given world state
(define (render ws)
  (place-image CAR ws Y-CAR BACKGROUND))
All you have to do from here is create the car image, a background image (start with an empty scene), and determine at which y coordinate you want to place it.

And then, you just need to test, which means evaluating expressions such as (render 50), (render 100), (render 150), and (render 200) and making sure that the resulting image is what you want. Naturally, this is somewhat more difficult than checking that a number is what you want. For now, we and you need to rely on your eyes, which is why doing this is called an eyeball test. In the penultimate subsection, we return to the testing issue in general and the one for images in particular.

Exercise 34: Finish the sample problem and get the program to run. That is, assuming that you have solved the exercise of creating the image of a car, define the constants BACKGROUND and Y-CAR. Then assemble the appropriate big-bang expression. When your program runs to your satisfaction, add a tree to scenery. We used
(define tree
  (underlay/xy (circle 10 'solid 'green)
               9 15
               (rectangle 2 20 'solid 'brown)))
to create a tree-like shape. Also add a clause to the big-bang expression that stops the animation when the car has disappeared on the right side of the canvas.

Exercise 35: Start with the following data definition:
; AnimationState is a Number
; interp. the number of clock ticks since the animation started
Like the original data definition, this one also equates the states of the world with the class of numbers. Its interpretation, however, explains that the number means something entirely different.

Design functions tock and render and develop a big-bang expression so that you get once again an animation of a car traveling from left to right across the world’s canvas.

How do you think this program relates to the animate function from Prologue: How to Program?

Exercise 36: Use the data definition from the preceding exercise to design a program that moves a red dot according to a sine wave.

Of Mice and Characters: Before you design world programs that deal with key strokes and mouse events, it is a good idea to practice with small, nearly trivial examples to understand what the event handlers can and cannot compute. We start with a simple problem concerning key strokes:

Sample Problem: Design a program that keeps track of all key strokes. The program should display the accumulated key strokes as a red text in the 11 point font.

The problem looks easy and suggests a relative straightforward data definition for representing the state of the world:
; AllKeys is a String.
; interp. the sequence of keys pressed since
; big-bang created the canvas
Just to emphasize that names don’t matter when used consistently, we don’t use WorldState this time. The second sentence of the problem statement also spells out some physical and graphical constants:
; physical constants:
(define WIDTH 100)
(define HEIGHT 50)
 
; graphical constant:
(define MT (empty-scene WIDTH HEIGHT))

From the data definition and the desire to deal with key events, we get a wish list with two functions:
  • the function remember to manage key strokes:
    ; AllKeys String -> AllKeys
    ; add ke to ak, the state of the world
    (define (remember ak ke)
      ak)

  • the function show to display the current state of the world:
    ; AllKeys -> Image
    ; render the string as a text and place it into MT
    (define (show ak)
      MT)

Here we spelled out a complete signature, purpose, and header for each function.

Next we need to pick one of the two wishes and finish it. We choose remember and make up examples:
; AllKeys String -> AllKeys
; add ke to ak, the state of the world
 
(check-expect (remember "hello" " ") "hello ")
(check-expect (remember "hello " "w") "hello w")
 
(define (remember ak ke) ak)
One interpretation for the examples is that some user has entered the word "hello" so far and next presses the space bar, which is represented as the string " "; furthermore, if the state of the world is ever "hello " and the user presses the “w” key, the next state of the world should be "hello w". Both example-tests imply that the state of the world represents the key strokes seen so far as a string, when read from left to right; the next key stroke is added at the end. Thus, the examples suggest the obvious function definition:
; AllKeys String -> AllKeys
; add ke to ak, the state of the world
 
(check-expect (remember "hello" " ") "hello ")
(check-expect (remember "hello " "w") "hello w")
 
(define (remember ak ke)
  (string-append ak ke))
Copy the code to DrRacket, click on RUN, and thus confirm that the example-tests work fine.

The design of the remaining function, show, is equally straightforward, mostly because the problem almost dictates the examples:
; AllKeys -> Image
; render the string as a text and place it into MT
 
(check-expect
 (show "hello") (place-image (text "hello" 11 "red") 10 20 MT))
(check-expect
 (show "mark") (place-image (text "mark" 11 "red") 10 20 MT))
 
(define (show ak)
  (place-image (text ak 11 "red") 10 20 MT))
The key to the example is that by writing down expressions that construct the desired output, we can easily guess what the function should look like. Notice, though, the arbitrarily chosen positions of the text; nothing in the problem statement dictates this. We simply pick something and stick to it. If this were a for-money project, you would ask your client of course if the program works as imagined before you move on to add other features.

As we have said before, tests exist to reveal flaws; they can’t show that the program truly works. For that, you must run the program and experiment, and eventually you must allow the expected user to play with intermediate designs. So here is how you launch this interactive program:
(big-bang ""
          (on-key remember)
          (to-draw show))
This opens the world canvas, like this:

enabling you to press keys and see them drawn there:

Even though it isn’t immediately obvious, we choose to press the “h” key. The first image shows the empty canvas, the second one reflects the key event.

Exercise 37: Key event handlers are also applied to strings such as "\t" (the tab key) and "\r". Appearances are deceiving, however. These strings consists of a single character and remember therefore adds them to the end of the current world state. Read the documentation of on-key to find out which strings belong to KeyEvent; then change remember so that it also ignores all one-character strings that represent key strokes.

Let us look at a program that interacts with the mouse. The figure below displays the simplest such program; it is an interactive program that just records where the mouse events occur via small dots.It is acceptable to break the rule of separating data representations and image rendering for such experimental programs, whose purpose it is to determine how newly introduced things work. It ignores what kind of mouse event occurs, and it also ignores the first guideline about the separation of state representation and its image. Instead the program uses images as the state of the world. Specifically, the state of the world is an image that contains red dots where a mouse event occurred. When another event is signaled, the clack function just paints another dot into the current state of the world.

; AllMouseEvts is an element of Image.
 
; graphical constants
(define MT (empty-scene 100 100))
 
; clack : AllMouseEvts Number Number String -> AllMouseEvts
; add a dot at (x,y) to ws
 
(check-expect
 (clack MT 10 20 "something mousy")
 (place-image (circle 1 "solid" "red") 10 20 MT))
 
(check-expect
 (clack (place-image (circle 1 "solid" "red") 1 2 MT) 3 3 "")
 (place-image (circle 1 "solid" "red") 3 3
              (place-image (circle 1 "solid" "red") 1 2 MT)))
 
(define (clack ws x y action)
  (place-image (circle 1 "solid" "red") x y ws))
 
; show : AllMouseEvts -> AllMouseEvts
; just reveal the current world state
 
(check-expect (show MT) MT)
 
(define (show ws)
  ws)

Figure 9: A mouse event recorder

To play with this program, copy it into the definitions area of DrRacket and click RUN. In the interactions area, evaluate

(big-bang (empty-scene 100 100) (to-draw show) (on-mouse clack))

This opens the world’s canvas and allows you to move the mouse and to record its movement. Doing so produces a canvas that looks roughly like this:

It reveals that a computer and its operating system do not track every single move of your mouse. Instead they sample mouse events sufficiently often and tell your program about those sample events. Usually these samples are sufficient.

Normal interactive programs don’t ignore the kind of mouse events that takes place. Just like the key event tracker above, they inspect the string and compute different results, depending on what kind of string they received. Designing such programs requires a bit more knowledge about BSL and a bit more insight into design than we have presented so far. And the next chapter introduces all this.

3.7 Virtual Pet Worlds

This exercise section introduces the first two elements of a virtual pet game. It starts with just a display of a cat that keeps walking across the screen. Of course, all the walking makes the cat unhappy and its unhappiness shows. Like all pets, you can try petting, which helps some, or you can try feeding, which helps a lot more.

So let’s start with an image of our favorite cat:

Copy the cat image and paste it into DrRacket, then give the image a name with define.

Exercise 38: Design a “virtual cat” world program that continuously moves the cat from left to right, by three pixels at a time. Whenever the cat disappears on the right it should re-appear on the left.

Exercise 39: Improve the cat animation with a second, slightly different image:

Adjust the rendering function so that it uses one cat image or the other based on whether x coordinate is odd. Read up on odd? in the help desk.

Exercise 40: Our virtual pet game will need a gauge to show how happy the cat is. If you ignore the cat, it becomes less happy. If you pet the cat, it becomes happier. If you feed the cat, it becomes much, much happier. We feed the cat by pressing the down arrow key, and we pet it by pressing the up arrow key.

Do not integrate your solution to this exercise with the program of the preceding two exercises; you don’t know enough yet.

Design a world program that maintains and displays a “happiness gauge” over time. With each clock tick, happiness decreases by -0.1, starting with 100, the maximum score; it never falls below 0, the minimum happiness score.

To show the level of happiness, we use a scene with a solid, red rectangle with a black frame. For a happiness level of 0, the red bar should be gone; for a happiness level of 100, the bar should go all the way across the scene.

4 Intervals, Enumerations, Itemizations

Thus far you have four choices to represent information as data: numbers, strings, images, and Boolean values. For many problems this is enough, but there are many more for which these four collections of data in BSL (or different ones in different programming languages) don’t suffice. Put differently, programming with just the built-in collections of data is often clumsy and therefore error prone.

At a minimum, good programmers must learn to design programs with restrictions on these built-in collections. One way to restrict is to enumerate a bunch of elements from a collection and to say that these are the only ones that are going to be used for some problem. Enumerating elements works only when there is a finite number of them. To accommodate collections with “infinitely” many elements, we introduce intervals, which are collections of elements that satisfy a specific property.

Infinite may just mean “so large that enumerating the elements is entirely impractical.”

Defining enumerations and intervals means distinguishing among different kinds of elements. To distinguish in code requires conditional functions, that is, functions that choose different ways of computing results depending on the value of some argument. Both Many Ways To Compute and Mixing It Up With Booleans illustrate with examples how to write such functions. In both, however, there is no design system; all you have is some new construct in your favorite programming language (that’s BSL) and some examples on how to use it.

In this chapter, we introduce enumerations and intervals and discuss a general design strategy for these forms of input data. We start with a second look at the cond expression. Then we go through three different scenarios of distinct subclasses of data: enumerations, intervals, and itemizations, which mix the first two. The chapter ends with a section on the general design strategy for such situations.

4.1 Conditional Computations

Recall the brief introduction to conditional expressions in Prologue: How to Program. Since cond is the most complicated expression form in this book, let us take a close look at its general shape:
(cond
  [ConditionExpression1 ResultExpression1]
  [ConditionExpression2 ResultExpression2]
  ....
  [ConditionexpressionN ResultExpressionN])
Like all expressions, a cond expression starts with ( and ends with ). Like all expressions except for function application, a cond is marked with a keyword, in this case the word cond. Following the keyword, a programmer writes as many cond-lines as needed; each cond line consists of two expressions, enclosed in opening and closing brackets: [ and ]. The use of brackets isn’t necessary; it is a convention to make cond-lines stand out. Put differently, it is perfectly acceptable to use pairs of [ and ] in place of pairs of ( and ) and vice versa.

Here is a function definition that uses a conditional expression:
(define (next traffic-light-state)
  (cond
    [(string=? "red" traffic-light-state) "green"]
    [(string=? "green" traffic-light-state) "yellow"]
    [(string=? "yellow" traffic-light-state) "red"]))
Like the mathematical example in Prologue: How to Program, this example illustrates the convenience of using cond expressions. In many problem contexts, a function must distinguish several different situations. With a cond expression, you can use one line per possibility and thus remind the reader of the code of the different situations from the problem statement.

A note on pragmatics: Contrast cond expressions with if expressions from Mixing It Up With Booleans. The latter distinguish one situation from all others. As such, if expressions are much less suited for multi-situation contexts; they are best used when all we wish to say is “one or the other.” We therefore always use cond for situations when we wish to remind the reader of our code that some distinct situations come directly from data definitions. For other pieces of code, we use whatever construct is most convenient.

When the conditions get too complex in a cond expression, you occasionally wish to say something like "in all other cases." For these kinds of problems, cond expressions permit the use of the else keyword for the very last cond line:
(cond
  [ConditionExpression1 ResultExpression1]
  [ConditionExpression2 ResultExpression2]
  ....
  [else DefaultResultExpression])
If you make the mistake of using else in some other cond line, BSL in DrRacket signals an error:
> (cond
    [(> x 0) 10]
    [else 20]
    [(< x 10) 30])

cond: found an else clause that isn't the last clause in its cond expression

That is, BSL rejects grammatically incorrect phrases because it makes no sense to figure out what such a phrase might mean.

Imagine designing a function that, as part of a game-playing program, computes some good-bye sentence at the end of the game:
; PositiveNumber is a Number greater or equal to 0.
 
; PositiveNumber -> String
; compute the reward level from the given score s
(define (reward s)
  (cond
    [(<= 0 s 10)
     "bronze"]
    [(and (< 10 s) (<= s 20))
     "silver"]
    [(< 20 s)
     "gold"]))
(define (reward s)
  (cond
    [(<= 0 s 10)
     "bronze"]
    [(and (< 10 s) (<= s 20))
     "silver"]
    [else
     "gold"]))
For a line-by-line comparison, this table displays a cond with three full-fledged conditions on the left and with an else clause on the right. To formulate the last condition for the function on the left, you must calculate that (< 20 s) holds if
While the calculation looks simple in this case, it is easy to make small mistakes and to introduce bugs into your program. It is therefore better to formulate the function definition as shown on the right, if you know that you want the exact opposite—called the complementof all previous conditions in a cond.

4.2 How It Works

From reading the Many Ways To Compute and Mixing It Up With Booleans, you roughly know how DrRacket evaluates conditional expressions. Let us go over the idea a bit more precisely for cond expressions. Re-consider this definition
(define (reward s)
  (cond
    [(<= 0 s 10) "bronze"]
    [(and (< 10 s) (<= s 20)) "silver"]
    [else "gold"]))
The function reward consumes a numeric score—a positive number—and produces a color string.

Just looking at the cond expression you cannot predict which of the three cond clauses is going to be used. And that is the point of a function. The function deals with many different inputs, for example, 2, 3, 7, 18, 29. For each of these inputs, it may have to proceed in a different manner. Differentiating among the different classes of inputs is the purpose of the cond expression.

Take for example

(reward 3)

You know that DrRacket replaces function applications with the function’s body after substituting the argument for the parameter:
(cond
  [(<= 0 3 10) "bronze"]
  [(and (< 10 3) (<= 3 20)) "silver"]
  [else "gold"])
For a cond expression, DrRacket evaluates one condition at a time. For the first one that evaluates to true it replaces the cond with the result expression:
(reward 3)
= (cond
    [(<= 0 3 10) "bronze"]
    [(and (< 10 3) (<= 3 20)) "silver"]
    [else "gold"])
= (cond
    [true "bronze"]
    [(and (< 10 3) (<= 3 20)) "silver"]
    [else "gold"])
= "bronze"
In this particular example, the first condition—which the substitution turned into an ordinary expression in the arithmetic of Boolean values—evaluates to true because 0 is less than 3 and 3 is less than 10.

Here is a second example:
(reward 21)
= (cond
    [(<= 0 21 10) "bronze"]
    [(and (< 10 21) (<= 21 20)) "silver"]
    [else "gold"])
= (cond
    [false "bronze"]
    [(and (< 10 21) (<= 21 20)) "silver"]
    [else "gold"])
= (cond
    [(and (< 10 21) (<= 21 20)) "silver"]
    [else "gold"])
Note how the first condition evaluated to false this time, and as mentioned in Many Ways To Compute the entire cond clause is dropped. The rest of the calculation proceeds as expected:
...
= (cond
    [(and (< 10 21) (<= 21 20)) "silver"]
    [else "gold"])
= (cond
    [(and true (<= 21 20)) "silver"]
    [else "gold"])
= (cond
    [(and true false) "silver"]
    [else "gold"])
= (cond
    [false "silver"]
    [else "gold"])
= (cond
    [else "gold"])
= "gold"
Like the first condition, the second one also evaluates to false, though in several steps, and so the calculation proceeds to the third cond line. The else tells DrRacket to replace the entire cond expression with the result expression from this clause.

Exercise 41: Enter the definition of reward and the application (reward 18) into the Definitions area of DrRacket and use the stepper to find out how DrRacket evaluates applications of the function.

Exercise 42: A cond expression is really just an expression and may therefore show up in the middle of another expression:
(- 200 (cond
         [(> y 200) 0]
         [else y]))
Evaluate the above expression for two distinct values of y: 100 and 210.

Nesting cond expressions can eliminate common expressions. Recall the following function definition from Prologue: How to Program:
(define (create-rocket-scene.v5 h)
  (cond
    [(<= h ROCKET-CENTER-TO-BOTTOM)
     (place-image ROCKET 50 h MTSCN)]
    [(> h ROCKET-CENTER-TO-BOTTOM)
     (place-image ROCKET 50 ROCKET-CENTER-TO-BOTTOM MTSCN)]))
As you can see, both branches of the cond expression have the following shape:

(place-image ROCKET X ... MTSCN)

with ... indicating where they differ. Reformulate create-rocket-scene.v5 so that it uses this common shape with a cond expression in place of ... in lieu of the cond expression.

4.3 Enumerations

Not all strings represent mouse events. If you looked in HelpDesk when the last section introduced the on-mouse clause for big-bang, you found out that only six strings are used to notify programs of mouse events:
; A MouseEvt is one of these strings:
;  "button-down"
;  "button-up"
;  "drag"
;  "move"
;  "enter"
;  "leave"
The interpretation of these strings is quite obvious. One of the first two strings shows up when the computer user clicks the mouse button or releases it. In contrast, the third and fourth are about moving the mouse and possibly holding down the mouse button at the same time. Finally, the last two strings represent the events of a mouse moving over the edge of the canvas: either going into the canvas from the outside or exiting the canvas.

More importantly, the data definition for representing mouse events as strings looks quite different from the data definitions we have seen so far. It is called an enumeration, and it is a data representation in which every possibility is listed. It should not come as a surprise that enumerations are common. Here is a simple one:
; A TrafficLight shows one of three colors:
;  "red"
;  "green"
;  "yellow"
; interp. each element of TrafficLight represents which colored
; bulb is currently turned on
We call it simplistic because it does not include the "off" state, the "blinking red" state, or the "blinking yellow" state. It is a simplistic representation of the states that a traffic light can take on. Unlike others, this data definition also uses a slightly different phrase to explain what a term (TrafficLight) means but this is an inessential difference.

Programming with enumerations is mostly straightforward. When a function’s input is a class of data whose description spells out its elements on a case-by-case basis, the function should distinguish just those cases and compute the result on a per-case basis. For example, if you wanted to define a function that computes the next state of a traffic light, given the current state as an element of TrafficLight, you would come up with a definition like this one:
; TrafficLight -> TrafficLight
; given state s, determine the next state of the traffic light
 
(check-expect (traffic-light-next "red") "green")
 
(define (traffic-light-next s)
  (cond
    [(string=? "red" s) "green"]
    [(string=? "green" s) "yellow"]
    [(string=? "yellow" s) "red"]))
Because the data definition for TrafficLight consists of three distinct elements, the traffic-light-next function naturally distinguishes between three different cases. For each case, the result expression is just another string, the one that corresponds to the next case.

Exercise 43: If you copy and paste the above function definition into the definitions area of DrRacket and click RUN, DrRacket highlights two of the three cond lines. This coloring tells you that your test cases do not cover all possible cases. Add enough tests to make DrRacket happy.

Exercise 44: Design a function that renders the state of a traffic light as a solid circle of the appropriate color. When you have tested this function sufficiently, enter a big-bang expression that displays your traffic light and that changes its state on every clock tick.

The main idea of an enumeration is that it defines a collection of data as a finite number of pieces of data. Each item explicitly spells out which piece of data belongs to the class of data that we are defining. Usually, the piece of data is just shown as is; on some occasions, the item of an enumeration is an English sentence that describes a finite number of elements of pieces of data with a single phrase.

Here is an important example:
; A 1String is a string of length 1,
; including " " (the space bar), "\t" (tab),
; "\r" (return), and "\b" (backspace).
You know that such a data definition is proper if you can describe all of its elements with a BSL test. In the case of 1String, you can find out whether some string s belongs to the collection with

(= (string-length s) 1)

An alternative way to check that you have succeeded is to enumerate all the members of the collection of data that you wish to describe:
; A 1String is one of:
;  "q"
;  "w"
;  "e"
;  "r"
;  "t"
;  "y"
; ...
In this case you can see that it suffices to go along the key board and to express all keys as strings.

You know where to find the rest of this data definition. The data definition for representing key strokes is a realistic example that employs this same technique. Here is an excerpt:
; A KeyEvent is one of:
;  a single-character string, i.e., a string of length 1
;  "left"
;  "right"
;  "up"
;  "down"
;  ...
The first item in this enumeration describes the same bunch of strings that 1String describes. The clauses that follow enumerate strings for special key events, such as pressing one of the four arrow keys or releasing a key.

Asked to design an interactive program with a function for key events, we would use the following outline:
; TrafficLight KeyEvent -> ...
(define (handle-key-events w ke)
  (cond
    [(= (string-length ke) 1) ...]
    [(string=? "left" ke) ...]
    [(string=? "right" ke) ...]
    [(string=? "up" ke) ...]
    [(string=? "down" ke) ...]
    ...))
The body of the function consists of a cond expression and for each line in the enumeration of the data definition, there is one cond line. The condition in the first cond line—also known as a clauseidentifies the KeyEvents identified in the first line of the enumeration, the second cond clause corresponds to the second data enumeration line, and so on.

; Position is a Number.
; interp. distance between the left periphery and the ball
 
; Position KeyEvent -> Position
; compute the next location of the ball
 
(check-expect (nxt 13 "left") 8)
(check-expect (nxt 13 "right") 18)
(check-expect (nxt 13 "a") 13)
(define (nxt p k)
  (cond
    [(= (string-length k) 1) p]
    [(string=? "left" k) (- p 5)]
    [(string=? "right" k) (+ p 5)]
    [else p]))
(define (nxt p k)
  (cond
    [(string=? "left" k) (- p 5)]
    [(string=? "right" k) (+ p 5)]
    [else p]))

Figure 10: Conditional functions and special enumerations

When programs rely on data definitions that are defined by a programming language (such as BSL) or its libraries (such as the "universe" teachpack), it is common that they use only a part of the enumeration. To illustrate this point, let us look at a representative problem.

Sample Problem: Design an interactive program that moves a red dot left or right on a horizontal line in response keystrokes on the "left" and "right" arrow keys.

Figure 10 presents two solutions to this problem. The function on the left is organized according to the basic idea of using one cond clause per line in the data definition of the input, here KeyEvent. In contrast, the right-hand side displays a function that uses the three essential lines: two for the keys that matter and one for everything else. The re-ordering is appropriate because only two of the cond-lines are relevant and they can be cleanly separated from other lines. Naturally, this kind of re-arrangement is done after the function is designed properly.

4.4 Intervals

Imagine yourself responding to the following sample design task:

Sample Problem: Design a program that simulates the landing of a UFO.

After a bit of thinking, you could come up with a program like the one figure 11. Understand the data definition and the function definitions fully before you read on.

; WorldState is a Number
; interp. height of UFO (from top)
 
; constants:
(define WIDTH 300)
(define HEIGHT 100)
(define CLOSE (/ HEIGHT 3))
 
; visual constants:
(define MT (empty-scene WIDTH HEIGHT))
(define UFO
  (overlay (circle 10 "solid" "green")
           (rectangle 40 2 "solid" "green")))
 
; WorldState -> WorldState
; compute next location of UFO
 
(check-expect (nxt 11) 14)
 
(define (nxt y)
  (+ y 3))
 
; WorldState -> Image
; place UFO at given height into the center of MT
 
(check-expect (render 11)
              (place-image UFO (/ WIDTH 2) 11 MT))
 
(define (render y)
  (place-image UFO (/ WIDTH 2) y MT))
 
; run program run
; WorldState -> WorldState
(define (main y0)
  (big-bang y0 (on-tick nxt) (to-draw render)))

Figure 11: Landing a UFO

Before you release this "game" program, however, you may wish to add the display of status line to the canvas:

Sample Problem: The status line says "descending" when the UFO’s height is above one third of the height of the canvas. It switches to "closing in" below that. And finally, when the UFO has reached the bottom of the canvas, the status notifies the player that the UFO has "landed."

You are free to use appropriate colors for the status line.

In this case, we don’t have a finite enumeration of distinct elements or distinct subclasses of data. After all conceptually the interval between 0 and HEIGHT (for some number greater than 0) contains an infinite number of numbers and a large number of integers. Therefore we use intervals to superimpose some organization on the generic data definition, which just uses "numbers" to describe the class of coordinates.

An interval is a description of a class of (real or rational or integer) numbers via boundaries. The simplest interval has two boundaries: left and right. If the left boundary is to be included in the interval, we say it is a closed on the left. Similarly, a right-closed interval includes its right boundary. Finally, if an interval does not include a boundary, it is said to be open at that boundary.

Pictures of, and notations for, intervals use brackets for closed boundaries and parentheses for open boundaries. Here are four pictures of simple intervals:

Exercise 45: Determine the integers that each of the four intervals contains.

The interval concept helps us formulate a data definition that captures the revised problem statement better than the "numbers" based definition:
; constants:
(define CLOSE (/ HEIGHT 3))
 
; A WorldState falls into one of three intervals:
;  between 0 and CLOSE
;  between CLOSE and HEIGHT
;  below HEIGHT

Specifically, there are three intervals, which we may picture as follows:

What you see is the standard number line, turned vertical and broken into intervals. Each interval starts with an angular downward-pointing bracket () and ends with an upward-pointing bracket (). The picture identifies three intervals in this manner:
  • the upper interval goes from 0 to CLOSE;

  • the middle one starts at CLOSE and reaches HEIGHT; and

  • the lower, invisible interval is just a single line at HEIGHT.

Of course, one can also think of the last interval as one that starts at HEIGHT and goes on forever.

Visualizing the data definition in this manner helps with the design of functions in many ways. First, it immediately suggests how to pick examples. Clearly we want the function to work inside of all the intervals and we want the function to work properly at the ends of each interval. Second, the image as well as the data definition tell us that we need to formulate a condition that determines whether or not some "point" is within one of the intervals.

Putting the two together also raises a question, namely, how exactly the function deals with the end points. In the context of our example, two points on the number line belong to two intervals: CLOSE belongs to both the upper interval and the middle one, while HEIGHT seems to fall into both the middle one and the lowest one. Such overlaps usually cause problems for programs, and they ought to be avoided.

BSL functions avoid them naturally due to the way cond expressions are evaluated. Consider this natural organization of a function that consumes elements of WorldState:
; WorldState -> WorldState
(define (f y)
  (cond
    [(<= 0 y CLOSE) ...]
    [(<= CLOSE y HEIGHT) ...]
    [(>= y HEIGHT) ...]))
The three cond lines correspond to the three intervals. Each condition identifies those values of y that are in between the limits of the intervals. Due to the way cond lines are checked one by one, however, a y value of CLOSE makes BSL pick the first cond line, and a y value of HEIGHT triggers the evaluation of the second ResultExpression.

If we wanted to make this choice obvious and immediate for every reader of our code, we would use different conditions:
; WorldState -> WorldState
(define (g y)
  (cond
    [(<= 0 y CLOSE) ...]
    [(and (< CLOSE y) (<= y HEIGHT)) ...]
    [(> y HEIGHT) ...]))
Note how the second cond line of g uses an and expression to combine a strictly-less check with a less-than-or-equal check instead of f’s <= with three arguments.

Given all that, we can complete the definition of the function that adds the requested status line to our UFO animation:
; WorldState -> Image
; add a status line to the scene create by render  
 
(check-expect (render/status 10)
              (place-image (text "descending" 11 "green")
                           10 10
                           (render 10)))
 
(define (render/status y)
  (cond
    [(<= 0 y CLOSE)
     (place-image (text "descending" 11 "green")
                  10 10
                  (render y))]
    [(and (< CLOSE y) (<= y HEIGHT))
     (place-image (text "closing in" 11 "orange")
                  10 10
                  (render y))]
    [(> y HEIGHT)
     (place-image (text "landed" 11 "red")
                  10 10
                  (render y))]))
The function uses a cond expression to distinguish the three intervals. In each cond clause, the ResultExpression uses render (from figure 11) to create the image with the descending UFO and then places an appropriate text at position (10,10) with place-image.

To run the animation with this function, you need to change the main function just a tiny bit:
; WorldState -> WorldState
(define (main y0)
  (big-bang y0 (on-tick nxt) (to-draw render/status)))

One aspect of this function definition might disturb you, and to clarify why, let us refine the sample problem from above just a tiny bit:

Sample Problem: The status line, positioned at (20,20), says "descending" when the UFO’s height is above one third of the height of the canvas. It switches to "closing in" below that. And finally, when the UFO has reached the bottom of the canvas, the status notifies the player that the UFO has "landed."

You could imagine such a change to the problem statement after your client has run your program.

At this point, you have no choice but to change the function render/status at three distinct places because you have three copies of one external piece of information: the location of the status line. To avoid multiple changes for a single element, programmers try to avoid copies. You have two choices to fix this problem here. The first one is to use constant definitions, which we have seen before. The second one is to think of the cond expression as an expression that may appear anywhere in a function, including in the middle of some other expression:
; WorldState -> Image
; add a status line to the scene create by render  
 
(check-expect (render/status 42)
              (place-image (text "descending" 11 "green")
                           20 20
                           (render 42)))
 
(define (render/status y)
  (place-image
    (cond
      [(<= 0 y CLOSE)
       (text "descending" 11 "green")]
      [(and (< CLOSE y) (<= y HEIGHT))
       (text "closing in" 11 "orange")]
      [(> y HEIGHT)
       (text "landed" 11 "red")])
    20 20
    (render y)))
In this revised definition of render/status, the cond expression is the first argument to place-image. As you can see, its result is always a text image that is placed at position (20,20) into the image created by (render y).

4.5 Itemizations

An interval distinguishes different subclasses of numbers; an enumeration spells out item for item the useful elements of an existing class of data. Data definitions that use itemizations generalize intervals and enumerations. They allow the combination of any existing data classes (defined elsewhere) with each other and with individual pieces of data.

Here is an obvious example, a rewrite of an important example from Enumerations:
; A KeyEvent is one of:
;  1String
;  "left"
;  "right"
;  "up"
;  "down"
;  ...
In this case, the KeyEvent data definition refers to the 1String data definition. Since functions that deal with KeyEvents often deal with 1Strings separately from the rest and do so with auxiliary functions, we now have a convenient way to express signatures for these functions, too.

The description of the string->number primitive, which we used before, employs the idea of an itemization in a sophisticated way. Its signature is
; string->number : String -> NorF
; converts the given string into a number;
; produces false if impossible
meaning that the result signature names a simple class of data:
; A NorF is one of:
;  false
;  a Number
This itemization combines one piece of data (false) with a large class of data (Number).

Now imagine a function that consumes the result of string->number and adds 3, dealing with false as if it were 0:
; NorF -> Number
; add 3 to the given number; 3 otherwise
 
(check-expect (add3 false) 3)
(check-expect (add3 0.12) 3.12)
 
(define (add3 x)
  (cond
    [(false? x) 3]
    [else (+ x 3)]))
As above, the function’s body consists of a cond expression with as many clauses as there are items in the enumeration of the data definition. The first cond clause recognizes when the function is applied to false; the corresponding result is 3 as requested. The second clause is about numbers and adds 3 as required.

Let’s solve a somewhat more purposeful design task:

Sample Problem: Design a program that launches a rocket when the user of your program presses the space bar and displays the rising rocket. The rocket is to move upward at a rate of three pixels per clock tick.

This revised version of the “rocket launch” problem requires the user to launch the rocket. Its wording suggests a data definition that represents two classes of states:
; A LR (short for: launching rocket) is one of:
;  "resting"
;  non-negative number
; interp. a rocket on the ground or the height of a rocket in flight
One state represents the rocket on the ground; the other one is about a rocket in flight. While the interpretation of "resting" is obvious, the interpretation of numbers is a bit ambiguous. Any given number could refer to at least two different notions of “height:”
  1. the word “height” could refer to the distance between the ground and the rocket’s point of reference, say, its center; or

  2. it could mean the distance between the top of the canvas and the reference point.

Either one works fine. The second one uses the conventional computer meaning of the word “height.” It is thus slightly more convenient for functions that translate the state of the world into an image, and we therefore choose to interpret the number in that spirit.

To drive home this choice, exercise 50 below asks you to solve the exercises of this section using the first interpretation of “height.”

Exercise 46: The design recipe for world programs demands that you translate information into data and vice versa to ensure a complete understanding of the data definition. In some way it is best to draw some world scenarios and to represent them with data and, conversely, to pick some data examples and to draw pictures that match them. Do so for the LR definition, including at least HEIGHT and 0 as examples.

In reality, rocket launches come with count-downs:

Sample Problem: Design a program that launches a rocket when the user presses the space bar. At that point, the simulation starts a count-down for three ticks, before it displays the scenery of a rising rocket. The rocket should move upward at a rate of three pixels per clock tick.

This revision of the problem calls for three distinct classes of states:
; A LRCD (short for: launching rocket count down) is one of:
;  "resting"
;  a number in [-3,-1]
;  a non-negative number
; interp. a rocket resting on the ground, in count-down mode,
;   or the number of pixels from the top i.e. its height
The new second class of data—three negative numbers—represent the world after the user pressed the space bar and before the rocket lifts off.

Now that we have the data definition, we write down the obvious physical and graphical constants:

; physical constants
(define HEIGHT 300)
(define WIDTH  100)
(define YDELTA 3)
 
; graphical constants
(define BACKG  (empty-scene WIDTH HEIGHT))
(define ROCKET (rectangle 5 30 "solid" "red")) ; use your favorite image
The YDELTA constant describes how fast the rocket moves along the y axis, as specified in the problem statement; HEIGHT and WIDTH are arbitrary as is the graphical constant for the rocket.

Recall from Designing World Programs that the next step is to design three functions:
  • one for displaying the current state of the world as an image:
    ; LRCD -> Image
    ; render the state as a resting or flying rocket
    (define (show x)
      BACKG)

  • one for reacting to the user’s key presses, the space bar in this problem:
    ; LRCD KeyEvent -> LRCD
    ; start the count-down when space bar is pressed,
    ; and the rocket is resting
    (define (launch x ke)
      x)
    As Designing World Programs says, a key event handler is applied to two values: the current state of the world and the key event.

  • and one for reacting to clock ticks once the simulation is launched:
    ; LRCD -> LRCD
    ; raise the rocket by YDELTA if it is moving already
    (define (fly x)
      x)

While the design strategy for world programs—specifically, the mandates of the "universe" teachpack—dictates the signature, we have added purpose statements that match the problem.

From here, we use the design recipe for functions to create complete definitions for all three of them, starting with examples for the first one:
(check-expect
 (show "resting")
 (place-image ROCKET
              10 (- HEIGHT (/ (image-height ROCKET) 2))
              BACKG))
 
(check-expect
 (show -2)
 (place-image (text "-2" 20 "red")
              10 (* 3/4 WIDTH)
              (place-image ROCKET
                           10 (- HEIGHT (/ (image-height ROCKET) 2))
                           BACKG)))
 
(check-expect
 (show 53)
 (place-image ROCKET 10 53 BACKG))
As before in this chapter, we make one test per subclass in the data definition. The first example shows the resting state, the second the middle of a count down, and the last one the rocket in flight. Furthermore, we express the expected values as expressions that draw appropriate images. We used DrRacket’s interaction area to create these images; what would you do?

A first look at the examples suggests the introduction of at least one additional constant:

(define ROCKET-CENTER (/ (image-height ROCKET) 2))

This value is used at least twice to place the rocket at the appropriate place, namely on the ground of the empty scene.

A second look at the examples reveals that making examples also means making choices. Nothing in the problem statement actually demands how exactly the rocket is displayed before it is launched but doing so is natural. Similarly, nothing says to display a number during the count down, but it adds a nice touch. Last but not least, if you solved exercise 46 you also know that HEIGHT and 0 are special points for the third clause of the data definition.

In general, intervals deserve special attention when you make up examples, that is, they deserve at least three kinds of examples: one from each end and another one from inside. Since the second subclass of LRCD is a (finite) interval and the third one is a half-open interval, let us add take a closer look at their end points:
  • Clearly, (show -3) and (show -1) must produce images like the one for (show -2). After all, the rocket still rests on the ground, even if the count down numbers differ.

  • The case for (show HEIGHT) is different. According to our agreement, HEIGHT represents the state when the rocket has just been launched. Pictorially this means the rocket is still resting on the ground. Based on the last test case above, here is the test case that expresses this insight:
    (check-expect
     (show HEIGHT)
     (place-image ROCKET 10 HEIGHT BACKG))
    Except that if you evaluate the “expected value” expression by itself in DrRacket’s interaction area, you see that the rocket is half-way underground. This shouldn’t be the case of course, meaning we need to adjust this test case and the above:
    (check-expect
     (show HEIGHT)
     (place-image ROCKET 10 (- HEIGHT ROCKET-CENTER) BACKG))
     
    (check-expect
     (show 53)
     (place-image ROCKET 10 (- 53 ROCKET-CENTER) BACKG))

  • Finally, determine the result you now expect from (show 0). It is a simple but revealing exercise.

Following the precedents in this chapter, show uses a cond expression to deal with the three clauses of the data definition:
(define (show x)
  (cond
    [(string? x) ...]
    [(<= -3 x -1) ...]
    [(>= x 0) ...]))
Each clause identifies the corresponding subclass with a precise condition: (string? x) picks the first subclass, which consists of just one element, the string "resting"; (<= -3 x -1) completely describes the second subclass of data; and (> x 0) is a test for all non-negative numbers.

Exercise 47: Why would it be incorrect to formulate the first condition as (string=? "resting" x)? Conversely, formulate a completely accurate condition, that is, a Boolean expression that evaluates to true precisely when x belongs to the first subclass of LRCD. Similarly, what is a completely accurate condition for the third clause?

Combining the examples and the above skeleton of the show function yields a complete definition in a reasonably straightforward manner:
(define (show x)
  (cond
    [(string? x)
     (place-image ROCKET 10 (- HEIGHT ROCKET-CENTER) BACKG)]
    [(<= -3 x -1)
     (place-image (text (number->string x) 20 "red")
                  10 (* 3/4 WIDTH)
                  (place-image ROCKET
                               10 (- HEIGHT ROCKET-CENTER)
                               BACKG))]
    [(>= x 0)
     (place-image ROCKET 10 (- x ROCKET-CENTER) BACKG)]))
Indeed, this way of defining functions is highly effective and is an essential element of the full-fledged design recipe that we are developing in this book.

Stop! Do you notice all the occurrences of 10 in the code? It always refers to the x coordinate of the rocket. So go ahead, create a constant definition for this 10. Now read on.

Exercise 48: Take a second look at show. An expression of the shape

(place-image ROCKET 10 (- ... ROCKET-CENTER) BACKG)

appears three different times in the function: twice to draw a resting rocket and once to draw a flying rocket. Define an auxiliary function that performs this work and thus shorten show. Why is this a good idea? We discussed this idea in Prologue: How to Program.

Let us move on to the second function, which deals with the key event to launch the rocket. We have its header material, so we formulate examples as tests:
(check-expect (launch "resting" " ") -3)
(check-expect (launch "resting" "a") "resting")
(check-expect (launch -3 " ") -3)
(check-expect (launch -1 " ") -1)
(check-expect (launch 33 " ") 33)
(check-expect (launch 33 "a") 33)
An inspection of these six examples shows that the first two are about the first subclass of LRCD, the third and fourth concern the count-down, and the last two are about key events when the rocket is already in the air.

Since writing down the sketch of a cond expression worked well for the design of the show function, we do it again:
(define (launch x ke)
  (cond
    [(string? x) ...]
    [(<= -3 x -1) ...]
    [(>= x 0) ...]))
Looking back at the examples suggests that nothing changes when the world is in a state that is represented by the second or third subclass of data. Put differently, it is obvious that launch should produce xthe current state of the world—when this happens:
(define (launch x ke)
  (cond
    [(string? x) ...]
    [(<= -3 x -1) x]
    [(>= x 0) x]))
Finally, the first example identifies the exact case when the launch function produces a new world state:
(define (launch x ke)
  (cond
    [(string? x) (if (string=? " " ke) -3 x)]
    [(<= -3 x -1) x]
    [(>= x 0) x]))
Specifically, when the state of the world is "resting" and the user presses the space bar, the function starts the count-down with -3.

Copy the code into the definitions area of DrRacket and ensure that the above definitions work. At that point, you may wish to add a function for running the program:
; LRCD -> LRCD
(define (main1 s)
  (big-bang s
            (to-draw show)
            (on-key launch)))
This function does not specify what to do when the clock ticks; after all, we haven’t designed fly yet. Still, with main1 it is possible to run this incomplete version of the program and to check that you can start the count-down. What would you provide as the argument in a call to main1?

; LRCD -> LRCD
; raise the rocket by YDELTA if it is moving already
 
(check-expect (fly "resting") "resting")
(check-expect (fly -3) -2)
(check-expect (fly -2) -1)
(check-expect (fly -1) HEIGHT)
(check-expect (fly 10) (- 10 YDELTA))
(check-expect (fly 22) (- 22 YDELTA))
 
(define (fly x)
  (cond
    [(string? x) x]
    [(<= -3 x -1) (if (= x -1) HEIGHT (+ x 1))]
    [(>= x 0) (- x YDELTA)]))

Figure 12: Launching a count-down and a lift-off

The design of flythe clock-tick handler—proceeds just like the design of the preceding two functions, and the above figure displays the result of the design process. Once again the key is to cover the space of possible input data with a good bunch of examples, especially for the two intervals. These examples ensure that the count-down and the transition from the count-down to the lift-off work properly.

Exercise 49: Define main2 so that you can launch the rocket and watch it lift off. Read up on the on-tick clause to determine the length of one tick and how to change it.

If you watch the entire launch, you will notice that once the rocket reaches the top, something curious happens. Explain. Add a stop-when clause to main2 so that the simulation of the lift-off stops gracefully when the rocket is out of sight.

In solving exercise 49 you now have a complete, working program but one that behaves a bit strangely. Experienced programmers tell you that using negative numbers to represent the count-down phase is too “brittle.” The next chapter introduces the means to provide a good data definition for this problem. Before we go there, however, let us reiterate in the following section how to design programs that consume data described by itemizations.

Exercise 50: Recall that the word “height” forced us to choose one of two possible interpretation. Now that you have solved the exercises in this section, solve them again using the first interpretation of the word. Compare and contrast the solutions.

4.6 Designing With Itemizations

What the preceding three sections have clarified is that the design of functions can and must exploit the organization of the data definition. Specifically, if a data definition singles out certain pieces of data or specifies ranges of data, then the creation of examples and the organization of the function reflects these cases and ranges.

In this section, we refine the design recipe of From Functions to Programs so that you can proceed in a systematic manner when you encounter problems concerning functions that consume itemizations, including enumerations and intervals. To keep the explanation grounded, we illustrate the six design steps with the following, somewhat simple example:

Sample Problem: The state of Tax Land has created a three-stage sales tax to cope with its budget deficit. Inexpensive items, those costing less than $1,000, are not taxed. Luxury items, with a price of more than $10,000, are taxed at the rate of eight percent (8.00%). Everything in between comes with a five percent (5.25%) price tag.

Design a function for a cash register that computes the sales tax for each item. That is, design a function that, given the price of an item, computes the amount of tax to be charged.

So keep this problem in mind as we reconsider the steps of our design recipe:
  1. When the problem statement distinguishes different classes of input information, we need carefully formulated data definitions.

    A data definition should explicitly enumerate different subclasses of data or in some cases just individual pieces of data. Each of those subclasses represents a subclass of information. The key is that each subclass of data is distinct from every other class so that our function can distinguish the subclasses, too.

    Our sample problem deals with prices and taxes, which are usually positive numbers. It also clearly distinguishes three ranges of positive numbers:
    ; A Price falls into one of three intervals:
    ;  0 through 1000;
    ;  1000 through 10000;
    ;  10000 and above.
    Make sure you understand how these three ranges relate to the original problem.

  2. As far as the signature, purpose statement, and function header are concerned, we proceed as before.

    Here is the material for our running example:
    ; Price -> Number
    ; compute the amount of tax charged for price p
    (define (sales-tax p) 0)

  3. For functional examples, however, it is imperative that we pick at least one example from each subclass in the data definition. Also, if a subclass is a finite range, be sure to pick examples from the boundaries of the range and from its interior.

    Since our data definition involves three distinct intervals, let us pick all boundary examples and one price from inside each interval and determine the amount of tax for each: 0, 537, 1000, 1282, 10000, and 12017. Before you read on, try to calculate the tax for each of these prices.

    Here is our first attempt:

    0.00

    537.00

    1000.00

    1282.00

    10000.00

    12017.00

    0.00

    0.00

    ???

    64.10

    ???

    961.36

    Stop for a moment and think about the table entries with question marks.

    These question marks point out that the problem statement uses the somewhat vague phrase “those costing less than $1,000” and “more than $10,000” to specify the tax table. While a programmer may immediately jump to the conclusion that these words mean “strictly less” or “strictly more,” the lawmakers may have meant to say “less or equal” or “more or equal,” respectively. Being skeptical, we decide here that Tax Land legislators always want more money to spend, so the tax rate for $1,000 is 5% and the rate for $10,000 is 8%. A programmer in a tax company would have to ask the tax-law specialist in the company; if you were designing your own tax software, you might have to visit your local library or tax office.

    Once we have figured out how the boundaries are supposed to be interpreted in the domain, we need to also refine the data definition. Stop! Modify the data definition above so that it expresses the choices just made.

    Before we go, let us turn some of the examples into test cases:
    (check-expect (sales-tax 537) 0)
    (check-expect (sales-tax 1000) (* 0.05 1000))
    (check-expect (sales-tax 12017) (* 0.08 12017))
    Take a close look. Instead of just writing down the expected result, we write down how to compute the expected result. This makes it easier later to formulate the function definition.

    Stop! Write down the remaining test cases. Think about why you may need more test cases than subclasses in the data definition.

  4. The biggest novelty is the conditional template. In general,

    the template mirrors the organization of subclasses with a cond.

    This slogan means two concrete things. First, the function’s body must be a conditional expression with as many clauses as there are distinct subclasses in the data definition. If the data definition mentions three distinct subclasses of input data, you need three cond clauses; if it has seventeen subclasses, the cond expression contains seventeen clauses. Second, you must formulate one condition expression per cond clause. Each expression involves the function parameter and identifies one of the subclasses of data in the data definition.

    ; Price -> Number
    ; compute the amount of tax charged for price p
    (define (sales-tax p)
      (cond
        [(and (<= 0 p) (< p 1000)) ...]
        [(and (<= 1000 p) (< p 10000)) ...]
        [(>= p 10000) ...]))
  5. We are ready to define the function. Given that the function body already contains a schematic cond expression, it is natural to start from the various cond lines. For each cond line, we assume that the input parameter meets the condition and we take a look at corresponding test cases. To formulate the corresponding result expression, we write down the computation for this example as an expression that involves the function parameter. We ignore all other possible kinds of input data when you work on one line; the other cond clauses take care of those.

    ; Price -> Number
    ; compute the amount of tax charged for price p
    (define (sales-tax p)
      (cond
        [(and (<= 0 p) (< p 1000)) 0]
        [(and (<= p 1000) (< p 10000)) (* 0.05 p)]
        [(>= p 10000) (* 0.08 p)]))
  6. Finally, we run the tests and make sure that the tests cover all cond clauses in the function.

    What do you do when one of your test cases fails? Review at the end of Designing Functions concerning test failures.

Exercise 51: Introduce constant definitions that separate the intervals for low prices and luxury prices from the others so that the legislator in Tax Land can easily raise the taxes even more.

4.7 A Bit More About Worlds

Let us exploit our knowledge to create a world that simulates a basic US traffic light. When the light is green and it is time to stop the traffic, the light turns yellow and after that turns red. When the light is red and it is time to get the traffic going, the light switches to green.

Figure 13: How a traffic light functions

Figure 13 summarizes this description as a state transition diagram. Such a diagram consists of states and arrows that connect these states. Each state depicts a traffic light in one particular configuration: red, yellow, or green. Each arrow shows how the world can change, from which state it can transition to another state. Our sample diagram contains three arrows, because there are three possible ways in which the traffic light can change. Labels on the arrows indicate the reason for changes; a traffic light transitions from one state to another as time passes.

In many situations, state transition diagrams have only a finite number of states and arrows. Computer scientists call such diagrams finite state machines or automata, for short: FSA. While FSAs look simple at first glance, they play an important role in computer science.

To create a world program for an FSA, we must first pick a data representation for the possible “states of the world,” which, according to Designing World Programs, represents those aspects of the world that may change in some ways as opposed to those that remain the same. In the case of our traffic light, what changes is the color of the light, that is, which bulb is turned on. The size of the bulbs, their arrangement (horizontal or vertical), and other aspects don’t change. Since there are only three states—red, yellow, and green—we pick three strings, as shown in the data definition of TrafficLight.

Figure 14: How to represent a traffic light

Figure 14 shows how to interpret the three elements of TrafficLight. Like the original figure, it consists of three states, arranged in such a way that it is easy to interpret each data element as a representation of a concrete configuration and to represent any such configuration as a piece of data. Also, the arrows are now labeled with tick to suggest that our world program uses the passing of time as the trigger that changes the state of the traffic light. Alternatively, we could use keystrokes or mouse events to switch the light, which would be especially appropriate if we wanted to simulate the manual operation of a traffic light.

Now that we know how to represent the states of our world, how to go from one to the next, and that the state changes at every tick of the clock, we can write down the signature, a purpose statement, and a stub for the two functions we must design:
; TrafficLight -> TrafficLight
; determine the next state of the traffic light, given current-state
(define (tl-next current-state) current-state)
 
; TrafficLight -> Image
; render the current state of the traffic light as a image
(define (tl-render current-state) (empty-scene 100 30))
In previous sections, we used the names render and next to name the functions that translate a state of the world into an image and that deal with clock ticks. From now on, we prefix these names with some syllable that suggests to which world the functions belong. Because the specific functions have appeared before, we leave them as exercises.

Exercise 52: Finish the design of a world program that simulates the traffic light FSA. Here is the main function:
; TrafficLight -> TrafficLight
; simulate a traffic light that changes with each tick
(define (traffic-light-simulation initial-state)
  (big-bang initial-state
            [to-draw tl-render]
            [on-tick tl-next 1]))
The function uses as its argument the initial state for the big-bang expression. It tells DrRacket to re-draw the state of the world with tl-render and to react to clock ticks with tl-next. Also note it informs the computer that the clock should tick once per second (how?).

In short, you have two design tasks to complete: tl-render and tl-design.

Hint 1: Create a DrRacket buffer that includes the data definition for TrafficLight and the function definitions of tl-next and tl-render.

For the design of the latter, we include some test cases:
(check-expect (tl-render "red") )
(check-expect (tl-render "yellow") )
(check-expect (tl-render "green") )

Hint 2: We started from the following graphical constants:
(define SPACE 5) ; the space between bulbs
(define RAD (* SPACE 2)) ; a bulb's radius
and introduced additional constants for the diameter, the width, the height, and so forth. You may find this auxiliary function helpful:
; TrafficLight TrafficLight -> Image
; render the c colored bulb of the traffic light,
; when on is the current state
(define (bulb on c)
  (if (light=? on c) (circle RAD "solid" c) (circle RAD "outline" c)))

Hint 3: Look up the image primitive place-image; it simplifies the task quite a bit.

Here is another finite-state problem that introduces a few additional complications:

Sample Problem: Design a world program that simulates the working of a door with an automatic door closer. If this kind of door is locked, you can unlock it with a key. An unlocked door is closed but someone pushing at the door opens it. Once the person has passed through the door and lets go, the automatic door takes over and closes the door again. When a door is closed, it can be locked again.

To tease out the essential elements, we again draw a transition diagram; see the left-hand side of the figure. Like the traffic light, the door has three distinct states: locked, closed, and open. Locking and unlocking are the activities that cause the door to transition from the locked to the closed state and vice versa. As for opening an unlocked door, we say that one needs to push the door open. The remaining transition is unlike the others, because it doesn’t require any activities by anyone or anything else. Instead, the door closes automatically over time. The corresponding transition arrow is labeled with *time* to emphasize this.

Figure 15: A transition diagram for a door with an automatic closer

Following our recipe, we start with a translation of the three real-world states into BSL data:
; A DoorState is one of:
;  "locked"
;  "closed"
;  "open"
That is, we use three strings to represent the three states, and the interpretation of these strings is natural.

The next step of a world design demands that we translate the actions in our domain—the arrows in the left-hand diagram—into interactions with the computer that the "universe" teachpack can deal with. Our pictorial representation of the door’s states and transitions, specifically the arrow from open to closed suggests the use of a function that simulates time. For the other three arrows, we could use either keyboard events or mouse clicks or both. Let us uses three keystrokes: "u" for unlocking the door, "l" for locking it, and the space bar " " for pushing it open. The right-hand side diagram expresses these choices graphically; it translates the above state-machine diagram from the world of information into the world of data and function suggestions.

Once we have decided to use the passing of time for one action and key presses for the others, we must design functions that transform the current state of the world—represented as DoorStateinto the next state of the world. Put differently, we have just created a wish list with two handler functions that have the following signature and purpose statements:
  • door-closer, which closes the door during one tick;

  • door-actions, which manipulates the door in response to pressing a key; and

  • door-render, which translates the current state of the door into an image.

The last wish comes from our desire to render states as images in our simulation.

We start with door-closer. Since door-closer acts as the on-tick handler, we get its signature from our choice of DoorState as the collection of world states:
; DoorState -> DoorState
; closes an open door over the period of one tick
(define (door-closer state-of-door) state-of-door)
Making up examples is trivial when the world can only be in one of three states. Here we use a table to express the basic idea, just like in some of the mathematical examples before:

given state

desired state

"locked"

"locked"

"closed"

"closed"

"open"

"closed"

This table can easily be expressed as BSL examples:
(check-expect (door-closer "locked") "locked")
(check-expect (door-closer "closed") "closed")
(check-expect (door-closer "open") "closed")

The template step demands a conditional with three clauses:
(define (door-closer state-of-door)
  (cond
    [(string=? "locked" state-of-door) ...]
    [(string=? "closed" state-of-door) ...]
    [(string=? "open" state-of-door) ...]))
and the process of turning this template into a function definition is dictated by the examples:
(define (door-closer state-of-door)
  (cond
    [(string=? "locked" state-of-door) "locked"]
    [(string=? "closed" state-of-door) "closed"]
    [(string=? "open" state-of-door) "closed"]))
Don’t forget to run the example-tests.

The second function, door-actions, takes care of the remaining three arrows of the diagram. Functions that deal with keyboard events consume both a world and a key event, meaning the signature is as follows:

; DoorState KeyEvent -> DoorState
; three key events simulate actions on the door
(define (door-actions s k) s)
As for door-closer, we summarize the examples in a table:

given state

"locked"

"closed"

"closed"

"open"

given key event

"u"

"l"

" "

desired state

"closed"

"locked"

"open"

"open"

The examples combine information from our drawing with the choices we made about mapping actions to keyboard events. Unlike the table of examples for traffic light, this table is incomplete. Think of some other examples; then consider why our table suffices.

From here, it is straightforward to create a complete design:
(check-expect (door-actions "locked" "u") "closed")
(check-expect (door-actions "closed" "l") "locked")
(check-expect (door-actions "closed" " ") "open")
(check-expect (door-actions "open" "a") "open")
(check-expect (door-actions "closed" "a") "closed")
 
(define (door-actions s k)
  (cond
    [(and (string=? "locked" s) (string=? "u" k)) "closed"]
    [(and (string=? "closed" s) (string=? "l" k)) "locked"]
    [(and (string=? "closed" s) (string=? " " k)) "open"]
    [else s]))
Note the use of and to combine two conditions: one concerning the current state of the door and the other concerning the given key event.

Last but not least we need a function that renders the current state of the world as a scene. For simplicity, let us just use a large text for this purpose:
; DoorState -> Image
; translate the current state of the door into a large text
 
(check-expect (door-render "closed") (text "closed" 40 "red"))
 
(define (door-render s)
  (text s 40 "red"))
Here is how we run the program:
; DoorState -> DoorState
; simulate a door with an automatic door closer
(define (door-simulation initial-state)
  (big-bang initial-state
          (on-tick door-closer)
          (on-key door-actions)
          (to-draw door-render)))
Now it is time for you to collect the pieces and run them in DrRacket to see whether it all works.

Exercise 53: During a door simulation the “open” state is barely visible. Modify door-simulation so that the clock ticks once every three seconds. Re-run the simulation.

; A DoorState is one of:
;  "locked"
;  "closed"
;  "open"
 
; 
; DoorState -> DoorState
; closes an open door over the period of one tick
 
(check-expect (door-closer "locked") "locked")
(check-expect (door-closer "closed") "closed")
(check-expect (door-closer "open") "closed")
 
(define (door-closer state-of-door)
  (cond
    [(string=? "locked" state-of-door) "locked"]
    [(string=? "closed" state-of-door) "closed"]
    [(string=? "open" state-of-door) "closed"]))
 
; 
; DoorState KeyEvent -> DoorState
; three key events simulate actions on the door
 
(check-expect (door-actions "locked" "u") "closed")
(check-expect (door-actions "closed" "l") "locked")
(check-expect (door-actions "closed" " ") "open")
(check-expect (door-actions "open" "a") "open")
(check-expect (door-actions "closed" "a") "closed")
 
(define (door-actions s k)
  (cond
    [(and (string=? "locked" s) (string=? "u" k)) "closed"]
    [(and (string=? "closed" s) (string=? "l" k)) "locked"]
    [(and (string=? "closed" s) (string=? " " k)) "open"]
    [else s]))
 
; 
; DoorState -> Image
; the current state of the door as a large red text
 
(check-expect (door-render "closed")
              (text "closed" 40 "red"))
 
(define (door-render s)
  (text s 40 "red"))
 
; 
; DoorState -> DoorState
; simulate a door with an automatic door closer
(define (door-simulation initial-state)
  (big-bang initial-state
            (on-tick door-closer)
            (on-key door-actions)
            (to-draw door-render)))

Figure 16: A door-simulation program

5 Adding Structure

Well, we could play some mathematical tricks that would “merge” two numbers into a single number in such a way that we could later extract them again. While these tricks are well-known to trained computer scientists, it should be clear to every budding programmer that such coding tricks obscure the true intentions behind a program. We therefore don’t play this kind of game. Suppose you want to design a world program that simulates a ball bouncing back and forth between two of the four walls. For simplicity, assume that it always moves two pixels per clock tick. If you follow the design recipe, your first focus is to form a data representation for what changes over time. A bouncing ball with constant speed has two always-changing properties: the location of the ball and the direction of its movement. The problem is that the "universe" teachpack keeps track of only one value, and so the question arises how one piece of data can represent two changing quantities of information.

Here is another scenario that raises the same question. Your cell phone is mostly a few million lines of software with some plastic attached to them. Among other things, it administrates your list of contacts. Before we even consider a representation for your ever-growing list of phone numbers and friends, let us ponder the question of how to represent the information about a single contact, assuming each contact comes with a name, a phone number, and an email address. Especially in a context where you have lots and lots of contacts, it is important to glue together all the information that belongs to one contact; otherwise the various pieces could get scrambled by accident.

Every programming language provides some mechanism for combining several pieces of data into one piece and for retrieving the constituent values. BSL is no exception; it offers structure type definitions as the fundamental mechanism for combining several values into one. In general, a structure type definition introduces many different functions into the available vocabulary, including one that creates structure instances—structures for short—and several others that extract values from instances. The chapter starts with the mechanics of structure types and then presents how structure types relate to the entire universe of BSL data. After presenting a design recipe for functions that process structures, the chapter ends with a look at structures in world programs.

5.1 Structures

You may have encountered Cartesian points in your mathematics courses in school. They are closely related though their y coordinates mean something slightly different than the y coordinates of posns. A First Look: A location on a world canvas is uniquely identified by two pieces of data: the distance from the left margin and the distance from the top margin. The first is called an x-coordinate and the second one is the y-coordinate.

DrRacket, which is basically a BSL program, represents such locations with posn structures. A posn structure combines two numbers. That is, a posn is a single value that contains two values. We can create a posn structure with the operation make-posn, which consumes two numbers and makes a posn. For example,
(make-posn 3 4)
(make-posn 8 6)
(make-posn 5 12)
are expressions that create posn structures. Each of these structures has the same status as a number or a Boolean or a string as far as computations are concerned. In particular, both primitive operations and functions can consume and produce structures.

Now consider designing a function that computes the distance of some location to the origin of the canvas:

The picture clarifies that “distance” means the length of the most direct path from the location to the top-left corner of the canvas—“as the crow flies.”

Here are the signature, header, and purpose statement:
; Posn -> Number
; to compute the distance of a-posn to the origin
(define (distance-to-0 a-posn)
  0)
The key is that distance-to-0 consumes a single value, some posn. It produces a single value, the distance of the location to the origin.

In order to make up examples, we need to know how to compute this distance. For points with 0 as one of the coordinates, the result is the other coordinate:
(check-expect (distance-to-0 (make-posn 0 5)) 5)
(check-expect (distance-to-0 (make-posn 7 0))  7)
For the general case, we could try to figure out the formula from some basic geometry, or we may recall the formula from your mathematics courses. As you know, this is domain knowledge that you might have but in case you don’t, we supply it; after all, this domain knowledge isn’t computer science. So, here is the distance of a point (x,y) from the origin as a formula:

Given this formula, we can easily make up some more functional examples from the above data examples:
(check-expect (distance-to-0 (make-posn 3 4)) 5)
(check-expect (distance-to-0 (make-posn 8 6)) 10)
(check-expect (distance-to-0 (make-posn 5 12)) 13)
Just in case you’re wondering, we rigged the examples so that the results would be easy to figure out. This isn’t the case for all posn structures.

Next we can turn our attention to the definition of the function. The examples imply that the design of distance-to-0 doesn’t need to distinguish between different situations. Still, we are stuck because distance-to-0 has a single parameter that represents the entire pixel but we need the two coordinates to compute the distance. Put differently, we know how to combine two numbers into a posn structure using make-posn but we don’t know how to extract these numbers from a posn structure.

An alternative terminology is “to access the fields of a record.” We prefer to think of structures as containers from which we can extract other values. BSL provides operations for extracting values from structures. For posn structures, there are two such operations, one per coordinate: posn-x and posn-y. The former operation extracts the x coordinate; the latter extracts the y coordinate.

To describe how posn-x, posn-y, and make-posn are related, we experiment with the functions in the interactions area of DrRacket:
> (posn-x (make-posn 7 0))

7

and
> (posn-y (make-posn 7 0))

0

The experiments are roughly analogous to the equations that govern addition and subtraction; adding z right after subtracting z from a number yields this original number.

Suppose we have the following in the definitions area of DrRacket:

(define a-posn (make-posn 7 0))

Then we can use these two operations in the interactions area:
> (posn-x a-posn)

7

> (posn-y a-posn)

0

Naturally, we can nest such expressions:
> (* (posn-x a-posn) 7)

49

> (+ (posn-y a-posn) 13)

13

In short, posn-x and posn-y are primitive functions that work on posn structures just like + and - work on numbers. Figure out what happens when you apply posn-x to a number.

At this point we know enough to complete the definition of distance-to-0. We know that the function’s a-posn parameter is a posn structure and that the structure contains two numbers. We can extract those with (posn-x a-posn) and (posn-y a-posn). Here is how we put this knowledge to our function outline:
(define (distance-to-0 a-posn)
  (... (posn-x a-posn) ...
   ... (posn-y a-posn) ...))
Using this outline and the examples, the rest is easy:
(define (distance-to-0 a-posn)
  (sqrt
    (+ (sqr (posn-x a-posn))
       (sqr (posn-y a-posn)))))

The function squares (posn-x a-posn) and (posn-y a-posn), which represent the x and y coordinates, sums up the results, and takes the square root. With DrRacket, we can also quickly check that our new function produces the proper results for our examples.

Exercise 54: Evaluate the following expressions:
by hand. Show all steps. Assume that sqr performs its computation in a single step. Check the results with DrRacket’s stepper.

Exercise 55: The Manhattan distance of a point to the origin considers a path that follows a rectangular grid, like those rigid blocks in Manhattan.

When placed in such a context, one cannot walk a straight path from a point to the origin; instead a person must follow the grid pattern. For a point such as (3,4), a local resident might say “go three blocks this way, turn right, and then go four blocks straight” to provide directions to get to the origin of the grid.

Design the function manhattan-distance, which measures the Manhattan distance of the given posn structure to the origin.

Defining a Structure: Unlike numbers or Boolean values, structures such as posn usually don’t come with a programming language. Only the mechanism to define structure types is provided; the rest is left up to the programmer. This is also true for BSL.

A structure type definition is, as the term indicates, a form of definition. Here is how the creator of DrRacket defined the posn structure type in BSL:

(define-struct posn (x y))

In general, a structure type definition has this shape:

(define-struct StructureName (FieldName ... FieldName))

The keyword define-struct signals to the reader (and to DrRacket) that a structure type is being defined. It is followed by the name of the structure, here posn. The third part of a structure type definition is a sequence of names enclosed in parentheses, e.g., (x y); these names are the fields of the structure type.

Unlike an ordinary function definition, a structure type definition defines many functions simultaneously. Specifically, it defines three kinds of primitive operations:
  • one constructor, a function that creates structure instances from as many values as there are fields; as mentioned, structure is short for structure instance. The phrase structure type is a generic name for the collection of all possible instances.

  • one selector per field, which extracts the value of the field from a structure instance; and

  • one structure predicate, which like ordinary predicates distinguishes instances from all other kinds of values.

The rest of the program can use these operations as if they were built-in primitives.

One curious aspect of structure type definitions is that it makes up names for the various new operations it creates. Specifically, for the name of the constructor, it prefixes the structure name with “make-” and for the names of the selectors it postfixes the structure name with the field names. Finally, the predicate is just the structure name with “?” added; we pronounce this question mark as “huh” when we read program fragments aloud.

This naming convention appears to be complicated but, with a little bit of practice, it is easy to remember. It also immediately explains the functions that come with posns: make-posn is the constructor, posn-x and posn-y are selectors. While we haven’t encountered posn? yet, we now know that it exists and we can confirm our ideas of how it works with experiments in the interactions area. Once again assume that we have

(define a-posn (make-posn 7 0))

in the definitions window. If posn? is a predicate that distinguishes posns from all other values, then we should expect that it yields false for numbers and true for a-posn:
> (posn? a-posn)

true

> (posn? 42)

false

> (posn? true)

false

> (posn? (make-posn 3 4))

true

Naturally, for Boolean values it also produces false, and when applied to other instances of posn, it returns true.

Enough with posns for a while, let us look at a struct definition that we might use to keep track of entries on a contact list like the one in your cell phone:

(define-struct entry (name phone email))

Here are the functions that this definition introduces:
  • the constructor make-entry, which consumes three values and creates an instance of entry;

  • the selectors entry-name, entry-phone, and entry-email, which all consume one value—an instance of entryand produces a value; and

  • the predicate entry?.

So each entry combines three values or, equivalently, each entry has three fields. Thus, the expression

(make-entry "Sarah Lee" "666-7771" "lee@classy-university.edu")

creates an entry structure with "Sarah Lee" in the name field, "666-7771" in the phone field, and "lee@classy-university.edu" in the email field.

One way to think of an structure instance is as a lock box with as many compartments as there are fields:

The box itself carries a label, identifying it as an instance of a specific structure type definition. Each compartment, too, is labeled. We italicize all labels in such pictures. A different structure instance, say,

(make-entry "Tara Harper" "666-7770" "harper@small-college.edu")

is illustrated with a similar box picture, though the content of the compartments differs:

In the context of this imagery, a selector is like a labeled key that opens a compartment with the same label but only for appropriately labeled boxes.

Let us make this concrete again. Enter these definitions
(define pl
  (make-entry "Sarah Lee" "666-7771" "lee@classy-university.edu"))
 
(define bh
  (make-entry "Tara Harper" "666-7770" "harper@small-college.edu"))
into the definitions area and then experiment with expressions like these:
> (entry-name pl)

"Sarah Lee"

> (entry-name bh)

"Tara Harper"

> (entry-name (make-posn 42 5))

entry-name: expects an entry, given (posn 42 5)

When entry-name is applied to the above instance of entry, it extracts the value in the name field; otherwise it signals an error. The other selectors work just fine, too:
> (entry-email pl)

"lee@classy-university.edu"

> (entry-phone pl)

"666-7771"

Exercise 56: Write down the names of the functions (constructors, selectors, and predicates) that the following structure type definitions define:
Make sensible guesses as to what kind of values go with which fields and create at least one instance per structure type definition. Then draw box representations for each of them.

Nested Structures: The speed of a moving ball is the number of pixels it moves per clock tick. Its velocity is the speed plus the direction in which it moves. When a ball moves along a horizontal or vertical line, an integer (or a real number) is a perfectly adequate data representation for a its velocity:
  • A positive number means the ball moves in one direction.

  • A negative number means it moves in the opposite direction.

We can use this domain knowledge to formulate a structure type definition to represent the bouncing ball from the example mentioned at the beginning of this chapter:

(define-struct ball (location velocity))

For a ball moving up and down, both fields are going to contain numbers, with the first one telling us how many pixels the ball is from the top of the canvas and the second one the direction and speed at which it is moving. Thus, (make-ball 10 -3) could represent a ball that is 10 pixels from the top and moves 3 pixels per clock tick toward the top. Remember that for computer scientists, the positive direction of the y axis is down.

Exercise 57: There are other ways to represent bouncing balls. Here is one:
(define SPEED 3)
 
(define-struct balld (location direction))
 
(make-balld 10 'up)
Interpret this program fragment in terms of a “world scenario” and then create other instances of balld.

Objects in games and simulations don’t always move along vertical or horizontal lines. They move in some “oblique” manner across the screen. Describing both the location and the velocity of a ball moving across a 2-dimensional world canvas demands two numbers: one per direction. For the location part, the two numbers represent the x and y coordinates. Velocity describes the changes in the x and y direction; in other words, these “change numbers” must be added to the respective coordinates to find out where the object is next.

Let us use posn structures to represent locations and let us introduce a vel structure type for velocities:Those of you who know complex numbers may wish to contemplate a data representation that uses complex numbers for both location and velocity. BSL supports those, too.

(define-struct vel (deltax deltay))

In case you are wondering, the word “delta” is commonly used to speak of change when it comes to simulations of physical activities.

Now we can use instances of ball to represent balls that move in straight lines but not necessarily along only horizontal or vertical lines:

(define ball1 (make-ball (make-posn 30 40) (make-vel -10 5)))

One way to interpret this instance is to think of a ball that is 30 pixels from the left, 40 pixels from the top. It moves 10 pixels toward the left per clock tick, because subtracting 10 pixels from the x coordinate brings it closer to the left. As for the vertical direction the ball drops at 5 pixels per clock tick, because adding positive numbers to a y coordinate increases the distance from the top.

Since structures instances are values, it is not surprising that for ball1 we can create a structure containing a structure. Pictorially, it just means a box may contain boxes:

In this case, the outer box contains two boxes, one per field. In general, structure instances—and thus boxes—can be nested arbitrarily deep. We are going to study examples of this kind.

Working with values such as ball1 is just like working with plain structures:
> (ball-velocity ball1)

(vel ...)

> (vel-deltax (ball-velocity ball1))

-10

> (posn-x (ball-velocity ball1))

posn-x: expects a posn, given (vel ...)

Applying ball-velocity to ball1 extracts the value of the velocity field, which is an instance of vel. As with numeric arithmetic, we can also evaluate nested expressions, for example, (vel-deltax (ball-velocity ball1)). Since the inner expression extracts the velocity from ball1, the outer expression extracts the value of the deltax field, which in this case is -10. Finally, using posn-x on a vel signals an error.

Exercise 58: An alternative to the nested data representation of balls is a flat representation:

(define-struct ballf (x y deltax deltay))

Create an instance of ballf that is interpreted in the same way as ball1.

Let us briefly look at the example of contact lists. Many cell phones support contact lists that allow three phone numbers per name: one for a home line, one for the office, and one for a cell phone number. For phone numbers, we wish to include both the area code and the local number. Since this nests the information, the data representation needs to be similar so that it is easy to go back and forth:
(define-struct centry (name home office cell))
 
(define-struct phone (area number))
 
(make-centry "Shriram Fisler"
             (make-phone 207 "363-2421")
             (make-phone 101 "776-1099")
             (make-phone 208 "112-9981"))
The intention here is that an entry on a contact list has four fields: a name and three phone records. The latter are represented with instance of phone, which separates the area code from the local phone number.

In sum, nesting information is natural. The best way to represent such information with data is to mirror the nesting with nested structure instances. Doing so makes it easy to interpret the data in the application domain of the program, and it is also straightforward to go from examples of information to data. Of course, it is really the task of data definitions to facilitates this task of going back and forth between information and data. We have not written data definitions so far, but we are going to catch up with this in the next section.

Data in Structures: Until now, data definitions have been rather boring. We have either used built-in collections of data to represent information (numbers, Boolean values, strings) or specified an itemization (interval or enumeration), which restricts an existing collection. The introduction of structures adds complexity to our data definitions.

The purpose of a data definition for structures is to describe what kind of data each field may contain. For some structure type definitions doing so is easy and obvious:
(define-struct posn (x y))
; A Posn is a structure: (make-posn Number Number)
; interp. the number of pixels from left and from top
It doesn’t make any sense to use other kinds of data to create a posn. Similarly, all instances of entry, our structure type definition for entries on a contact lists, are clearly supposed to be strings according to our usage in the preceding section:
(define-struct entry (name phone email))
; An Entry is a structure: (make-entry String String String)
; interp. name, 7-digit phone number, and email address of a contact
In both cases, it is also easy to describe how a reader is to interpret instances of these structures in the application domain.

In contrast, the ball structure type definition allows two distinct interpretations:
(define-struct ball (location velocity))
; A Ball-1d is a structure:  (make-ball Number Number)
; interp. 1: the position from top and the velocity
; interp. 2: the position from left and the velocity
Whichever one we use in a program, we must stick to it consistently. As the preceding section shows, however, it is also possible to use ball structures in an entirely different manner:
; A Ball-2d is a structure: (make-ball Posn Vel)
; interp. 2-dimensional position with a 2-dimensional velocity
 
(define-struct vel (deltax deltay))
; A Vel is a structure: (make-vel Number Number)
; interp. velocity in number of pixels per clock tick for each direction
To do so, we use names for the two data collections that are not as closely related to the structure type definition as usual. They are just names, however, and as long as we use the names consistently, we can call these data collections whatever we want. Here we introduce a second collection of data (Ball-2d), distinct from the first one (Ball-1d), to describe data representations for balls that move in straight lines across a world canvas. In short, it is possible to use one and the same structure type in two different ways. Of course, within one program stick to one and only one use; otherwise you are setting yourself up for problems.

Note also that Ball-2d refers to another one of our data definitions, namely, the one for Vel. While all other data definitions have thus far referred to built-in data collections (numbers, Boolean values, strings), it is perfectly acceptable and indeed common that one of your data definition refers to another. Later, when you design programs, such connections provide some guidance for the organization of programs.

The question of what data definitions really mean and is the topic of the next section.

Of course, at this point, it should really raise the question of what data definitions really mean and this is what the next section deals with.

Exercise 59: Formulate a data definition for the above phone structure type definition that accommodates the given examples.

Next formulate a data definition for phone numbers using this structure type definition:

(define-struct phone# (area switch phone))

Historically, the first three digits make up the area code, the next three the code for the phone switch (exchange) of your neighborhood, and the last four represent the phone with respect to the neighborhood. Describe the content of the three fields as precisely as possible with intervals.

5.2 Programming With Structures

Structures defines distance-to-0, a function that consumed a structure and produced a number. Now we look at a few more such functions before formulating some general principles in the next section.

Sample Problem: Your team is designing a program that keeps track of the last mouse click on a 100 x 100 canvas. Together you chose Posn as the data representation for the x and y coordinates of the mouse click. Design a function that consumes a mouse click and a 100 x 100 scene and adds a circular red spot to the scene where the click occurred.

Since a mouse click is represented with a Posn, the sample problem practically dictates the signature:
; visual constants
(define MTS (empty-scene 100 100))
(define DOT (circle 3 "solid" "red"))
 
; Posn Image -> Image
; adds a red spot to s at p
(define (scene+dot p s)
  s)
The code fragment includes two definitions for graphical constants as inspired by the problem statement, though the radius for the red spot is arbitrary. And the preliminary definition comes with a purpose statement that, as suggested in Designing Functions, uses the function’s parameters to express what the function computes.

Next we work out a couple of examples:
(check-expect (scene+dot (make-posn 10 20) MTS)
              (place-image DOT 10 20 MTS))
(check-expect (scene+dot (make-posn 88 73) (rectangle 100 100 "solid" "blue"))
              (place-image DOT 88 73 (rectangle 100 100 "solid" "blue")))
The second example clarifies that scene+dot does not just add a dot to an empty image; it adds it to the given scene.

Given that the function consumes a Posn, we know that the function can extract the values of the x and y fields:
(define (scene+dot p s)
  (... (posn-x p) ... (posn-y p) ...))
Once we see these additional pieces in the body of the function, the rest of the definition is straightforward. Using place-image, the function puts DOT into s at the coordinates contained in p:
(define (scene+dot p s)
  (place-image DOT (posn-x p) (posn-y p) s))

; visual constants
(define MTS (empty-scene 100 100))
(define DOT (circle 3 "solid" "red"))
 
; Posn Image -> Image
; adds a red spot to s at p
 
(check-expect (scene+dot (make-posn 10 20) MTS)
              (place-image DOT 10 20 MTS))
(check-expect (scene+dot (make-posn 88 73) MTS)
              (place-image DOT 88 73 MTS))
 
(define (scene+dot p s)
  (place-image DOT (posn-x p) (posn-y p) s))

Figure 17: Adding a dot to a scene

A function may produce structures, not just consume then. Indeed, a common kind of function consumes and produces structures:

Sample Problem: Design the function x+ and y-. The former consumes a Posn and increases the x coordinate by 3; the latter consumes a Posn and decreases the y coordinate by 3.

It is easy to imagine that this kind of problem comes up as you are creating a game that moves some object along horizontal and vertical lines.

We can copy the first few steps of the design of scene+dot:
; Posn -> Posn
; increase the x coordinate of p by 3
(check-expect (x+ (make-posn 10 0)) (make-posn 13 0))
(define (x+ p)
  (... (posn-x p) ... (posn-y p) ...))
The signature, the purpose, and the example all come out of the problem statement. Instead of a header—a function with a default result—our sketch contains the two selector expressions for Posns. After all, the information for the result must come from the inputs, and the input is a structure that contains two values.

Finishing the design is a small step now. Since the desired result is a Posn and since the latter are created with make-posn, we use it to combine the pieces in the function skeleton:
; Posn -> Posn
; increase the x coordinate of p by 3
(check-expect (x+ (make-posn 10 0)) (make-posn 13 0))
(define (x+ p)
  (make-posn (+ (posn-x p) 3) (posn-y p)))
Of course, the function adds 3 to the x coordinate. For practice, we leave the design of y- to you.

Here is a variant of the problem:

Sample Problem: Design the function posn-up-x, which consumes a Posn p and a Number n. It produces a Posn like p with n in the x field.

Functions such as posn-up-x are called updaters, and they are extremely useful as we shall see.

We define the function without further ado:
; Posn Number -> Posn
; update p's x coordinate with n
(check-expect (posn-up-x (make-posn -10 22) 100)
              (make-posn 100 22))
(define (posn-up-x p n)
  (make-posn n (posn-y p)))
A neat observation is that we can define x+ using posn-up-x:
; Posn -> Posn
; increase the x coordinate of p by 3
; version2
(check-expect (x+ (make-posn 10 0)) (make-posn 13 0))
(define (x+ p)
  (posn-up-x p (+ (posn-x p) 3)))
Do the same for y-.

Many functions deal with nested structures. We illustrate this point with another small excerpt from a world program.

When you are given a location and a velocity (change in location per time unit), you can find out the location after one time unit by adding the velocity to the location.

Sample Problem: Your team is designing a game program that keeps track of an object that moves across the canvas at changing speed. The chosen data representation is a structure that contains two Posns:
(define-struct velocity (dx dy))
; A Velocity is a structure: (make-vel Number Number)
; interp. (make-vel a b) means that the object moves a steps
; along the horizontal and b steps along the vertical per tick
 
(define-struct ufo (loc vel))
; A UFO is a structure: (make-ufo Posn Velocity)
; interp. (make-ufo p v) is at location p
; moving at velocity v
Design the function move1, which moves some given UFO for one tick of the clock.

Let us start with some examples that explore the data definitions a bit:
(define v1 (make-velocity 8 -3))
(define v2 (make-velocity -5 -3))
(define p1 (make-posn 22 80))
(define p2 (make-posn 30 77))
(define u1 (make-ufo p1 v1))
(define u2 (make-ufo p1 v2))
(define u3 (make-ufo p2 v1))
(define u4 (make-ufo p2 v2))
The first four are elements of Velocity and Posn, respectively. The last four combine the first four in all possible combinations. Note that unlike function definitions, these constant definitions must be ordered in this manner.

Next we write down a signature, a purpose, some examples and a function header:
; UFO -> UFO
; move the ufo u, i.e., compute its new location in one clock
; tick from now and leave the velocity as is
 
(check-expect (ufo-move u1) u3)
(check-expect (ufo-move u2) (make-ufo (make-posn 17 77) v2))
 
(define (ufo-move u) u)
For the function examples, we use of the data examples and our domain knowledge of positions and velocities. Specifically, we know that a vehicle that is moving north at 60 miles per hour and west at 10 miles per hour is going to end up 60 miles north from here and 10 miles west after one hour of driving. (Calculate where it is going to be after two hours.)

As before, we decide that a function that consumes a structure instance can (and probably must) extract information from the structure to compute its result. So once again we add selector expressions to the function definition:
(define (ufo-move u)
  (... (ufo-loc u) ; a Posn
   ... (ufo-vel u) ; a Velocity
   ...))
For good measure we include comments that explain what kind of value each selector expression extracts. Naturally this knowledge comes from the data definition for UFO.

The addition of the selector expressions and their comments immediately raises the question whether we need to refine this sketch even more. After all, the elements of Posn and Velocity are also structures, and we could extract values from them in turn:
... (posn-x (ufo-loc u)) ... (posn-y (ufo-loc u)) ...
... (velocity-dx (ufo-vel u)) ... (velocity-dy (ufo-vel u)) ...
Doing so makes the sketch look quite complex, however. Instead we contemplate how we should combine the given Posn and the given Velocity in order to obtain the next location of the UFO.

Our domain knowledge says that we should “add” the two together, where “adding” can’t mean the operation we usually apply to numbers. So let us imagine that we have a function for adding a Velocity to a Posn:
; Posn Velocity -> Posn
; add v to p
(define (posn+ p v) p)
Writing down the signature, purpose, and header like this is a legitimate way of programming. It is called “making a wish” and is a part of “making a wish list” as described in From Functions to Programs.

The key is to make wishes in such a way that we can complete the function that we are working on. In this manner, we can split difficult programming tasks into different tasks, a technique that helps us solve problems in reasonably small steps. For the sample problem, we get a complete definition for move-ufo:
(define (ufo-move u)
  (make-ufo
    (posn+ (ufo-loc u) (ufo-vel u))
    (ufo-vel u)))
Because posn+ is a complete—even though we still have to work on posn+we can even click RUN and check that DrRacket doesn’t complain about BSL grammar.

In geometry courses, the operation corresponding to posn+ is called a translation. Now it is time to focus on posn+. We have completed the first two steps of the design (data definitions, signature/purpose/header), so we must create examples. One easy way to create functional examples for a “wish” is to use the examples for the original function and to turn them into examples for the new function:
(check-expect (posn+ p1 v1) p2)
(check-expect (posn+ p1 v2) (make-posn 17 77))
For this problem, we know that (ufo-move (make-ufo p1 v1)) is to produce p2. At the same time, we know that ufo-move applies posn+ to p1 and v1, implying that posn+ must produce p2 for these inputs. Stop! Check our manual calculations to insure you are following what we are doing.

We are now able to add selector expressions to our design sketch:
(define (posn+ p v)
  (... (posn-x p) ... (posn-y p) ...
   ... (velocity-dx v) ... (velocity-dy v) ...))
Because posn+ consumes instances of Posn and Velocity and because each piece of data is an instance of a two-field structure, we get four expressions. In contrast to the nested selector expressions from above, these are simple applications of a selector to a parameter.

If we remind ourselves what these four expressions represent, or if we recall how we computed the desired results from the two structures, our completion of the definition of posn+ is straightforward:
(define (posn+ p v)
  (make-posn (+ (posn-x p) (velocity-dx v))
             (+ (posn-y p) (velocity-dy v))))
The first step is to add the velocity in the x direction to the x coordinate and the velocity in the y direction to the y coordinate. Doing so yields two expressions that compute the two new coordinates. With make-posn we can combine the two coordinates into a single Posn again, as desired.

Try it out. Enter these definitions and their test cases into the definitions area of DrRacket and make sure they work. It is the first time that we made a “wish” and you need to make sure you understand how the two functions work together.

5.3 The Universe Of Data

In mathematics such collections are called sets. Every language comes with a universe of data. This data represents information from and about the external world; it is what programs manipulate.This universe of data is a collection that consists of collections of built-in data but also of classes of data that programs create.

Figure 18: The universe of data

Figure 18 shows one way to imagine the universe of BSL. Since there are an infinitely many numbers and strings, the collection of all data is infinite. We indicate “infinity” in the figure with “...” but a real definition would have to avoid this imprecision.

Neither programs nor individual functions in programs deal with the entire universe of data. It is the purpose of a data definition to describe parts of this universe and to name these parts so that we can refer to them concisely. Put differently, a named data definition is a description of a collection of data, and that name is usable in other data definitions and in function signatures. In a function signature, the name specifies what data a function will deal with and, implicitly, which part of the universe of data it won’t deal with.

Figure 19: The meaning of a data definition

Practically the data definition of preceding chapters restrict built-in collections of data. They do so via an explicit or implicit itemization of all included values. For example, the region shaded with gray stripes in figure 19 depicts the following data definition:
; A BS is one of:
;  "hello",
;  "world", or
;  pi.
While a data definition picking out two strings and one number is silly, note the stylized mix of English and BSL that is used. Its meaning is precise and unambiguous, clarifying exactly which elements belong to BS and which don’t.

The introduction of structure types creates an entirely new picture. When a programmer defines a structure type, the universe expands with all possible structure instances. For example, the addition of a posn structure type means that instances of posn with all possible values in the two fields appear. The middle bubble in figure 20 depicts the addition of those values, showing things such as (make-posn "hello" 0) and even (make-posn (make-posn 0 1) 2). And yes, even though some of these instances of posn make no sense to us, it is indeed possible to construct all of them in a BSL program.

Figure 20: Adding structure to a universe

The addition of another structure type definition mixes and matches everything, too. Say we add a structure type definition for ball, also with two fields. As the third bubble in figure 20 shows, this addition creates instances of ball that contain numbers, posns, and so on as well as instances of posn that contain instances of ball. Try it out in DrRacket! Add

(define-struct ball (location velocity))

to the definitions area, hit RUN, and create some structure instances like this:
> (make-posn (make-ball "hello" 1) false)

(posn (ball "hello" 1) false)

> (make-posn (make-ball (make-ball (make-posn 1 2) 3) 4) 5)

(posn (ball (ball (posn 1 2) 3) 4) 5)

Of course, there are no limits on how far you can nest these structure, as the second expression illustrates. Play with structures and learn.

Figure 21: Imposing a data definition on structures

In mathematics and physics courses, you encounter one such kind of data: Cartesian coordinates, which also combine two numbers into one item. As far as the pragmatics of data definitions is concerned, a data definition for structure types describes large collections of data via combinations of existing data definitions with instances. When we write

; Posn is (make-posn Number Number)

we are describing an infinite number of possible instances of posn. Like above, the data definitions use combinations of natural language, data collections defined elsewhere and data constructors. Nothing else should show up in a data definition at the moment.

A data definition for structures specifies a new collection of data made up of those instances to be used by our functions. For example, the data definition for Posns identifies the region shaded with gray stripes in figure 21, which includes all those posns whose two fields contain numbers. At the same time, it is perfectly possible to construct an instance of posn that doesn’t satisfy the requirement that both fields contain numbers:

(make-posn (make-posn 1 1) "hello")

this structure contains a posn in the x field and a string in the y field.

Exercise 60: Formulate data definitions for the following structure type definitions:
Make sensible assumptions as to what kind of values go into each field.

Exercise 61: Provide a structure type definition and a data definition for representing points in time since midnight. A point in time consists of three numbers: hours, minutes, and seconds.

Exercise 62: Provide a structure type definition and a data definition for representing lower-case three-letter words. A word consists of letters, represented with the one-letter strings "a" through "z".

Programmers not only write data definitions, they also read them in order to understand programs, to eliminate errors, to expand the kind of data it can deal with, and so on. We read a data definition to understand how to create data that belongs to the designated collection and to determine whether some piece of data belongs to some specified class.

Since data definitions play such a central and important role in the design process, it is often best to illustrate data definitions with examples just like we illustrate the behavior of functions with examples. And indeed, creating data examples from a data definition is straightforward:
  • for a built-in collection of data (number, string, Boolean, images), choose your favorite examples;

    Note: on occasion, people use descriptive names to qualify built-in data collections, such as NegativeNumber or OneLetterString. They are no replacement for a well-written data definition.

  • for an enumeration, use several of the items of the enumeration;

  • for intervals, use the end points (if they are included) and at least one interior point;

  • for itemizations, deal with each part separately; and

  • for data definitions for structures, follow the natural language description, that is, use the constructor and pick an example from the data collection named for each field.

That’s all there is to constructing examples from data definitions for most of this book, though data definitions are going to become much more complex than what you have seen so far.

Exercise 63: Create examples for the following data definitions:
  • ; A Color is one of:
    ;  "white"
    ;  "yellow"
    ;  "orange"
    ;  "green"
    ;  "red"
    ;  "blue"
    ;  "black"

    Note: DrRacket recognizes many more strings as colors.

  • ; H (a happiness scale value) is a number in [0,100], i.e.,
    ; a number between 0 and 100
  • (define-struct person (fstname lstname male?))
    ; Person is (make-person String String Boolean)
    Is it a good idea to use a field name that looks like the name of a predicate?

  • (define-struct dog (owner name age happiness))
    ; Dog is (make-dog Person String PositiveInteger H)

    Add an interpretation to this data definition, too.

  • ; Weapon is one of:
    ;  false
    ;  Posn
    ; interp. false means the missile hasn't been fired yet;
    ;   an instance of Posn means the missile is in flight
The last definition is an unusual itemization, using both built-in data and a structure type definition. The next chapter deals with this kind of data definition in depth.

5.4 Designing With Structures

The introduction of structure types reinforces that the process of creating functions has (at least) six steps, something already discussed in Designing With Itemizations. It no longer suffices to rely on built-in data collections to represent information; it is now clear that programmers must create data definitions for all but the simplest problems.

  1. When a problem calls for the representation of pieces of information that belong together or describe a natural whole, we need a structure type definition. It requires as many fields as there are relevant properties. An instance of this structure type corresponds to the whole, and the values in the fields to its attributes.

    A data definition for a structure type introduces a name for the collection of instances that we consider legitimate. Furthermore it must describe which kind of data goes with which field. Use only names of built-in data collections or previously defined data definitions.

    In the end, we (and others) must be able to use the data definition to create sample structure instances. Otherwise, something is wrong with our data definition. To ensure that we can create instances, our data definitions should come with one data examples.

  2. We still need a signature, a purpose statement, and a function header but there is nothing new here.

  3. We use the examples from the first step to create functional examples. For each field associated with intervals or enumerations, we make sure to pick end points and intermediate points to create functional examples.

    For example, imagine designing a function that consumes a structure that combines TrafficLight with Number. Since TrafficLight contains three elements, we make sure to use all three in combination with numbers as input samples.

  4. A function that consumes structures usually—though not always—extracts the values from the various fields in the structure. To remind ourselves of this possibility, we write templates for such functions containing a selector for each field. Furthermore, we may want to write down next to each selector expression what kind of data it extracts from the given structure; such information is found in the data definition.

    We do not, however, create selector expressions if a field value is itself a structure. In general, it is better to make a wish for an auxiliary function that processes the extracted field values.

  5. We use the selector expressions from the template when we finally define the function, keeping in mind that we may not need (some of) them.

  6. Test. Test as soon as the function header is written. Test until all expressions have been covered. And test again when you make changes.

Exercise 64: Create templates for functions that consume instances of the following structure types:

Exercise 65: Design the function time->seconds, which consumes instances of the time structures developed in exercise 61 and produces the number of seconds that have passed since midnight. For example, if you are representing 12 hours, 30 minutes, and 2 seconds with one of these structures and if you then apply time->seconds to this instance, the correct result is 45002.

Exercise 66: Design the function different. It consumes two (representations of) three-letter words and creates a word from the differences. For each position in the words, it uses the letter from the second word if the two are the same; otherwise it uses the marker "*". Note: The problem statement mentions two different tasks: one concerning words and one concerning letters. This suggests that you design two functions.

5.5 Structure In the World

When a world program must track two different and independent pieces of information, we must use a collection of structures to represent the world state data. One field keeps track of one piece of information and the other field the second piece of information. Naturally, if the domain world contains more than two independent pieces of information, the structure type definition must specify as many fields as there are distinct pieces of information.

Consider a space invader game, in which a UFO descends along a straight vertical line and some tank moves horizontally at the bottom of a scene. If both objects move at known constant speeds, all that’s needed to describe these two objects is one piece of information per object: the y coordinate for the UFO and the x coordinate for the tank. Putting those together requires a structure with two fields:

(define-struct space-game (ufo tank))

We leave it to you to formulate an adequate data definition for this structure type definition including an interpretation. Ponder the hyphen in the name of the structure. BSL really allows the use of all kinds of characters in the names of variables, functions, structures, and field names. What are the selector names for this structure, the name of the predicate?

Every time we say piece of information, we don’t necessarily mean a single number or a single word. A piece of information may itself combine several components. Thus, creating a data representation for world states that consist of two (or more) complex pieces of information leads to nested structures.

Let us return to our imaginary space invader game for a moment. Clearly, a UFO that descends only along a vertical line is boring. To turn this idea into an interesting game where the tank attacks the UFO with some weapon, the UFO must be able to descend in non-trivial lines, perhaps jumping randomly. An implementation of this idea means that we need two coordinates to describe the location of the UFO, so that our revised data definition for the space game becomes:
; SpaceGame is (make-space-game Posn Number).
; interp. (make-space-game (make-posn ux uy) tx) means that the
; UFO is currently at (ux,uy) and the tank's x coordinate is tx

Understanding what kind of data representations is needed for world programs takes practice. To this end, the following two sections introduce several reasonably complex problem statements. We recommend you solve them before moving on to the kind of games that you might like to design on your own.

5.6 A Graphical Editor

One step of the programming process is to create a text document. To program in BSL, you open DrRacket, type on the keyboard, and watch text appear. Pressing the left arrow on the keyboard moves the cursor to the left; pressing the backspace (or delete) key erases a single letter to the left of the cursor—if there is a letter to start with.

This process is called “editing” though its precise name should be “text editing of programs” because we will use “editing” for a more demanding task than typing on a keyboard. When you write and revise other kinds of documents, say, an English assignment, you are likely to use other software applications, called word processors, though computer scientists dub all of them editor or even graphical editor.

You are now in a position to design a world program that acts as a one-line editor for plain text. Editing here includes entering letters and somehow changing the already existing text, including the deletion and the insertion of letters. This implies some notion of position within the text. People call this position a cursor; most graphical editors display it in such a way that it can easily be spotted.

Take a look at the following editor configuration:

The red line indicates the cursor. Can you imagine how this configuration came about? Someone entered the text “helloworld” and then hit the left arrow key five times, causing the cursor to move from the end of the text to its current position. Pressing the space bar now causes the editor to change its display as follows:

In short, it shows the phrase “hello world” with a cursor between the newly inserted space and the “w” from the original text.

Given this background, an editor world program must keep track of two pieces of information:
  1. the text entered so far

  2. the current location of the cursor.

And this suggests a structure type with two fields.

We can imagine several different ways of going from the information to data and back. For example, one field in the structure may contain the entire text entered and the other the number of characters between the first character (counting from the left) and the cursor. Another data representation is to use two strings in the two fields: the part of the text to the left of the cursor and the part of the text to its right. Here is our preferred approach to representing the state of an editor:
(define-struct editor (pre post))
; Editor = (make-editor String String)
; interp. (make-editor s t) means the text in the editor is
; (string-append s t) with the cursor displayed between s and t
Solve the next few exercises based on this data representation.

Exercise 67: Design the function render, which consumes an Editor and produces an image.

The purpose of the function is to render the text within an empty scene of 200 x 20 pixels. For the cursor, use a 1 x 20 red rectangle and for the strings, black text of size 11.

Develop the image for a sample string in DrRacket’s interaction area. We started with this expression:
(overlay/align "left" "center"
               (text "hello world" 11 'black)
               (empty-scene 200 20))
When you are happy with the looks of the image, use the expression as a test and as a guide to the design of render.

Exercise 68: Design edit. The function consumes two inputs, an editor ed and a KeyEvent ke, and it produces another editor. Its task is to add a single-character KeyEvent ke to the end of the pre field of ed, unless ke denotes the backspace ("\b") key. In that case, it deletes the character immediately to the left of the cursor (if there are any). The function ignores the tab "\t" and rubout "\u007F" keys.

The function pays attention to only two KeyEvents longer than one letter: "left" and "right". The left arrow moves the cursor one character to the left (if any), and the right arrow moves it one character to the right (if any). All other such KeyEvents are ignored.

Develop a good number of examples for edit, paying attention to special cases. When we solved this exercise, we created 20 examples and turned all of them into tests.

Hint: Think of this function as consuming KeyEvents, a collection that is specified as an enumeration. It uses auxiliary functions to deal with the Editor structure. Keep a wish list handy; you will need to design additional functions for most of these auxiliary functions, such as string-first, string-rest, string-last, and string-remove-last. If you haven’t done so, solve the exercises in Functions.

Exercise 69: Define the function run. It consumes a string—the pre field of an editor—and then launches an interactive editor, using render and edit from the preceding two exercises for the to-draw and on-key clauses.

Exercise 70: Notice that if you type a lot, your editor program does not display all of the text. Instead the text is cut off at the right margin. Modify your function edit from exercise 68 so that it ignores a keystroke if adding it to the end of the pre field would mean the rendered text is too wide for your canvas.

Exercise 71: Develop a data representation based on our first idea, using a string and an index. Then solve exercises exercise 67 through exercise 70 again.

Follow the design recipe.

Note: The exercise is a first study of making design choices. It shows that the very first design choice concerns the data representation. Making the right choice requires planning ahead and weighing the complexity of each. Of course, getting good at this is a question of gaining experience.

And again, if you haven’t done so, solve the exercises in Functions.

5.7 More Virtual Pets

In this section we continue our virtual zoo project from Virtual Pet Worlds. Specifically, the goal of the exercise is to combine the cat world program with the program for managing its happiness gauge. When the combined program runs, you see the cat walking across the canvas and, with each step, its happiness goes down. The only way to make the cat happy is to feed it (down arrow) or to pet it (up arrow). Finally, the goal of the last exercise is create another virtual, happy pet.

Exercise 72: Define a structure type that keeps track of the cat’s x coordinate and its happiness. Then formulate a data definition for cats, dubbed VCat, including an interpretation with respect to a combined world.

Exercise 73: Design a “happy cat” world program that presents an animated cat and that manages and displays its happiness level. The program must (1) use the structure type from the preceding exercise and (2) reuse the functions from the world programs in Virtual Pet Worlds.

Exercise 74: Modify your program of the preceding exercises so that it stops when the cat’s happiness ever falls to 0.

Exercise 75: Extend your structure type definition and data definition to include a direction field. Adjust your program so that the cat moves in the specified direction. The program should move the cat in the current direction, and it should turn the cat around when it reaches either end of the scene.

(define cham )

This drawing of a chameleon is a transparent image. To insert it into DrRacket, insert it with the “Insert Image” menu item. Using this instruction preserves the transparency of the drawing’s pixels.

When a partly transparent image is combined with a colored shape, say a rectangle, the image takes on the underlying color. In the chameleon drawing, it is actually the inside of the animal that is transparent; the area outside is solid white. Try out this expression in your DrRacket, using the "2hdtp/image" teachpack:

(overlay
  cham
  (rectangle (image-width cham) (image-height cham) "solid" "red"))

Exercise 76: Design a world program that has the chameleon continuously walking across the screen, from left to right. When it reaches the right end of the screen, it disappears and immediately reappears on the left. Like the cat, the chameleon gets hungry from all the walking and, as time passes by, this hunger expresses itself as unhappiness.

For managing the chameleon’s happiness gauge, you may reuse the happiness gauge from the virtual cat. To make the chameleon happy, you feed it (down arrow, two points only); petting isn’t allowed. Of course, like all chameleon’s, ours can change color, too: "r" turns it red, "b" blue, and "g" green. Add the chameleon world program to the virtual cat game and reuse functions from the latter when possible.

Start with a data definition, VCham, for representing chameleons.

Exercise 77: Copy your solution to exercise 76 and modify the copy so that the chameleon walks across a tricolor background. Our solution uses these colors: Have some Italian pizza when you’ve solved the problem.
(define BACKGROUND
  (beside (empty-scene WIDTH HEIGHT "green")
          (empty-scene WIDTH HEIGHT "white")
          (empty-scene WIDTH HEIGHT "red")))
but you may use any colors you wish. Observe how the chameleon changes colors to blend in as it crosses the border between two colors.

When you watch the animation carefully, you will see the chameleon riding on a white rectangle. If you know how to use image editing software, modify the picture so that the white rectangle is invisible. Then the chameleon will really blend in!

6 Itemizations and Structures

In the preceding two chapters, we have encountered two new kinds of data definitions. Those that employ itemization (enumeration and intervals) are used to create small collections from large ones. Those that use structures combine multiple collections. Since this book keeps emphasizing that the development of data representations is the starting point for proper program design, it cannot surprise you that programmers frequently want to itemize data definitions that involve structures or use structures to combine itemized data.

Recall the imaginary space invader game from Structure In the World in the preceding chapter. Thus far, it involves one UFO, descending from space, and one tank on the ground, moving horizontally. Our data representation uses a structure with two fields: one for the data representation of the UFO and another one for the data representation of the tank. Naturally players will want a tank that can fire off a missile. All of a sudden, we can think of a second kind of state that contains three independently moving objects: the UFO, the tank, and the missile. Thus we have two distinct structures: one for representing two independently moving objects and another one for the third. Since a world state may now be one of these two structures, it is natural to use an itemization to describe all possible states:
  1. the state of the world is a structure with two fields, or

  2. the state of the world is a structure with three fields.

No need to worry, the next chapter is about firing as many missiles as you want, without reloading. As far as our domain is concerned—the actual game—the first kind of state represents the time before the tank has launched its sole missile and the second one after the missile has been fired.

This chapter introduces the basic idea of itemizing data definitions that involve structures. Because we have all the other ingredients we need, we start straight with itemizing structures, After that, we discuss some examples, including world programs that benefit from our new power. The last section is about errors in programming.

6.1 Designing With Itemizations, Again

Let us start with a refined problem statement for our space invader game from Programming With Structures.

Sample Problem: Design a game program using the "universe" teachpack for playing a simple space invader game. The player is in control of a tank (a small rectangle) that must defend our planet (the bottom of the canvas) from a UFO (see Intervals for one possibility) that descends from the top of the canvas to the bottom. In order to stop the UFO from landing, the player may fire a single missile (a triangle smaller than the tank) by hitting the space bar. In response, the missile emerges from the tank. If the UFO collides with the missile, the player wins; otherwise the UFO lands and the player loses.

Here are some details concerning the three game objects and their movements. First, the tank moves a constant speed along the bottom of the canvas though the player may use the left arrow key and the right arrow key to change directions. Second, the UFO descends at a constant velocity but makes small random jumps to the left or right. Third, once fired the missile ascends along a straight vertical line at a constant speed at least twice as fast as the UFO descends. Finally, the UFO and the missile collide if their reference points are close enough, for whatever you think “close enough” means.

The following two subsections use this sample problem as a running example, so study it well and solve the following exercise before you continue. Doing so should help you understand the problem in enough depth.

Exercise 78: Draw some sketches of what the game scenery looks like at various stages. Use the sketches to determine the constant and the variable pieces of the game. For the former, develop physical constants that describe the dimensions of the world (canvas), its objects, and the graphical constants used to render these objects. Then develop graphical constants for the tank, the UFO, the missile, and some background scenery. Finally, create your initial scene from the constants for the tank, the UFO, and the background.

Defining Itemizations: The first step in our design recipe calls for the development of data definitions. One purpose of a data definition is to describe the construction of data that represent the state of the world; another is to describe all possible pieces of data that the functions of the world program may consume. Since we haven’t seen itemizations that include structures, this first subsection introduces the basic idea via example. While this is straightforward and probably won’t surprise you, pay close attention.

For this space invader game, we get away with one structure type definition of three fields