Chapter 5 Making Decisions
Section 5.1 Boolean Values
So far, we’ve only seen Racket code that uses numbers. Numbers are cool, but there’s other kinds of data out there, and we should broaden our horizons a bit. In programming a good next step is to look at boolean values, which is a fancy name for values that are either
true
or false
. With boolean values, we can add logic to our functions, eventually writing functions that behave differently depending on the parameters. Whevener we work with a function that retusn a boolean value, we can imagine it asking a question, like Is this number odd?, Are these values equal?...In Racket, there are different ways of representing literal boolean values. We can either use
true
and false
or #t
and #f
. There is no major advantage to one set over another, though there’s a resonable readability argument to go with true false
.Section 5.2 Comparison Functions
Comparison functions take numbers as arguments and return boolean values based on the type of comparison being performed.
=
: Equals. Ex.(= a b)
>
: Greater than. Ex.(> a b)
<
: Less than. Ex.(< a b)
>=
: Greater than or equal. Ex.(>= a b)
<=
: Less than or equal. Ex.(<= a b)
Like the arithmetic functions, these functions can all take two or more arguments.
(= a b c)
would return true
only if all three values are equal. (< a b c)
would return true
only if a
is less than b
and b
is less than c
.Section 5.3 Boolean Functions
Boolean functions return boolean values but they also take in boolean values as arguments. These are also called logical operators because they directly correspond to logical operations that you may have learned about in a math class at some point. In Racket, we have three boolean functions:
(and p q)
- Returns
true
only when all the arguments are alsotrue
- Returns
false
when any of the arguments arefalse
(or p q)
- Returns
true
when any of the arguments aretrue
- Returns
false
only when all the arguments are alsotrue
(not p)
- Returns the opposite boolean value of
p
Like the comparison functions,
and
and or
can both take two or more arguments. not
only works with a single argument.Section 5.4 What if?...
So now that we’ve got a whole lot of boolean tools to work with, what can we do with them? One thing is we can use a boolean value to change how a function behaves.
Let’s say you were writing a program that works with computer color values. Traditionally those values cannot be greater than 255. Suppose you want to write a function that would make sure you have a valid color value. If the input is less than or equal to 255, then the function should return the original value. But if the input is greater than 255, then the function should return 255 (since that is the largest possible value). So here we have a function that returns its parameter sometimes, and other times 255.
In Racket, there is a function called
if
that does exactly what we want!. if
is a function that takes three arguments. The first is a boolean value and the next two are any kind of value. When the boolean parameter is true
, the first parameter is returned, otherwise, the second is. Generally, if
works like this:(if BOOLEAN
RESULT_IF_TRUE
RESULT_IF_FALSE
)
Using
if
, we can now write our color validation function:(define validColor
(lambda (c)
(if (>= c 255)
255
c)
))
The
if
function in validColor
returns either c
or 255
, making it fairly straightforward. But remember that in Racket, we can always use a function that returns a value in place of a value (when necessary). Consider a slightly more complex task, a function that takes in a valid color value (so you can assume it is less than 255) changes it to be closer to the middle instead of the extremes:(define mutedColor
(lambda (c)
(if (< c 128)
(+ 25 c)
(- 25 c)
)))
Section 5.5 Nesting
Sometimes, a single true/false test does not cover all possible cases. Take our computer color problem from before. Colors must be less than or equal to 255, but they also have to be greater than or equal to 0, a case that was conveniently omitted before. A more useful
validColor
function would be able to work as already defined, but also return 0 if a negative color value was provided.(validColor 176)
returns176
(validColor 987)
returns255
(validColor -32)
returns0
The problem is that
if
can only give one of two possible results, but we need three possible results. In addition to what we’ve already coded, we need to check if the provided color is less than 0.(if (>= c 0)
c
0)
So how do we incorporate this function into our exiting
validColor
code? Well, remember that if
is a function that returns a value, and we can always use a function in place of a value:(define validColor
(lambda (c)
(if (>= c 255)
255
(if (>= c 0)
c
0))
))
We’ve taken the false result of our original function, which was
c
and replaced it with a section if
function, which will return c
or 0
. Generally speaking, when we place an if
function inside another if
function, we call this nesting. There’s no (reasonable) limit to how deep your nesting can go, it all depends on the problem you’re trying to solve. There’s also no reason that you can double-nest, that is use an if
function for both the true and false sections of another if
.Section 5.6 In Too Deep
At some point, you may find that nesting This is a perfectly valid
if
functions gets cumbersome. At about the third or fourth level in, you’ve tabbed pretty far over, and your code can get less readable. Consider the following if
function: (if (<= s 14)
14
(if (<= s 18)
18
(if (<= s 23)
23
(if (<= s 28)
28
(if (<= s 34)
34
42)))))
if
function, but some would argue that because there are some many nested functions, it looses out on the all-important readability scale. In Racket, there is another decision-making function designed specifically when you have a series of related conditions to check.cond
is a function that has the ability to produce different results based on multiple boolean tests: (cond
(TEST0 RESULT0)
(TEST1 RESULT1)
(TEST2 RESULT2)
...
(else DEFAULT)
)
TEST
statement above must be a boolean value (or a function that returns a boolean value). The RESULT
s or DEFAULT
can be any value. cond
will return the RESULT
attached the the first TEST
that is/returns true
, all subsequent functions within cond
will be skipped.You’ll notice the last test/result pair has the word
else
, which has a special meaning for use with cond
. In the event that all the boolean test of a cond
statement are false
, the result attached else
will be returned. This makes sure that cond
will always return a value, regardless of your tests, which is generally a good thing.If we take our super-nested
if
function, and translate into cond
, it would look like this: (cond
((<= s 14) 14)
((<= s 18) 18)
((<= s 23) 23)
((<= s 28) 28)
((<= s 34) 34)
(else 42)
)
It is worth noting that
cond
is not necessary, you can get the same results using if
. The reason cond
exists is to make certain functions easier to understand/write. If you prefer if
you can just stick with it. Conversely, you could use cond
in place of all your if
functions, if you really dig how cond
works. That being said, whether or not you have a preference, you should understand how each works.