Branching

Case expressions are useful for conditionals that have two or more branches. Some other languages call this kind of expression a “switch”. As mentioned on the previous page, pattern guards can also be used to a similar effect.

This program uses the time package for handling time conversions.

import Data.Time

The branches are introduced once by case...of. Haskell doesn’t typically use curly braces, but the of is necessary and the indentation matters.

The values on the left side of the -> arrows are the cases of the type that the expression in case <exp> of evaluates to. The < function returns a Boolean, so this an alternate to if expressions but with explicit matching.

timeNow now =
  case todHour (localTimeOfDay (zonedTimeToLocalTime now)) < 12 of
    True  -> putStrLn "It's before noon"
    False -> putStrLn "It's after noon"

case expressions can be used to match on the values of a custom datatype. Here is a custom type representing three mutually exclusive service plans.

The billAmount function associates a ServicePlan value to a numeric value.

data ServicePlan = Free | Monthly | Annual

billAmount plan =
  case plan of
    Free    -> 0
    Monthly -> 5
    Annual  -> billAmount Monthly * 12

Some types have more values than you want to match on manually, and maybe some of the possible inputs are irrelevant to your needs. An underscore in the final case is a catchall that will match against any value.

writeNumber i =
  case i of
    1 -> "one"
    2 -> "two"
    3 -> "three"
    _ -> "unknown number"

You can use those functions in a main program by declaring some variables and applying the functions to them within the do-block. This is what runhaskell will run (see below).

main =
  do
    now <- getZonedTime
    timeNow now

    let plan = Free
    putStrLn ("Customer owes " ++ show (billAmount plan)
              ++ " dollars.")

    let i = 2
    putStrLn ("Write " ++ show i ++ " as " ++ (writeNumber i))

Let’s use the REPL to check our work. Passing the filename you want to load into the GHCi session ensures that all the things you need are in scope at the start of your session.

$ ghci branching.hs

GHCi can give lots of information about programs. This session demonstrates applying single functions to appropriate arguments.

λ> billAmount Monthly
5

λ> billAmount Annual
60

λ> writeNumber 6
"unknown number"

If you try applying billAmount to a number, or writeNumber to a Boolean, GHCi will become annoyed. Displayed here are the first lines of the error messages you’d receive for these examples. Both of them are conveying the information that neither ServicePlan values nor Bool values are numbers, so they cannot be used interchangeably with numbers.

λ> billAmount 5

<interactive>:3:12: error:
    • Could not deduce (Num ServicePlan)
        arising from the literal ‘5’

λ> writeNumber True

<interactive>:4:1: error:
    • No instance for (Num Bool)
        arising from a use of ‘writeNumber’

The >>= function can be thought of as a means of passing the output of the first function, getZonedTime, to a second function. The first example only prints the output of getZonedTime. The second example passes the output of getZonedTime to our timeNow function.

λ> getZonedTime >>= print
2019-07-30 14:04:19.224690712 MDT

λ> getZonedTime >>= timeNow
It's after noon

When you want to quit GHCi, use :quit.

λ> :quit
Leaving GHCi.

You can use runhaskell to run the main program. Note that outputs that depend on the current time may be different when you run the program locally.

$ runhaskell branching.hs
It's after noon
Customer owes 0 dollars.
Write 2 as two