The case-of Expression
We’ve seen pattern matching in function arguments, but there’s also a way to pattern match in an expression. It looks like this:
case <value> of <pattern> -> <expression>
<pattern> -> <expression>
As an example let’s rewrite the describe
example from the first lecture using case
:
describe :: Integer -> String
describe 0 = "zero"
describe 1 = "one"
describe 2 = "an even prime"
describe n = "the number " ++ show n
describe :: Integer -> String
describe n = case n of 0 -> "zero"
1 -> "one"
2 -> "an even prime"
n -> "the number " ++ show n
A more interesting example is when the value we’re pattern matching on is not a function argument. For example:
-- parse country code into country name, returns Nothing if code not recognized
parseCountry :: String -> Maybe String
parseCountry "FI" = Just "Finland"
parseCountry "SE" = Just "Sweden"
parseCountry _ = Nothing
flyTo :: String -> String
flyTo countryCode = case parseCountry countryCode of Just country -> "You're flying to " ++ country
Nothing -> "You're not flying anywhere"
Prelude> flyTo "FI"
"You're flying to Finland"
Prelude> flyTo "DE"
"You're not flying anywhere"
We could write the flyTo
function using a helper function for pattern matching instead of using the case-of expression:
flyTo :: String -> String
flyTo countryCode = handleResult (parseCountry countryCode)
where handleResult (Just country) = "You're flying to " ++ country
handleResult Nothing = "You're not flying anywhere"
In fact, a case-of expression can always be replaced with a helper function. Here’s one more example, written in both ways:
-- given a sentence, decide whether it is a statement, question or exclamation
sentenceType :: String -> String
sentenceType sentence = case last sentence of '.' -> "statement"
'?' -> "question"
'!' -> "exclamation"
_ -> "not a sentence"
-- same function, helper function instead of case-of
sentenceType sentence = classify (last sentence)
where classify '.' = "statement"
classify '?' = "question"
classify '!' = "exclamation"
classify _ = "not a sentence"
Prelude> sentenceType "This is Haskell."
"statement"
Prelude> sentenceType "This is Haskell!"
"exclamation"
When to Use Case Expressions
You might be asking, what is the point of having another pattern matching syntax. Well, case
expressions have some advantages over equations which we’ll discuss next.
Firstly, and perhaps most importantly, case
expressions enable us to pattern match against function outputs. We might want to write early morning motivational messages to working (lazy) Haskellers:
motivate :: String -> String
motivate "Monday" = "Have a nice week at work!"
motivate "Tuesday" = "You're one day closer to weekend!"
motivate "Wednesday" = "3 more day(s) until the weekend!"
motivate "Thursday" = "2 more day(s) until the weekend!"
motivate "Friday" = "1 more day(s) until the weekend!"
motivate _ = "Relax! You don't need to work today!"
Using a case
expression we can run a helper function against the argument and pattern match on the result:
motivate :: String -> String
motivate day = case distanceToSunday day of
6 -> "Have a nice week at work!"
5 -> "You're one day closer to weekend!"
n -> if n > 1
then show (n - 1) ++ " more day(s) until the weekend!"
else "Relax! You don't need to work today!"
By the way, there’s also a third way, guards:
motivate :: String -> String
motivate day
| n == 6 = "Have a nice week at work!"
| n == 5 = "You're one day closer to weekend!"
| n > 1 = show (n - 1) ++ " more day(s) until the weekend!"
| otherwise = "Relax! You don't need to work today!"
where n = distanceToSunday day
We’ll see in a moment how we can define distanceToSunday
using equations and case
expressions.
Secondly, if a helper function needs to be shared among many patterns, then equations don’t work. For example:
area :: String -> Double -> Double
area "square" x = square x
area "circle" x = pi * square x
where square x = x * x
This won’t compile because a the where
clause only appends to the "circle"
case, so the square
helper function is not available in the "square"
case. On the other hand, we can write
area :: String -> Double -> Double
area shape x = case shape of
"square" -> square x
"circle" -> pi * square x
where square x = x*x
Thirdly, case
expressions may help to write more concise code in a situation where a (long) function name would have to be repeated multiple times using equations. As we saw above, we might need a function which measures the distance between a given day and Sunday:
distanceToSunday :: String -> Int
distanceToSunday "Monday" = 6
distanceToSunday "Tuesday" = 5
distanceToSunday "Wednesday" = 4
distanceToSunday "Thursday" = 3
distanceToSunday "Friday" = 2
distanceToSunday "Saturday" = 1
distanceToSunday "Sunday" = 0
Using a case
expression leads into much more concise implementation:
distanceToSunday :: String -> Int
distanceToSunday d = case d of
"Monday" -> 6
"Tuesday" -> 5
"Wednesday" -> 4
"Thursday" -> 3
"Friday" -> 2
"Saturday" -> 1
"Sunday" -> 0
These three benefits make the case
expression a versatile tool in a Haskeller’s toolbox. It’s worth remembering how case
works.
(Representing weekdays as strings may get the job done, but it’s not the perfect solution. What happens if we apply motivate
to "monday"
(with all letters in lower case) or "Wed"
? In Lecture 5, we will learn a better way to represent things like weekdays.)
Exercises
All exercises can be found in Set2a and Set2b. Please pay attention in the title of the exercise in which file the exercises of this section can be found.
Exercises from 2a:
None for now :)!
Exercises from 2b:
You can check your current points from the blue blob in the bottom-right corner of the page.