In the section En garde!, we learnt that guarded equations allow us to simulate the behaviour of the switch statement from languages such as C, Java, and JavaScript. Guarded equations also behave similarly to the match statement from Python. Below, we discuss another way to mimic the switch and match statements.

Brief case

Let’s start with an example. An integer $n$ is even if it is a multiple of 2, and odd otherwise. We write the latter as the following mathematical expression:

\[\begin{equation} \label{eqnParity} p(n) = \begin{cases} \text{even},& \text{if } n \bmod 2 = 0,\\[8pt] \text{odd},& \text{otherwise}. \end{cases} \end{equation}\]

In Haskell, we use the case expression to implement formula (\ref{eqnParity}). Examine the code below:

:include: file=”assets/src/decide/parity.hs”, line=27:33

A case expression has the format specified below:

1
2
3
4
5
6
case expression of
    pattern1 -> expression1
    pattern2 -> expression2
    ...
    patternN -> expressionN
    _        -> defaultExpression

We start a case expression with the keyword case, followed by an expression expression that evaluates to one of a finite number of values. If the value matches pattern1, then expression1 is evaluated. In case the value matches pattern2, then expression2 is evaluated. And so on. The default pattern is the wildcard _, which should be the last pattern of a case expression as a means of catching all other patterns not handled by pattern1 to patternN. The wildcard pattern is equivalent to the default case in the switch statement of C and JavaScript, and the case _ in a Python match statement.

Let’s compare the Haskell code above with implementations in other languages. In C, expression (\ref{eqnParity}) can be implemented via a switch statement as follows:

:include: file=”assets/src/decide/parity.c”, line=25:-

Here’s a JavaScript implementation:

:include: file=”assets/src/decide/parity.js”, line=25:-

Python wants to join in the fun:

:include: file=”assets/src/decide/parity.py”, line=26:35

Renaissance artists

Do you know which colour is associated with which ninja turtle? Donatello is purple, Leonardo (da Vinci) is blue, Michelangelo is orange, and Raphael is red. Given a name of a ninja turtle, we want to output the corresponding colour. Any other name would not be recognized as the name of a ninja turtle. In case you cannot remember the colour of a turtle, the function below might help:

:include: file=”assets/src/decide/turtle.hs”, line=28:36

Here’s a slightly more difficult problem. Given a person’s name, output the first vowel in the name. Some names do not contain vowels at all, in which case output the null character '\0'. The following code solves the above problem. Similar to the script :script: file=”assets/src/decide/parity.hs” , the code below illustrates that the wildcard pattern _ is not required as the final pattern in a case expression, provided we have covered all possibilities.

:include: file=”assets/src/decide/name.hs”, line=25:38

The function vowel in the script :script: file=”assets/src/decide/name.hs” is defined in terms of a list comprehension, a topic to be discussed in the section ¿Comprende?. Test the above code with these Renaissance artists:

The following names do not have vowels at all. Use the names to test the above code as well.

Which one?

We conclude this section with a retrospective on the decision-making constructs available in Haskell. Hitherto the chapter has discussed the following techniques for decision-making:

  1. Conditional expression
  2. Guarded equation
  3. Pattern matching
  4. Case expression

Which technique should you choose? Generally, you should err on the side of readability. A conditional expression is ideal for simple decision-making, where the conditional is a short expression that allows you to fit an if...then...else construct on one line, like the ternary operator in some other languages. Some Haskell code formatter would automatically structure an if...then...else across three lines, which defeats the purpose of a conditional expression being like a ternary operator. You might as well use a guarded equation instead of a conditional expression.

Pattern matching is ideal for the situation where you have a finite set of values. Suppose you have the values $a_1,\, a_2,\, \dots,\, a_n$. With a guarded equation you would test each value via a guard such as | x == a1. It is tedious to write out all such conditionals, especially when you have three or more conditionals. Your code can be simplified somewhat by using pattern matching. Case expression and pattern matching are almost identical. Whenever you can replace a case expression by pattern matching, do so. For everything else, use a Master Car… err… a case expression.

Exercises

:exercise: Rewrite the function parity of the script :script: file=”assets/src/decide/parity.hs” to use a where clause.

:exercise: Rewrite the function parity of the script :script: file=”assets/src/decide/parity.hs” to use a conditional expression.

:exercise: Rewrite the function parity of the script :script: file=”assets/src/decide/parity.hs” to use a guarded equation.

:exercise: The following table presents various famous Renaissance artists and one of their expertise.

Artist Expertise
Albrecht Dürer painter
Andrea Mantegna painter
Donatello sculptor
Giovanni Bellini painter
Jan van Eyck painter
Leonardo da Vinci painter
Lorenzo Ghiberti sculptor
Michelangelo painter
Raphael painter
Sandro Botticelli painter

Write a function that, given the name of a Renaissance artist, outputs the artist’s expertise.

:exercise: Write a function that accepts a person’s name. If the name has at most three letters, output the name. If the name has more than three letters, output the first letter of the name. If the given parameter is an empty string, output the null character \0. Use a case expression in your solution.

:exercise: Repeat the previous exercise, but without using a case expression.

:exercise: Using a case expression, write a function that takes a list of integers. If the list is empty, output zero. If the list has only one element, square the number. If the list has only two elements, multiply those two elements together. For all other cases, sum the elements of the list. Provide another implementation that does not use case expression.

:exercise: The table below presents the dress/shoes combination as recommended by InStyle. Write a function that takes the name of a dress and outputs the corresponding recommended shoes.

Dress Shoes
babydoll dress loafers
blazer dress kitten heels
button-down shirtdress white sneakers
cocktail dress open-toe heeled sandals
denim dress cowboy boots, sneakers, loafers
full skirt pointed-toe pumps, heeled sandals
maxidress strappy block heels
midi dress open-toe kitten heels
minidress ballet flats
slip dress anything and everything
sundress loafers
T-shirt dress combat boots

:exercise: The following are categories of HTTP status codes:

Write a function that takes a status code and outputs the category to which the code belongs.

:exercise: This is a continuation of the previous exercise. Refer to this page for an extended list of HTTP status codes and their meanings. Write a function that takes a status code and outputs its meaning.