En garde!

Hi! I’m a shouty man.1 Do you miss the switch statement from languages such as C, Java, and JavaScript? Or the match statement from Python? Tired of writing (or reading) nested conditional expressions like the following?

nested.hs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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"

Say no more. What if I tell you that you can get rid of nested conditional expressions? How would you like to organize your nested conditional expressions according to the cases or conditions you want to match? Curious? Read on.

Guarded equations

A conditional expression can be replaced with a guarded equation. To understand what a guarded equation can do for you, recall the function maxInt from the section Multiple parameters:

max.hs

1
2
3
4
5
6
-- | The maximum of two integers.
maxInt :: Integer -> Integer -> Integer
maxInt x y =
    if x > y
        then x
        else y

Here is the same function, but written as a guarded equation:

max.hs

1
2
3
4
5
-- | The maximum of two integers.
maxIntg :: Integer -> Integer -> Integer
maxIntg x y
    | x > y = x
    | otherwise = y

And here is the guarded equation translated to a mathematical expression:

\[\text{max}(x,\, y) = \begin{cases} x,& \text{if } x > y,\\[8pt] y,& \text{otherwise}. \end{cases}\]

A guarded equation is a function that follows the format

1
2
3
4
5
funcName a b ...
    | conditional_1 = result_1
    | conditional_2 = result_2
    ...
    | otherwise = result_n

First, we have the name funcName of the guarded equation. We use the name of the guarded equation and pass parameters to it in the same way that we apply a function. A guarded equation is a function. The pipe symbol | is read as “such that” and signifies the beginning of a guard. A guard is a conditional, an expression that evaluates to a boolean value. To the right of the guard is the equal sign =, followed by the result corresponding to the guard. The line

1
| conditional_1 = result_1

can be read as, “The value of the function is such that if conditional_1 holds True, then the output is result_1.” The other lines have similar interpretation. The final line of a guarded equation is

1
| otherwise = result_n

Like the switch statement (and the label default) in some languages, Haskell does not require the guard otherwise in the final line of your guarded equation. However, do bear in mind that the guard otherwise is defined as otherwise = True and serves as a catch-all for every case not handled by the previous guards. Use the guard otherwise to handle the default output of your guarded equation.

Body mass index

The body mass index (BMI) is a quick and easy way to assess a person’s health in terms of their weight and height. The formula is given as

\[\text{BMI} = \frac{ \text{weight} }{ \text{height} \times \text{height} }\]

where the weight (or mass) is measured in kilograms and the height is measured in metres. According to the US Centers for Disease Control and Prevention (CDC), an adult of 20 years or older can be categorized as follows according to their BMI.

BMIStatus
below 18.5underweight
18.5–24.9healthy weight
25.0–29.9overweight
30.0 or aboveobese

The information in the above table is translated to a guarded equation as follows.

bmi.hs

1
2
3
4
5
6
7
8
-- | The weight status of an adult 20 years or older, based on their BMI.
weightStatus :: Double -> String
weightStatus bm
    | bm < 18.5 = "underweight"
    | (18.5 <= bm) && (bm <= 24.9) = "healthy weight"
    | (25.0 <= bm) && (bm <= 29.9) = "overweight"
    | bm >= 30.0 = "obese"
    | otherwise = "unknown BMI or weight status"

Java example

Different coffee beverages are best served with cups of various sizes. According to this website, the cup sizes corresponding to some drinks are as given below.

DrinkCup size
espresso56–85 ml
cappuccino142–170 ml
latte227–426 ml
frappuccino312–426 ml

Your mission, should you choose to accept it, is to write a function that takes the name of a caffeinated drink and outputs the corresponding cup size. Did you decline? No problem. Below is a translation of the above table to Haskell code.

coffee.hs

1
2
3
4
5
6
7
8
-- | The cup size corresponding to a coffee beverage.
cupSize :: String -> String
cupSize drink
    | drink == "espresso" = "56--85 ml"
    | drink == "cappuccino" = "142--170 ml"
    | drink == "latte" = "227--426 ml"
    | drink == "frappuccino" = "312--426 ml"
    | otherwise = "unknown drink"

Exercises

Exercise 1. Write a guarded equation to determine the minimum of two integers.

Exercise 2. Rewrite the program nested.hs by using guarded equation.

Exercise 3. The sign or signum function is defined as follows:

\[\text{sgn}(x) = \begin{cases} -1,& \text{if } x < 0,\\[8pt] 0,& \text{if } x = 0,\\[8pt] 1,& \text{if } x > 0. \end{cases}\]

Implement the sign function as a Haskell guarded equation.

Exercise 4. Repeat the daily saying exercise, but use guarded equation.

Exercise 5. The triangular function is defined as:

\[\text{tri}(x) = \begin{cases} 1 - |x|,& \text{if } |x| < 1,\\[8pt] 0,& \text{otherwise}. \end{cases}\]

Repeat the exercise on absolute value, but implement a guarded equation. Then implement the triangular function as a guarded equation.

Exercise 6. The size of a college in the USA can be categorized according to the number of students the college has. Refer to the table below.

StudentsSize
less than 5,000small
5,000 to 15,000medium
greater than 15,000large

Implement the above table as a guarded equation.

Exercise 7. In the USA, the size of woman’s clothing can be designated by a number between 0 and 22. We can assign the size to a category as given below.

Numeric sizeCategory
0–2XS
4–6S
8–10M
12–14L
16–18XL
20XXL
22XXXL

Implement the above table as a guarded equation.

Exercise 8. A quadratic equation can be written in the general form

\[ax^2 + bx + c = 0\]

where $a,b,c$ are real numbers with $a \neq 0$. The solution(s) to a quadratic equation depends on the value of the discriminant $\Delta = b^2 - 4ac$. In particular, we have the following cases:

  1. If $\Delta > 0$, then we have two distinct real solutions.
  2. If $\Delta = 0$, then we have a repeated real solution.
  3. If $\Delta < 0$, then we have two distinct complex solutions.

Write a guarded equation to determine the type of solutions of a given quadratic equation.

Exercise 9. Write a Haskell function that outputs the body mass index given an adult’s weight (kilogram) and height (metre). Use your implementation to test the function weightStatus from the section Body mass index.

Exercise 10. Using guarded equation, write a function for each of the following.

  • The maximum of three integers.
  • The minimum of three integers.
  • The middle of three integers.
  1. A recurring salesman from Horrible History. See him in action here