Learn You a Haskell for Great Good! - Miran Lipovaca [19]
Guards are very reminiscent of a big if/else tree in imperative languages, though they’re far more readable. While big if/else trees are usually frowned upon, sometimes a problem is defined in such a discrete way that you can’t get around them. Guards are a very nice alternative in these cases.
Many times, the last guard in a function is otherwise, which catches everything. If all the guards in a function evaluate to False, and we haven’t provided an otherwise catchall guard, evaluation falls through to the next pattern. (This is how patterns and guards play nicely together.) If no suitable guards or patterns are found, an error is thrown.
Of course, we can also use guards with functions that take multiple parameters. Let’s modify bmiTell so that it takes a height and weight, and calculates the BMI for us:
bmiTell :: Double -> Double -> String
bmiTell weight height
| weight / height ^ 2 <= 18.5 = "You're underweight, you emo, you!"
| weight / height ^ 2 <= 25.0 = "You're supposedly
normal. Pffft, I bet you're ugly!"
| weight / height ^ 2 <= 30.0 = "You're fat! Lose some weight, fatty!"
| otherwise = "You're a whale, congratulations!"
Now, let’s see if I’m fat:
ghci> bmiTell 85 1.90
"You're supposedly normal. Pffft, I bet you're ugly!"
Yay! I’m not fat! But Haskell just called me ugly. Whatever!
Note
A common newbie mistake is to put an equal sign (=) after the function name and parameters, before the first guard. This will cause a syntax error.
As another simple example, let’s implement our own max function to compare two items and return the larger one:
max' :: (Ord a) => a -> a -> a max' a b
| a <= b = b
| otherwise = a
We can also implement our own compare function using guards:
myCompare :: (Ord a) => a -> a -> Ordering
a `myCompare` b
| a == b = EQ
| a <= b = LT
| otherwise = GT
ghci> 3 `myCompare` 2
GT
Note
Not only can we call functions as infix with backticks, we can also define them using backticks. Sometimes this makes them easier to read.
where?!
When programming, we usually want to avoid calculating the same value over and over again. It’s much easier to calculate something only once and store the result. In imperative programming languages, you would solve this problem by storing the result of a computation in a variable. In this section, you’ll learn how to use Haskell’s where keyword to store the results of intermediate computations, which provides similar functionality.
In the previous section, we defined a BMI calculator function like this:
bmiTell :: Double -> Double -> String
bmiTell weight height
| weight / height ^ 2 <= 18.5 = "You're underweight, you emo, you!"
| weight / height ^ 2 <= 25.0 = "You're
supposedly normal. Pffft, I bet you're ugly!"
| weight / height ^ 2 <= 30.0 = "You're fat! Lose some weight, fatty!"
| otherwise = "You're a whale, congratulations!"
Notice that we repeat the BMI calculation three times in this code. We can avoid this by using the where keyword to bind that value to a variable and then using that variable in place of the BMI calculation, like this:
bmiTell :: Double -> Double -> String
bmiTell weight height
| bmi <= 18.5 = "You're underweight, you emo, you!"
| bmi <= 25.0 = "You're supposedly normal. Pffft, I bet you're ugly!"
| bmi <= 30.0 = "You're fat! Lose some weight, fatty!"
| otherwise = "You're a whale, congratulations!"
where bmi = weight / height ^ 2
We put the where keyword after the guards and then use it to define one or more variables or functions. These names are visible across all the guards. If we decide that we want to calculate BMI a bit differently, we need to change it only once. This technique also improves readability by giving names to things, and it can even make our programs faster, since our values are calculated just once.
If we wanted to, we could even go a bit overboard and write our function like this:
bmiTell :: Double -> Double -> String
bmiTell weight height
| bmi <= skinny = "You're underweight, you emo, you!"
| bmi <= normal = "You're supposedly normal.