1.4 Pattern matching
Our functions still lack an important ability: the ability to make decisions. Computers make decisions using branching logic. For example: If some condition is true, do this, otherwise do something else. Haskell has support for this through pattern matching, which is a way to inspect a piece of data. You can define multiple equations for a single function by matching on different values of the inputs, which we call patterns.
showFuelStatus :: Integer > String
showFuelStatus 0 = "We're all out of fuel, captain!"
showFuelStatus 1 = "We have one fuel core left."
showFuelStatus 2 = "We have two fuel cores left."
This function showFuelStatus
, when given an Integer
,
will output a String
based on the value of that
Integer
. There are three equations defined, each for a
different value of the input. If we add this definition to our
Lambdas.hs
file, we can load it in ghci and try it out.
> showFuelStatus 0
"We're all out of fuel captain!"
> showFuelStatus 1
"We have one fuel core left."
> showFuelStatus 7
*** Exception: Nonexhaustive patterns in function showFuelStatus
As you can see, it works fine if we apply it to inputs that it
anticipates, but since it only knows how to handle inputs of 0
,
1
, or 2
, it breaks if we use any other input value. We
can fix this by adding a default equation that matches on
anything. All we need to do is use a normal variable, just like
before when we defined functions.
showFuelStatus :: Integer > String
showFuelStatus 0 = "We're all out of fuel, captain!"
showFuelStatus 1 = "We have one fuel core left."
showFuelStatus 2 = "We have two fuel cores left."
showFuelStatus x = "We're all good."
The last equation will now be used if none of the others match the input.
> showFuelStatus 7
"We're all good."
That’s much better. What would happen if we move the last equation before all of the others?
showFuelStatus :: Integer > String
showFuelStatus x = "We're all good."
showFuelStatus 0 = "We're all out of fuel, captain!"
showFuelStatus 1 = "We have one fuel core left."
showFuelStatus 2 = "We have two fuel cores left."
If we load this into ghci,
> :reload
Lambdas.hs:15:1: warning: [Woverlappingpatterns]
Pattern match is redundant
In an equation for ‘showFuelStatus’: showFuelStatus 0 = ...

15  showFuelStatus 0 = "We're all out of fuel, captain!"
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
we get a warning about overlapping patterns. Since the first equation has a pattern that matches on anything, the other equations will never even be checked, and the first equation will always be used.
> showFuelStatus 0
"We're all good."
This happens because the equations in a definition are checked from top to bottom, one at a time, and the first one that matches is used. Now, move the catchall equation back to the bottom, and try applying it to a negative number.
showFuelStatus :: Integer > String
showFuelStatus 0 = "We're all out of fuel, captain!"
showFuelStatus 1 = "We have one fuel core left."
showFuelStatus 2 = "We have two fuel cores left."
showFuelStatus x = "We're all good."
> showFuelStatus (1)
"We're all good."
That’s going to be a problem. It’s possible that we could accidentally provide it with a negative number, which will give us the wrong result, meaning your ship could end up stranded in the middle of galactic space without any fuel. To fix this, we can use another feature of Haskell called case expressions, which allow us to match on either a piece of data directly, or on the result of an expression. A case expression has the following form.
case expression of
pattern1 > result1
pattern2 > result2
Start with the case
keyword, followed an expression to match
on, then the keyword of
. Then on the lines below, list out each
equation that you want to use for pattern matching. These equations
start with the pattern you want to match on, followed by an arrow
>
, and then the result for that equation. Here is an example.
checkUniverse :: String
checkUniverse = case 2 + 2 of
4 > "The laws of the universe still hold."
x > "The universe is broken!"
We are matching on the result of the expression 2 + 2
. This
case expression has two equations, the first matching on the number
4
, and the second matching on anything, which it assigns to the
variable x
. Try the following exercise.
Your starship is equipped with advanced warp drives, but they
quickly eat up your fuel and cause you to travel forward in time
faster than everything around you (time dilation). Sometimes, it is
important for you to avoid these relativistic effects, which are
explained by Einstein’s theory of special relativity. You can avoid
these effects by staying below 10% the speed of light. The speed of
light is approximately 300,000,000 meters per second, which you can
represent in Haskell as the floating point number 3.0e8
. This
is the same as the scientific notation shorthand 3.0×10^{8}.
In your Lambdas.hs
file, write a function with the following
type signature,
relativisticEffects :: Double > String
that takes the speed of your ship, and outputs whether or not you will experience relativistic effects at that speed.
We can simply divide the speed of our ship by the speed of
light, and check if the result is greater than or equal to
0.10
, which is 10%. Then we can pattern match on the
result, which will be a value of type Bool
.
relativisticEffects speed =
case (speed / 3.0e8) >= 0.10 of
True > "Things are about to get weird!"
False > "All good."
Now we can finally fix our faulty showFuelStatus
function using
case expressions.
showFuelStatus :: Integer > String
showFuelStatus 0 = "We're all out of fuel, captain!"
showFuelStatus 1 = "We have one fuel core left."
showFuelStatus 2 = "We have two fuel cores left."
showFuelStatus x = case x > 2 of
True > "We're all good."
False > "We have negative fuel, captain!"
If we test it in ghci,
> showFuelStatus 7
"We're all good"
> showFuelStatus (1)
"We have negative fuel, captain!"
we now get a more helpful output.