Conditionals and booleans
More Functions Practice: Cost of pens
Imagine that you are trying to compute the total cost of an order of pens with slogans (or messages) printed on them. Each pen costs 25 cents plus an additional 2 cents per character in the message (count spaces as characters for now).
First, let’s write two expressions that do this computation, just to make sure we understand how it should work:
# if ordering 3 pens that say "wow" > 3 * (0.25 + (string-length("wow") * 0.02)) # if ordering 10 pens that say "smile" > 10 * (0.25 + (string-length("smile") * 0.02))
Having a clear idea of the computation you want to do makes it much easier to write a function to do it. How do we figure out a function that would correspond to these two expressions? We find the places where the two functions are different, circle them, and label each of them with a different unique name. These names become the parameters of the function.
fun pen-cost(num-pens :: Number, message :: String) -> Number: doc: "cost of ordering pens with slogans" num-pens * (0.25 + (string-length(message) * 0.02)) end
The initial expressions we developed give rise to our examples:
fun pen-cost(num-pens :: Number, message :: String) -> Number: doc: "cost of ordering pens with slogans" num-pens * (0.25 + (string-length(message) * 0.02)) where: pen-cost(3, "wow") is 3 * (0.25 + (string-length("wow") * 0.02)) pen-cost(10, "smile") is 10 * (0.25 + (string-length("smile") * 0.02)) end
What are other good examples to check here? Ordering 0 pens and pens with empty messages are both good examples. You don’t have to check negative numbers of pens or messages that aren’t strings, though – those fall outside the expected “contract” of the function (we’ll discuss how to properly handle the negatives later).
Once you are comfortable with the idea of writing functions, you would skip the circle-and-label step, and just jump directly to writing the expression with the parameters in place. Use the circle-and-label technique as needed to get yourself to that point.
Pen costs, revisited
Shipping costs
We’ve got a function now that computes the cost of pens, but our pen company has to account for another cost: shipping! It turns out that shipping costs $10 for orders of $50 or less, and $20 for orders of more than $50. So we’re going to write a function that takes the subtotal (i.e. the cost of the pens on their own) and adds on the shipping.
fun add-shipping(subtotal :: Number) -> Number: ... end
Before we write the function, we can write some examples and a docstring.
fun add-shipping(subtotal :: Number) -> Number: doc: "add shipping amount to subtotal" ... where: add-shipping(1) is 1 + 10 add-shipping(50) is 50 + 10 add-shipping(50.01) is 50.01 + 20 add-shipping(100) is 100 + 20 end
add-shipping
adds a different amount depending on its input. In order to do
that, we’ll need a feature we haven’t seen before: conditionals. We’ll use a
conditional like this:
fun add-shipping(subtotal :: Number) -> Number: doc: "add shipping amount to subtotal" if ...: # subtotal is $50 or less subtotal + 10 else: subtotal + 20 end where: add-shipping(1) is 1 + 10 add-shipping(50) is 50 + 10 add-shipping(50.01) is 50.01 + 20 add-shipping(100) is 100 + 20 end
Boolean expressions
What’s an expression that says “the subtotal is $50 or less?” Expressions that
answer yes-or-no questions belong to a new type that we haven’t seen before:
Boolean
. There are two Boolean
values: true
and false
. We can compare
numbers and get Booleans:
> 30 <= 50 > 100 <= 50 > 100 > 50
Let’s say we have a number, subtotal
. What are some questions
we might ask about it?
> subtotal = 70 > 50 <= subtotal > subtotal < 100 > (50 <= subtotal) and (subtotal < 100) # what happens if we leave off the parentheses? > (subtotal <= 50) or (subtotal >= 70)
So in order to complete add-shipping
, we can use these comparison operators.
fun add-shipping(subtotal :: Number) -> Number: doc: "add shipping amount to subtotal" if subtotal <= 50: subtotal + 10 else: subtotal + 20 end where: add-shipping(1) is 1 + 10 add-shipping(50) is 50 + 10 add-shipping(50.01) is 50.01 + 20 add-shipping(100) is 100 + 20 end
Conditionals with multiple branches
What if shipping is more complicated? For instance, let’s say that shipping is still $20 orders between $50 and $100 (inclusive) but is $30 for orders over $100. We should first add some new examples:
fun add-shipping(subtotal :: Number) -> Number: doc: "add shipping amount to subtotal" if subtotal <= 50: subtotal + 10 else: subtotal + 20 end where: add-shipping(1) is 1 + 10 add-shipping(50) is 50 + 10 add-shipping(50.01) is 50.01 + 20 add-shipping(100) is 100 + 20 add-shipping(100.01) is 100.01 + 30 add-shipping(200) is 200 + 30 end
How can we modify our function to do the right thing? A conditional expression
can have any number of else if
branches:
fun add-shipping(subtotal :: Number) -> Number: doc: "add shipping amount to subtotal" if subtotal <= 50: subtotal + 10 else if (subtotal > 50) and (subtotal <= 100): subtotal + 20 else: subtotal + 30 end where: add-shipping(1) is 1 + 10 add-shipping(50) is 50 + 10 add-shipping(50.01) is 50.01 + 20 add-shipping(100) is 100 + 20 add-shipping(100.01) is 100.01 + 30 add-shipping(200) is 200 + 30 end