6 Conditionals and Booleans
6.1 Motivating Example: Shipping Costs
In Functions Practice: Cost of pens, we wrote a program (pencost) to compute the cost of ordering pens. Continuing the example, we now want to account for shipping costs. We’ll determine shipping charges based on the cost of the order.
Specifically, we will write a function addshipping to compute the total cost of an order including shipping. Assume an order valued at $10 or less ships for $4, while an order valued above $10 ships for $8. As usual, we will start by writing examples of the addshipping computation.
Do Now!
Use the is notation from where blocks to write several examples of addshipping. How are you choosing which inputs to use in your examples? Are you picking random inputs? Being strategic in some way? If so, what’s your strategy?
Here is a proposed collection of examples for addshipping.
addshipping(10) is 10 + 4 addshipping(3.95) is 3.95 + 4 addshipping(20) is 20 + 8 addshipping(10.01) is 10.01 + 8
Do Now!
What do you notice about our examples? What strategies do you observe across our choices?
Our proposed examples feature several strategic decisions:
Including 10, which is at the boundary of charges based on the text
Including 10.01, which is just over the boundary
Including both natural and real (decimal) numbers
Including examples that should result in each shipping charge mentioned in the problem (4 and 8)
So far, we have used a simple rule for creating a function body from examples: locate the parts that are changing, replace them with names, then make the names the parameters to the function.
Do Now!
What is changing across our addshipping examples? Do you notice anything different about these changes compared to the examples for our previous functions?
The values of 4 and 8 differ across the examples, but they each occur in multiple examples.
The values of 4 and 8 appear only in the computed answers—
not as an input. Which one we use seems to depend on the input value.
6.2 Conditionals: Computations with Decisions
To ask a question about our inputs, we use a new kind of expression called an if expression. Here’s the full definition of addshipping:
fun addshipping(orderamt :: Number) > Number: doc: "add shipping costs to order total" if orderamt <= 10: orderamt + 4 else: orderamt + 8 end where: addshipping(10) is 10 + 4 addshipping(3.95) is 3.95 + 4 addshipping(20) is 20 + 8 addshipping(10.01) is 10.01 + 8 end
In an if expression, we ask a question that can produce an answer that is true or false (here orderamt <= 10, which we’ll explain below in Booleans), provide one expression for when the answer to the question is true (orderamt + 4), and another for when the result is false (orderamt + 8). The else in the program marks the answer in the false case; we call this the else clause. We also need end to tell Pyret we’re done with the question and answers.
6.3 Booleans
Every expression in Pyret evaluates in a value. So far, we have seen three types of values: Number, String, and Image. What type of value does a question like orderamt <= 10 produce? We can use the interactions prompt to experiment and find out.
Do Now!
Enter each of the following expressions at the interactions prompt. What type of value did you get? Do the values fit the types we have seen so far?3.95 <= 10 20 <= 10
The values true and false belong to a new type in Pyret, called Boolean.Named for George Boole. While there are an infinitely many values of type Number, there are only two of type Boolean: true and false.
Exercise
What would happen if we entered orderamt <= 10 at the interactions prompt to explore booleans? Why does that happen?
6.3.1 Other Boolean Operations
There are many other builtin operations that return Boolean values. Comparing values for equality is a common one: There is much more we can and should say about equality, which we will do later [ReExamining Equality].








In general, == checks whether two values are equal. Note this is different from the single = used to associate names with values in the directory.
The last example is the most interesting: it illustrates that strings are casesensitive, meaning individual letters must match in their case for strings to be considered equal.This will become relevant when we get to tables later.












Do Now!
Can you compare true and false? Try comparing them for equality (==), then for inequality (such as <).










Why use these operators instead of the more generic ==?
Do Now!
Trynumequal("a", 1) stringequal("a", 1)
Therefore, it’s wise to use the typespecific operators where you’re expecting the two arguments to be of the same type. Then, Pyret will signal an error if you go wrong, instead of blindly returning an answer (false) which lets your program continue to compute a nonsensical value.





6.3.2 Combining Booleans
Often, we want to base decisions on more than one Boolean value. For
instance, you are allowed to vote if you’re a citizen of a country
and you are above a certain age. You’re allowed to board a bus
if you have a ticket or the bus is having a freeride day. We
can even combine conditions: you’re allowed to drive if you are above
a certain age and have good eyesight and—










Exercise
Explain why numbers and strings are not good ways to express the answer to a true/false question.
6.4 Asking Multiple Questions
We have to be able to ask another question to distinguish situations in which the shipping charge is 8 from those in which the shipping charge is 12.
The question for when the shipping charge is 8 will need to check whether the input is between two values.
The current body of addshipping asks one question: orderamt <= 10. We need to add another one for orderamt <= 30, using a charge of 12 if that question fails. Where do we put that additional question?
An expanded version of the ifexpression, using else if, allows you to ask multiple questions:
fun addshipping(orderamt :: Number) > Number: doc: "add shipping costs to order total" if orderamt <= 10: orderamt + 4 else if orderamt <= 30: orderamt + 8 else: orderamt + 12 end where: ... end
if QUESTION1: <result in case first question true> else if QUESTION2: <result in case QUESTION1 false and QUESTION2 true> else: <result in case both QUESTIONs false> end
Do Now!
The problem description for addshipping said that orders between 10 and 30 should incur an 8 charge. How does the above code capture “between”?
This is currently entirely implicit. It depends on us understanding the way a if evaluates. The first question is orderamt <= 10, so if we continue to the second question, it means orderamt > 10. In this context, the second question asks whether orderamt <= 30. That’s how we’re capturing “between”ness.
Do Now!
How might you modify the above code to build the “between 10 and 30” requirement explicitly into the question for the 8 case?
(orderamt > 10) and (orderamt < 30)
Do Now!
Why are there parentheses around the two comparisons? If you replace orderamt with a concrete value (such as 20) and leave off the parenthesis, what happens when you evaluate this expression in the interactions window?
Here is what addshipping look like with the and included:
fun addshipping(orderamt :: Number) > Number: doc: "add shipping costs to order total" if orderamt <= 10: orderamt + 4 else if (orderamt > 10) and (orderamt < 30): orderamt + 8 else: orderamt + 12 end where: addshipping(10) is 10 + 4 addshipping(3.95) is 3.95 + 4 addshipping(20) is 20 + 8 addshipping(10.01) is 10.01 + 8 addshipping(30) is 30 + 12 end
They signal to future readers (including ourselves!) the condition covering a case.
They ensure that if we make a mistake in writing an earlier question, we won’t silently get surprising output.
They guard against future modifications, where someone might modify an earlier question without realizing the impact it’s having on a later one.
6.5 Evaluating by Reducing Expressions
hours = 45
if hours <= 40: hours * 10 else if hours > 40: (40 * 10) + ((hours  40) * 15) end
if 45 <= 40: 45 * 10 else if 45 > 40: (40 * 10) + ((45  40) * 15) end
=> if false: 45 * 10 else if 45 > 40: (40 * 10) + ((45  40) * 15) end
=> if false: 45 * 10 else if true: (40 * 10) + ((45  40) * 15) end
=> (40 * 10) + ((45  40) * 15)
=> 400 + (5 * 15) => 475
This style of reduction is the best way to think about the evaluation of Pyret expressions. The whole expression takes steps that simplify it, proceeding by simple rules. You can use this style yourself if you want to try and work through the evaluation of a Pyret program by hand (or in your head).
6.6 Composing Functions
pencost for computing the cost of the pens
addshipping for adding shipping costs to a total amount
What if we now wanted to compute the price of an order of pens including shipping? We would have to use both of these functions together, sending the output of pencost to the input of addshipping.
Do Now!
Write an expression that computes the total cost, with shipping, of an order of 10 pens that say "bravo".
There are two ways to structure this computation. We could pass the result of pencost directly to addshipping:
addshipping(pencost(10, "bravo"))
Alternatively, you might have named the result of pencost as an intermediate step:
pens = pencost(10, "bravo") addshipping(pens)
Both methods would produce the same answer.
Exercise
Manually evaluate each version. Where are the sequences of evaluation steps the same and where do they differ across these two programs?
6.7 Nested Conditionals
We showed that the results in ifexpressions are themselves expressions (such as orderamt + 4 in the following function):
fun addshipping(orderamt :: Number) > Number: doc: "add shipping costs to order total" if orderamt <= 10: orderamt + 4 else: orderamt + 8 end end
The result expressions can be more complicated. In fact, they could be entire ifexpressions!. To see an example of this, let’s develop another function. This time, we want a function that will compute the cost of movie tickets. Let’s start with a simple version in which tickets are $10 apiece.
fun buytickets1(count :: Number) > Number: doc: "Compute the price of tickets at $10 each" count * 10 where: buytickets1(0) is 0 buytickets1(2) is 2 * 10 buytickets1(6) is 6 * 10 end
Now, let’s augment the function with an extra parameter to indicate whether the purchaser is a senior citizen who is entitled to a discount. In such cases, we will reduce the overall price by 15%.
fun buytickets2(count :: Number, issenior :: Boolean) > Number: doc: ```Compute the price of tickets at $10 each with senior discount of 15%``` if issenior == true: count * 10 * 0.85 else: count * 10 end where: buytickets2(0, false) is 0 buytickets2(0, true) is 0 buytickets2(2, false) is 2 * 10 buytickets2(2, true) is 2 * 10 * 0.85 buytickets2(6, false) is 6 * 10 buytickets2(6, true) is 6 * 10 * 0.85 end
The function now has an additional parameter of type Boolean to indicate whether the purchaser is a senior citizen.
We have added an if expression to check whether to apply the discount.
We have more examples, because we have to vary both the number of tickets and whether a discount applies.
Now, let’s extend the program once more, this time also offering a discount if the purchaser has bought more than 5 tickets. Where should we modify the code to do this? One option is to first check whether the senior discount applies. If not, we check whether the number of tickets qualifies for a discount:
fun buytickets3(count :: Number, issenior :: Boolean) > Number: doc: ```Compute the price of tickets at $10 each with discount of 15% for more than 5 tickets or being a senior``` if issenior == true: count * 10 * 0.85 else: if count > 5: count * 10 * 0.85 else: count * 10 end end where: buytickets3(0, false) is 0 buytickets3(0, true) is 0 buytickets3(2, false) is 2 * 10 buytickets3(2, true) is 2 * 10 * 0.85 buytickets3(6, false) is 6 * 10 * 0.85 buytickets3(6, true) is 6 * 10 * 0.85 end
Exercise
Show the steps through which this function would evaluate in a situation where no discount applies, such as buytickets3(2, false).
Do Now!
Look at the current code: do you see a repeated computation that we might end up having to modify later?
Part of good code style is making sure that our programs would be easy to maintain later. If the theater changes its discount policy, for example, the current code would require us to change the discount (0.85) in two places. It would be much better to have that computation written only one time. We can achieve that by asking which conditions lead to the discount applying, and writing them as the check within just one if expression.
Do Now!
Under what conditions should the discount apply?
fun buytickets4(count :: Number, issenior :: Boolean) > Number: doc: ```Compute the price of tickets at $10 each with discount of 15% for more than 5 tickets or being a senior``` if (issenior == true) or (count > 5): count * 10 * 0.85 else: count * 10 end end
Do Now!
Take a look at the expression issenior == true. What will this evaluate to when the value of issenior is true? What will it evaluate to when the value of issenior is false?
fun buytickets5(count :: Number, issenior :: Boolean) > Number: doc: ```Compute the price of tickets at $10 each with discount of 15% for more than 5 tickets or being a senior``` if issenior or (count > 5): count * 10 * 0.85 else: count * 10 end end
Do Now!
What do you write to eliminate == false? For example, what might you write instead of issenior == false?
Finally, notice that we still have one repeated computation: the base cost of the tickets (count * 10): if the ticket price changes, it would be better to have only one place to update that price. We can clean that up by first computing the base price, then applying the discount when appropriate:
fun buytickets6(count :: Number, issenior :: Boolean) > Number: doc: ```Compute the price of tickets at $10 each with discount of 15% for more than 5 tickets or being a senior``` base = count * 10 if issenior or (count > 5): base * 0.85 else: base end end
6.8 Recap: Booleans and Conditionals
With this chapter, our computations can produce different results in different situations. We ask questions using ifexpressions, in which each question or check uses an operator that produces a boolean.
There are two Boolean values: true and false.
A simple kind of check (that produces a boolean) compares values for equality (==) or inequality(<>). Other operations that you know from math, like < and >=, also produce booleans.
We can build larger expressions that produce booleans from smaller ones using the operators and, or, not.
We can use if expressions to ask true/false questions within a computation, producing different results in each case.
We can nest conditionals inside one another if needed.
You never need to use == to compare a value to true or false: you can just write the value or expression on its own (perhaps with not to get the same computation.