I can't decide

Donald: I don’t know whether I want to, or not!
Prof. Batty: That is a problem to be decided by Flipism! Heads, you do! Tails, you don’t! [Tosses a coin.] It came down heads! Here is your book—your membership button! You owe me a buck!
— Carl Barks. “Flip Decision” in Walt Disney’s Comics and Stories, volume 13, issue 5, 1953

How many times do you find yourself unable to make up your mind about something? Take the manual work out of decision making and let Haskell automate it for you. Haskell provides various ways to help you construct a program that branches into several paths, depending on whichever choice you make. The techniques are not guaranteed to cure you of indecision, but at least make it seem like your program knows when to be resolute.

Conditional if...then...else

Sam is writing a small program to remind her of Tabby’s Friday special treat. Tabby the cat likes to eat sardine. Once a week, Sam would feed Tabby a small can of sardine on Friday. Tabby would receive her usual, scheduled meal if it is not time for sardine. Sam translates the above logic into Haskell code as follows.

sardine.hs

1
2
3
4
5
6
7
8
9
10
11
import Text.Printf

-- | Sardine day.
main = do
    let today = "Friday"
    let sardineDay = "Friday"
    let meal =
            if today == sardineDay
                then "sardine"
                else "other scheduled meal"
    printf "Today's meal: %s\n" meal

The above code uses an expression we have not seen before, i.e. the conditional expression if...then...else. A Haskell conditional expression can be written as the following structure.

1
2
3
if condition
    then runCode
    else runOtherCode

"if..then...else" Control flow of if...then...else.

The conditional expression can be visualized as per the image above. The condition must be an expression that evaluates to a boolean value. If condition evaluates to True, control jumps to the then clause and the expression runCode is evaluated. In case condition evaluates to False, control jumps to the else clause and the expression runOtherCode is evaluated. Unlike some programming languages, Haskell requires the else clause. For this reason, the Haskell conditional expression is similar to the ternary operator in languages such as C, JavaScript, and Python. Observe the GHCi session below.

1
2
3
ghci> import Data.Char
ghci> putStrLn $ if isAlpha 'a' then "alphabetic" else "not alphabetic"
alphabetic

The whole if...then...else construct must result in a value. In particular, each of the then and else clauses must result in a value. The value of each clause must be of the same type. For example, if the then clause results in a value of type Integer, the else clause must also result in a value of type Integer.

Let’s use the above explanation of the conditional expression to understand how the program sardine.hs works. The condition is the expression today == sardineDay. Since the condition evaluates to True, control jumps to the then clause where we have the expression "sardine". This expression is merely a string. The result of the then clause is the string "sardine", which is assigned to meal. I know this is redundant, but assume for the sake of argument that the condition today == sardineDay evaluates to False. In that case, control would jump to the else clause, where we have the expression "other scheduled meal". Both expressions in the then and else clauses evaluate to the same type, i.e. String. The whole conditional expression evaluates to a value of type String. That value, whether it be from the then or else clause, is assigned to meal. Why the lack of an explicit return statement? As noted above, Haskell’s conditional expression is analogous to the ternary operator in some other programming languages.

Boolean chain

Audience: Boooo…
Mr. Burns: Smithers. Are they booing me?
Smithers: Aahh… no. They’re saying, “Boo-urns, boo-urns.”
The Simpsons, season 6, episode 18, 1995

The condition in an if...then...else expression must evaluate to a boolean value. It makes sense that the condition can be a chain of boolean expressions, connected via one or more boolean operators || and/or &&. For instance, although Friday is sardine day for Tabby, Sam only feeds sardine to Tabby during the afternoon. With the latter finer detail, Sam modifies the program sardine.hs as follows.

sardine-time.hs

1
2
3
4
5
6
7
8
9
10
11
12
13
import Text.Printf

-- | The day and time for sardine.
main = do
    let today = "Friday"
    let sardineDay = "Friday"
    let time = 1300 -- 1:00 pm in 24-hour time
    let sardineTime = 1300
    let meal =
            if (today == sardineDay) && (time == sardineTime)
                then "sardine"
                else "other scheduled meal"
    printf "Today's meal: %s\n" meal

Nested if...then...else

Baby doll: Help! I’m trapped in a matryoshka doll set.

In Haskell, conditional expressions can be nested within one another, like in languages such as C, JavaScript, and Python. For example, two integers $a$ and $b$ satisfy one of the following relations:

  • $a > b$
  • $a < b$
  • $a = b$

The above can be translated into Haskell code as:

nested.hs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import Text.Printf

-- | Relationship between two integers.
main = do
    putStrLn "Enter first integer."
    a <- getLine
    putStrLn "Enter second integer."
    b <- getLine
    let k = read a :: Integer
    let n = read b :: Integer
    let relation =
            if k < n
                then "less than"
                else if k > n
                         then "greater than"
                         else "equal to"
    printf "%s is %s %s\n" a relation b

A do block within if...then...else

Recall from the section Printing numbers that a do block allows you to perform a sequence of actions. You typically use a do block in the function main to denote all code in that function.1 A do block can be used in a then or else clause to allow you to perform multiple actions specific to the clause.

By way of example, suppose you want a program that prompts for an integer. If the integer you enter is negative, the program prints the string "I'm positive the number is negative". In case the integer you enter is zero or positive, the program doubles the number and prints the string "It's Dublin" together with the doubled number. You anticipate the then clause would have only one expression, namely the string to output for that clause. No do block for the then clause. However, you require a do block for the else clause because it would perform two actions, one after the other. You translate the above description to Haskell as follows.

double.hs

1
2
3
4
5
6
7
8
9
10
11
12
import Text.Printf

-- | Double an integer.
main = do
    putStrLn "Enter an integer."
    num <- getLine
    let n = read num :: Integer
    if n < 0
        then putStrLn "I'm positive the number is negative"
        else do
            let nn = 2 * n
            printf "It's Dublin: %d\n" nn

Let’s see how a do block can help Sam organize some of her weekend chores. Saturday is the day for weekly grocery shopping. Sam needs to stock up on food for herself as well as cat food for Tabby. The local supermarket has a sale this weekend on pet supplies. Sam needs to stock up on Tabby’s favourite treats: sardine and tuna. Sunday is the weekly grooming day for Tabby. Tabby needs to be brushed and combed to help keep her coat clean. Sunday is also laundry day. The above chores are translated into the program below to help Sam remind herself of her weekend todo lists.

weekend.hs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import Text.Printf

-- | Todo lists for the weekend.
main = do
    putStrLn "Enter a day."
    day <- getLine
    if day == "Saturday"
        then do
            putStrLn "1. Grocery shopping"
            putStrLn "2. Stock up on sardine and tuna cans"
        else if day == "Sunday"
                 then do
                     putStrLn "1. Load the washing machine"
                     putStrLn "2. Comb and brush Tabby"
                     putStrLn "3. Hang the clothes out to dry"
                 else putStrLn "Not the weekend"

Exercises

Exercise 1. Many countries have Saturday and Sunday as their weekend. Write a program that prompts for the name of a day of the week. Output whether the given day is a weekend.

Exercise 2. Write a program that prompts for an integer. Output whether the given integer is even or odd.

Exercise 3. Write a program that prompts for two integers. Output the maximum of both values. Repeat the exercise for the minimum of two integers.

Exercise 4. On Monday, Sam would feed pork and mixed vegetables to Tabby. Modify the program sardine.hs to account for Tabby’s meal on Monday.

Exercise 5. The program day.hs below prints the current day of the week, using functions defined in the package Data.Time. The function getCurrentTime returns the current time in UTC format, using your computer’s system clock. We then use utctDay to extract the current date, excluding the current time. The date is converted to the format of the Gregorian calendar and we destructure the resulting tuple as the variables year, month, and day. The function fromGregorian converts the values of year, month, and day to the format year-month-day, which is then fed to the function dayOfWeek to tell us the current day of the week. Repeat the previous exercise, but using the current day of the week instead of hard coding a day string.

day.hs

1
2
3
4
5
6
7
8
9
import Data.Time
import Text.Printf

-- | The current day of the week.
main = do
    time <- getCurrentTime
    let (year, month, day) = toGregorian $ utctDay time
    let today = dayOfWeek $ fromGregorian year month day
    printf "Today is %s\n" $ show today

Exercise 6. The program time.hs below outputs the current time of the day in 24-hour format. The function getCurrentTime returns the current time in UTC format, using your computer’s system clock. The function getCurrentTimeZone returns your computer’s configured time zone. Using your local time zone and the UTC date/time, the function utcToLocalTime returns your current local date/time, including fractional seconds. However, you only want the hour, minute, and second as integers so you use the function localTimeOfDay to obtain the required information, then destructure the result. Noon starts from 12 pm. Write a program to obtain the current hour. If the current time is 12 pm, then output the string "It's high noon". Otherwise output the string "Not yet high noon" for all other hours.

time.hs

1
2
3
4
5
6
7
8
9
10
11
12
13
import Data.Time
import Text.Printf

-- | Current time of the day in 24-hour format.
main = do
    dateTime <- getCurrentTime
    timeZone <- getCurrentTimeZone
    let localDateTime = utcToLocalTime timeZone dateTime
    let (TimeOfDay hour minute second) = localTimeOfDay localDateTime
    let h = toInteger hour
    let m = toInteger minute
    let s = floor second :: Integer
    printf "Current time: %d hours, %d minutes, and %d seconds\n" h m s

Exercise 7. The absolute value of a number $x$ is defined as:

\[|x| = \begin{cases} x,& \text{if } x \geq 0,\\[8pt] -x,& \text{otherwise}. \end{cases}\]

Write a program that prompts for a number and outputs its absolute value.

Exercise 8. According to this website, it is safe to feed the following fruits to cats: apple, banana, blueberries, cantaloupe, strawberries, watermelon. Eggs and cheese are also safe for cats. In addition to Tabby’s scheduled meal on Monday and Friday, Sam wants to feed mixed fruits to Tabby on Tuesday, scrambled eggs on Wednesday, and hard cheese such as cheddar or Gouda on Thursday. Repeat this exercise, but incorporate the above meals for Tabby on Tuesday, Wednesday, and Thursday.

Exercise 9. A four-digit year is a leap year, provided one of the following conditions holds:

  1. The number can be divided by 4 and cannot be divided by 100.
  2. The number can be divided by 400.

Write a program to determine whether the current year is a leap year. If it is a leap year, divide the year by 2 and output the current year together with its halved value. If the current year is not a leap year, output the string "No ribbit for froggy". You are not allowed to hard code the digits of the current year.

Exercise 10. The weekdays are Monday, Tuesday, Wednesday, Thursday, and Friday. The weekend consists of Saturday and Sunday. Write a program to prompt for the current day of the week. If it is a weekday, output the string "It's a weekday". If the day is part of the weekend, output the string "Weekend fun". If it is a Wednesday, Thursday, or Friday output the string "WTF". Furthermore, output the following strings, depending on the given day.

  • Monday. Output "A case of the Mondays".
  • Tuesday. Output "Taco night".
  • Wednesday. Output "It's Wednesday, my dude".
  • Thursday. Output "Hang in there. One more day.".
  • Friday. Output "Thank Gwynevere it's Friday".
  • Saturday. Output "Caturday :3".
  • Sunday. Output "Sunday driver".
  1. A do block is not necessary to denote all code in the function main. Other techniques are available to allow you to perform a sequence of actions.