Built with from Grav and Hugo
So far our programs have only been able to use numbers. We need to broaden that out
to other kinds of data, most notably Boolean values (true or false; yes or no).
Boolean values will be the key to programs that do different things, depending on the
data they are given.
We’ll also look at symbols and strings.
A literal is the way you write something down. The literal for the number seven is
The term Boolean comes from the name of the British
mathematician George Boole, who defined an algebra on these values.
The sample application, (= x y), will give an error unless x and y have
been defined. To test this in DrRacket, you might enter
(= x y)
(define x 3)
(define y 4)
(= x y)
x and y could also be provided by a function’s parameters.
and and or are actually special forms, like define. That is, they look like
functions but actually are not because their arguments are not
evaluated before the “function” is applied.
You may have noticed already that special forms are typeset in a bolder font in the slides.
The special forms we’ve seen so far are define, and, and or. Another one, cond,
will be introduced shortly.
The practise of only evaluating as many arguments as necessary is sometimes called “short circuiting”.
In the first example, a computer can determine if a number is odd or greater than 2
quickly and easily. In contrast,
determining if a number is prime may involve a huge number of division operations making it
a very slow test.
The following stepper shows the short-circuit evaluation but replaces prime? with a simpler
In the steep and not-steep examples, the expressions are logical opposites.
For any pair of values for delta-x and delta-y exactly one of the two functions
will be true and the other will be false.
steep? avoids division by zero because or stops evaluating arguments (and thus doesn’t
reach the division
by zero) when it finds that the first
one is true – and thus the whole expression must be true.
Similarly, the and in not-steep? stops evaluating arguments when it finds that one is false – and thus the whole expression must be false.
A convention is something that isn’t necessary but is commonly done. In this
case, it’s commonly done to improve the readability of the code.
We’re already seen predicates that do not follow this convention: <, =, etc.
The following video introduces conditional expressions, the key tool in
Racket that allows us to vary the execution of the program depending on the
conditions it encounters.
It uses a slightly more complex example with four question/answer pairs.
Video: Download m04/m04.50_cond
Properly nested brackets: [()]. Improperly nested brackets: [(]] or [(]).
The code shown on the slide would normally be included in a function. Here it’s
named my-abs because Racket won’t let us redefine the built-in function.
(define (my-abs x)
(cond [(< x 0) (- x)]
[(>= x 0) x]))
The code shown is a straight-forward (and correct!) implementation of the
conditions shown on the previous slide.
But when (>= grade 40) is evaluated in the second question/answer pair, we
already know that grade is at least 40. Because of Racket’s top-to-bottom
evaluation of a cond, we cannot reach the second question/answer pair unless
grade is at least 40. We don’t need to test it again.
(>= grade 40)
Similarly, we cannot reach the third question/answer pair unless grade is at
least 50 (and therefore don’t need to test again).
Note that the code as given on the previous slide will produce the correct answer if the order
of the question/answer pairs is rearranged. This code depends on the order. Rearrange it and
you’ll get wrong answers.
Earlier we made the distinction between closed-box and open-box tests. Now that we
have cond expressions, the need for open-box tests is more evident. We need to make
sure that all of the code in the conditional is tested and thus must choose our test cases
carefully with the specific questions in the conditional in mind.
Please watch the video for more details.
Video: Download m04/m04.60_testing
Closed-box testing is also known, more crypticly, as “black-box” testing. The corresponding term
for “open-box” is “white-box”.
This tax computation is an extended example to pull together a bunch of
things we’ve learned:
The background is using “interval” notation. A square bracket means the
value is included while a round bracket means it’s excluded. So “(45000, 90000]”
is a concise way of saying “all values x such that 45000 < x <= 90000”.
Note that we’re following the design recipe. We did a draft of the purpose; now examples.
Note that we have some “magic numbers” appearing – numbers like 0.15 and 45000. It might
be good to make these into constants.
There are also some repeated calculations based on those proposed constants:
0.15 * 45000 occurs twice. If we did an example for $160,000,
0.15 * 45000 + 0.20 * (90000-45000) would show up multiple times as well. These might
be good constants to have.
0.15 * 45000
0.15 * 45000 + 0.20 * (90000-45000)
The tax payable calculation is split into a number of cases. That makes the calculation
a natural fit for the cond expression. The question/answer pairs are “what’s your income?”
and “here’s how much tax you’ll owe.”
The questions have already been simplified based on their order.
What is the minimum number of tests for this code? Refer back to the course-after-cs135 example
if you need a refresher.
A helper function is simply a function that is defined to help simplify
the definition of another function. More in a moment.
Reducing repeated code is sometimes referred to as “DRY”: “Don’t Repeat Yourself”.
Being DRY reduces the amount of code you need to write, debug, and maintain. If there
is a bug, there’s only one place to go to fix it.
Factoring out complex calculations gives a separate function that is usually easier to test.
Humans can’t keep too much in our heads at once. We need to chunk things. Factoring
out calculations into a helper function is one way to do that. It’s often worth it even if the helper
function is only used once.
Having meaningful names in the code to identify an operation also helps with that chunking.
“Oh yeah, I know what that does.”
Use judgement and don’t go overboard on providing short definitions just to introduce a name.
Racket’s grandparent language, Lisp, was created in 1958 largely out of frustration
with the limited abilities of existing languages to handle non-numeric data.
The contract for symbol=? is Sym Sym -> Bool. It consumes two symbols
and produces a Boolean result.
Sym Sym -> Bool
Comparing symbols for equality is pretty much the only thing we’ll do with them
in CS135. That’s pretty limited, computationally! We can do so much more
with numbers (addition, multiplication, comparison, etc). But the self-documenting
nature of symbols makes the a worthwhile data type to have available.
Racket has many more functions on strings. See the online documentation or
use the help desk in DrRacket. There’s no need to memorize all those functions. We’ll
use a pretty small subset in CS135.
Symbols are like multiple choice; strings are like short answer.
Applying (symbol=? 'blue 100) breaks the contract that we gave earlier.
We said that symbol=? consumes two symbols. If you break the contract and
give it a symbol and a number, then it can do whatever it wants. In this
case, it gives an error.
(symbol=? 'blue 100)
equal?, on the other hand, has a contract of Any Any -> Bool. That is,
you can give it any pair of Racket values and it has to do the “right thing”. In
the case of (equal? 'blue 100), the “right thing” is to produce false.
Any Any -> Bool
(equal? 'blue 100)