Part 16

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 have reached the end of this section! Continue to the next section:

You can check your current points from the blue blob in the bottom-right corner of the page.