If you have not already done so, please take the quiz before continuing with lab.
In this lab, you'll practice with some of the things introduced in class, including functional decomposition, and functions on Booleans and symbols. You'll explore conditionals in greater depth. Based upon this work, we'll revise the design recipe.
Instructions for students & labbies: Students should read and use DrScheme on the exercises at their own pace, while labbies wander among the students, answering questions, bringing the more important ones to the lab's attention. Students shouldn't look at the sample solutions until finishing the exercise. Depending on their pace, students won't necessarily have time to finish all exercises. That's perfectly OK, in which case, students should skip ahead during lab to do at least one exercise in each section. For the last 25 minutes or so, have a group discussion on the points listed and consider the modified design recipe.
Design Recipe Reminder
Let's quickly recall the design recipe (so far). We'll fill in some new steps during this lab, so we'll start with some funny numbering.
- Function contract, purpose, header
- Function examples
- Function body
- Function testing
Programming is not tinkering on a program until it passes some tests. That approach leaves people wandering, and provides little confidence in the program correctness, and none in its maintainability. There is Process in creating a program.
Functional decomposition
In general, it is better to write your programs as a collection of small functions, rather than one giant function.
- Each function is simpler. Each is easier to understand, write, test, and debug.
- Code is re-used. Each computation is written once, instead of multiple times -- a a single point of control. Thus, if we need to change/fix, say, the computation of the average of a bunch of numbers, we only need to change one piece of code. This generally leads to shorter code with fewer mistakes.
|
Following the design template, rewrite the following (correct) code in better style.
; hypotenuse : real real -> positive-real
; Returns the length of the hypotenuse of a right triangle having
; the given height and width.
(define (hypotenuse height width)
(sqrt (+ (* height height) (* width width))))
'hypotenuse_examples
(= (hypotenuse 0 0) 0)
(= (hypotenuse 3 4) 5)
(= (hypotenuse 6 8) 10)
|
| Sample solution. |
Simple data: Booleans, symbols
We've seen that DrScheme has
-
numbers:
17,-42.89,3/17, … -
Booleans:
true,false -
symbols:
'central-daylight-time,'galatea2.2,'mary, …
Numbers are old hat, so let's explore Booleans and symbols some.
|
| Sample solution |
Conditionals
At the end of Monday's class, we just started to talk about Booleans
and conditionals. In this section, we'll go into explore conditionals
more.
A cond expression looks like the following:
(cond
[(symbol=? 'Sam 'Sandy) 'wrongo]
[(symbol=? 'Sam 'Sam) 'yep]
[else 'hmmmm])
This starts with the cond keyword.
It then has a series of "clauses", here, three of them.
Here, each clause is grouped with brackets, but you can use
parentheses instead, too.
Each clause has a question and an answer.
The whole thing evaluates to the answer of the first true
question.
The last clause's question can be else, which is always true.
Of course, writing code like the above, where we test the equality of two known things, is kind of silly. A more realistic example would be the following:
; number-to-day : nat in 1...7 -> 'symbol
; Purpose: Returns the name of the day corresponding to the
; given number, assuming that the week starts with Monday.
(define (number-to-day num)
(cond
[(= 1 num) 'Monday]
[(= 2 num) 'Tuesday]
[(= 3 num) 'Wednesday]
[(= 4 num) 'Thursday]
[(= 5 num) 'Friday]
[(= 6 num) 'Saturday]
[(= 7 num) 'Sunday]))
|
Sample solution for within-two?.
|
The previous example is one way we use conditionals. The following two have a slightly different flavor that we'll discuss.
|
Sample solutions:
fastest-runner,
colors.
|
Discussion
-
In
fastest-runner, we could have shortened the sample solution by eliminating the first part of theandin each of the 2nd and 3rd cases. Why is this a bad idea? -
In the running and color examples,
we could have replaced the last clause question with
else. Is this a good idea? -
Is there anything in common about the fastest runner and color
examples?
Yes!
We find there are often several times that we'd like
to create or our categories or types of data.
E.g.,
; A running-distance is one of ; - an integer in [0,300) ; - an integer in [300,1000) ; - an integer in [1000,10000) ; - an integer in [10000,+inf.0)
This data definition better reflects our thinking of the program. We might want to use this user-defined type several times. E.g., to write functions for female runners, male runners, Junior Olympics, whatever.Aside This is "half-open interval" notation, standard for mathematicians: the square-bracket means include the number in the interval, and the round-bracket means exclude the number from the interval. These are not meant to be scheme-parentheses. We'll use "+inf.0" to mean positive-infinity for now. -
Once we've given this name to this type of data, what would the
contract for your
fastest-runnerfunction be? - How would we define a color type? How would we use it for the color complement and primary color tester?
- There are several ways to write the primary color tester. What are their tradeoffs?
In summary, this "x, y, or z" structure
is a very common way to define data, and you see that the code
reflects this way of thinking about the data:
There is one cond branch for each
branch of data.
Stepping back: Recipe redux
What was involved in the preceding examples? We realized that when approaching a problem, the first thing we do, is decide how our program will represent data, in the problem we're modeling. Moreover, we've seen two common situations so far:
- The data is just one of the built-in data types. Any particular function dealing with that data will use the corresponding built-in functions.
-
The data is of the form "x, y, or z".
Any particular function dealing with that data will use
a
cond, with one branch for each type of data. - … There will be a third case next week. …
Our code mirrors our data, which in turn mirrors how we conceive of the problem. This is no coincidence. More than half the work of writing a good program lies in choosing good data definitions. We'll see how the shape of the data often guides you to a bug-free solution.
We'll close by updating our design recipe.
- 1. new Data definition: How, exactly, do you represent the problem in terms of Scheme data?
- 2. new Data examples.
- 4. Function contract, purpose, header.
- 5. Function examples: Use your previously-written data examples.
-
6. Function body.
-
new
If your data has various cases, include a
cond-- one branch per case of data. (You can write all thecondquestions first, to distinguish the data, and come back later to worry about the answer for a particular function.)
-
new
If your data has various cases, include a
- 7. Function testing: Use your previously-written examples.
Here's a full example for the colors.
| What is step 3? We'll talk about that in class soon: it will just be a case of saying that defining your data alone gives you enough information to write the skeleton of any function which processes that data! (viz. take the header, and include step 6a). This is handy because you can write the skeleton just once, and then copy/paste it for every later function involving that data. |