Lecture notes: Conditionals
Pen costs, revisited
Last time, we ended up with a pen-cost
function that looked like this:
fun pen-cost(num-pens :: Number, message :: String) -> Number: num-pens * (0.25 + (string-length(message) * 0.02)) where: pen-cost(3, "wow") is 0.87 pen-cost(10, "nice") is 10 * (0.25 + (string-length("nice") * 0.02)) end
We’re going to add one more thing to this function: a “docstring” describing what it does.
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 0.87 pen-cost(10, "nice") is 10 * (0.25 + (string-length("nice") * 0.02)) end
Whenever you write a function, you should include a docstring.
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 tests:
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): 20 else: 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
Putting it all together
Now, how will we calculate the total cost of pens?
fun total-cost(num-pens :: Number, slogan :: String) -> Number: doc: "compute the total cost of pens, including shipping" add-shipping(pen-cost(num-pens, slogan)) end
We’re using add-shipping
as a helper function: a function that helps us
break down a large problem into smaller subproblems. In this case, we have
identified two subproblems: computing the base cost of pens, and adding in
shipping costs.
More on helper functions
Let’s quickly revisit the snake patterns we talked about last time in order to find another example of helper functions. Last time we looked at code to create partial patterns; a function to create the full pattern is shown below:
fun stripe-pattern(color1 :: String, color2 :: String, color3 :: String) -> Image: frame(beside(rectangle(30, 100, "solid", color1), beside(rectangle(30, 100, "solid", color2), beside(rectangle(30, 100, "solid", color3), beside(rectangle(30, 100, "solid", color2), rectangle(30, 100, "solid", color1)))))) end stripe-pattern("green", "black", "white")
Can we exploit the structure of this image to write something a little cleaner?
Looking at the images, we see that there is an innermost rectangle, surrounded
by two copies of another rectangle, then surrounded again by two copies of a
third rectangle. If we had a surround
operation, the function would be cleaner:
fun stripe-pattern(color1 :: String, color2 :: String, color3 :: String) -> Image: surround(rectangle(30, 100, "solid", color1), surround(rectangle(30, 100, "solid", color2"), rectangle(30, 100, "solid", color3))) end stripe-pattern("green", "black", "white")
Functions let us add operations like this. We can define surround
:
fun surround(outer-image :: Image, inner-image :: Image) -> Image: beside(outer-image, beside(inner-image, outer-image)) end