Learn You a Haskell for Great Good! - Miran Lipovaca [18]
ghci> tell [1,2,3,4]
"This list is long. The first two elements are: 1 and 2"
ghci> tell []
"The list is empty"
The tell function is safe to use because it can match to the empty list, a singleton list, a list with two elements, and a list with more than two elements. It knows how to handle lists of any length, and so it will always return a useful value.
How about if instead we defined a function that only knows how to handle lists with three elements? Here’s an example of such a function:
badAdd :: (Num a) => [a] -> a
badAdd (x:y:z:[]) = x + y + z
Here’s what happens when we give it a list that it doesn’t expect:
ghci> badAdd [100,20]
*** Exception: examples.hs:8:0-25: Non-exhaustive patterns in function badAdd
Yikes! Not cool! If this happened inside a compiled program instead of in GHCi, the program would crash.
One final thing to note about pattern matching with lists: You can’t use the ++ operator in pattern matches. (Remember that the ++ operator joins two lists into one.) For instance, if you tried to pattern match against (xs ++ ys), Haskell wouldn’t be able to tell what would be in the xs list and what would be in the ys list. Though it seems logical to match stuff against (xs ++ [x,y,z]), or even just (xs ++ [x]), because of the nature of lists, you can’t.
As-patterns
There’s also a special type of pattern called an as-pattern. As-patterns allow you to break up an item according to a pattern, while still keeping a reference to the entire original item. To create an as-pattern, precede a regular pattern with a name and an @ character.
For instance, we can create the following as-pattern: xs@(x:y:ys). This pattern will match exactly the same lists that x:y:ys would, but you can easily access the entire original list using xs, instead of needing to type out x:y:ys every time. Here’s an example of a simple function that uses an as-pattern:
firstLetter :: String -> String
firstLetter "" = "Empty string, whoops!"
firstLetter all@(x:xs) = "The first letter of " ++ all ++ " is " ++ [x]
After loading the function, we can test it as follows:
ghci> firstLetter "Dracula"
"The first letter of Dracula is D"
Guards, Guards!
We use patterns to check if the values passed to our functions are constructed in a certain way. We use guards when we want our function to check if some property of those passed values is true or false. That sounds a lot like an if expression, and it is very similar. However, guards are a lot more readable when you have several conditions, and they play nicely with patterns.
Let’s dive in and write a function that uses guards. This function will berate you in different ways depending on your body mass index (BMI). Your BMI is calculated by dividing your weight (in kilograms) by your height (in meters) squared. If your BMI is less than 18.5, you’re considered underweight. If it’s anywhere from 18.5 to 25, you’re considered normal. A BMI of 25 to 30 is overweight, and more than 30 is obese. (Note that this function won’t actually calculate your BMI; it just takes it as an argument and then tells you off.) Here’s the function:
bmiTell :: => Double -> String
bmiTell bmi
| 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!"
A guard is indicated by a pipe character (|), followed by a Boolean expression, followed by the function body that will be used if that expression evaluates to True. If the expression evaluates to False, the function drops through to the next guard, and the process repeats. Guards must be indented by at least one space. (I like to indent them by four spaces so that the code is more readable.)
For instance, if we call this function with a BMI of 24.3, it will first check if that’s less than or equal to 18.5. Because it isn’t, it falls through to the next guard. The check is carried out with the second guard, and because 24.3 is less than 25.0,