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.
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
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.
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:
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.
: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.