Pattern Guards
The version of our sign
function in the previous section is a marked
improvement over the previous versions in terms of readability, but it's still
not how a seasoned Haskell programmer would write it. Here is probably how
they would do it:
sign :: Int -> Int
sign 0 = 0
sign x | x > 0 = 1
| otherwise = -1
This definition uses two features. The first one is pattern matching, which we discussed in the previous section. The second one is pattern guards. These are conditions that we can attach to patterns. The important thing is that these conditions can be arbitrary Boolean expressions. If a pattern matches but its attached condition is not satisfied, then we are not allowed to use this branch. The syntax for pattern guards is simple. You separate the pattern from its attached condition using a vertical bar. You can have multiple sub-branches corresponding to different conditions. For these sub-branches, the vertical bars need to line up. If lines get too long, I tend to put the pattern guards on new lines from the pattern, but in this case the vertical bars still need to line up, like so:
sign :: Int -> Int
sign 0 = 0
sign x
| x > 0 = 1
| otherwise = -1
So how do pattern guards work? Let's work through our sign
function. If we
call sign
with an argument that is 0
, then the first equation sign 0 = 0
applies because our argument matches the pattern 0
, and this pattern has no
pattern guards (conditions) attached to it. So we return 0
as before. If the
argument is not 0
, then the first equation does not apply, and we check the
second equation. The pattern x
of this equation matches any argument, and we
end up binding x
to this argument. However, this equation has two
sub-branches, each with a pattern guard. So we check the condition attached to
the first branch, x > 0
. If this condition is satisfied, then we take this
branch and we return 1
. If this condition is not satisfied, then we are not
allowed to take the first sub-branch, and we go on to checking the condition of
the second sub-branch. Here, the condition of the second sub-branch is
otherwise
, which is a condition that always succeeds.1 Thus, we end
up taking the second sub-branch and return -1
, which is the right answer
because we know that the argument is neither 0
nor positive, so it must be
negative.
A few more comments are in order. First, we can have as many sub-branches as we want. Here, we only needed two, but there are some case distinctions that branch into many different cases depending on a number of conditions.
Second, it is not required that some sub-branch succeeds for every pattern. To illustrate this, we could have written our sign function as
sign :: Int -> Int
sign 0 = 0
sign x | x > 0 = 1
sign _ = -1
Let's parse this. Again, if the argument is 0
, then the argument matches the
pattern of the first equation and the result is 0
. if the argument is
non-zero, then the first equation cannot be used and we check the second
equation. In this equation, the pattern x
matches the argument and gets
bound to the argument. However, we have the condition x > 0
attached to this
pattern. If this condition is satisfied, we are allowed to use this equation
and return 1
, which is correct because our argument is positive. If the
condition is not satisfied, then the entire second equation fails because it
has only one sub-branch and the condition attached to this sub-branch is not
satisfied. Thus, we try the third equation, sign _ = -1
. In this case, we
do not care anymore what the argument is because we know that it is negative,
given that the first two equations failed. Thus, we use the wildcard _
for
the argument in this equation and unconditionally return -1
.
Just to be sure that this definition does indeed do the right thing, here is the result of playing with it in GHCi:
>>> :{
| sign 0 = 0
| sign x | x > 0 = 1
| sign _ = -1
| :}
>>> sign 5
1
>>> sign (-10)
-1
>>> sign 0
0
The third comment is that we can attach pattern guards also to the branches of
a case
expression. This shouldn't be all that surprising, given that I
mentioned in the previous section that a definition of a function with multiple
equations is really a single equation with a case
expression. The two
definitions of sign
above can be written using a case
expression as
sign :: Int -> Int
sign x = case x of
0 -> 0
_ | x > 0 -> 1
| otherwise -> -1
or
sign :: Int -> Int
sign x = case x of
0 -> 0
_ | x > 0 -> 1
_ -> -1
To summarize, we can implement branching into various alternative paths of
computation in Haskell using if then else
expressions, case
expressions,
multiple equations and pattern matching, and pattern guards.
Exercise
Assume we want to implement a sign function that tells us in plain English whether a number is positive, negative or zero. If \(x = 0\), the function should produce the string "zero". If \(x > 0\), the string should be "positive". If \(x < 0\), the string should be "negative". Here's an attempt implementing such a function:
strSign :: Int -> String
strSign x = case x == 0 of
True -> "zero"
otherwise | x > 0 -> "positive"
| otherwise -> "negative"
_ -> "Oops! Something went wrong!"
The logic seems impeccable. If x == 0
, the result is "zero". Otherwise, if
x > 0
, the result is "positive". Otherwise, the result is "negative". In
addition, we pride ourselves in being good software engineers who always
catch unexpected behaviour and deal with it gracefully instead of simply
letting our program crash, so we added a catch-all branch with a wildcard
pattern. We don't expect this branch to ever be reached, so the string in
this case is "Oops! Something went wrong!". Now let's try this out:
>>> :{
| strSign x = case x == 0 of
| True -> "zero"
| otherwise | x > 0 -> "positive"
| | otherwise -> "negative"
| _ -> "Oops! Something went wrong!"
| :}
<interactive>:5:17: warning: [-Woverlapping-patterns]
Pattern match is redundant
In a case alternative: otherwise | otherwise -> ...
>>> strSign 0
"zero"
>>> strSign 1
"positive"
>>> strSign (-1)
"Oops! Something went wrong!"
The warning that GHCi spits out, "Pattern match is redundant", tells us that
the branch otherwise | otherwise
is never used, and our attempt to
evaluate strSign (-1)
confirms this: The result is "Oops! Something went
wrong!", not "negative", as we expected. Explain why the function
misbehaves.
Hint: Read the footnote on this page.
Solution
As explained in the footnote, otherwise
is simply a variable, not a
keyword. Here, the first branch of the case
expression applies if
x == 0
. Thus, the second branch is tried only when x == 0
evaluates
to False
. The pattern match for the second branch then sets
otherwise = False
. If x < 0
, the pattern guard x > 0
of the first
sub-branch fails. The pattern guard of the second sub-branch is
otherwise
. Since pattern matching sets otherwise = False
, this
pattern guard fails too! Since neither of the two sub-branches of the
otherwise
branch of the case
expression succeeds, we try the final
branch. This branch has a wildcard pattern and no pattern guards, so it
always succeeds, and the result is "Oops! Something went wrong!" for any
negative argument.
The footnote suggested that we could try to intentionally break
perfectly fine Haskell code by redefining otherwise
. We'd normally
never do this. What happend here was an accidental redefinition of
otherwise
, by using it as a pattern. The morale of the story: Never
use otherwise
anywhere in your code except as the final pattern guard
attached to some pattern.
Exercise
Implement a function myAbs
that returns the absolute value of its
argument. We discussed here how to implement such a function
using an if then else
expression. Use pattern guards instead in this
exercise.
Solution
myAbs :: Int -> Int
myAbs x | x > 0 = x
| otherwise = -x
Bonus: The Haskell standard library provides a signum
function for
number types, which does exactly the same as the sign
function we beat to
death in this chapter. Can you figure out a way to implement myAbs
completely without case distinctions?
Solution
myAbs :: Int -> Int
myAbs x = x * signum x
-
Try
GHCi>>> :info otherwise >>> otherwise
Can you guess what
otherwise
is? It sounds like a keyword, such aselse
, but it's actually a plain old value, defined asotherwise = True
. Since a pattern guard is a Boolean expression, andotherwise
is a Boolean expression that is always true, this allows us to write pattern guards that say, "otherwise, if none of the previous branches matched, use this branch". What a sneaky little hack.Given that
otherwise
is a plain old value, you are free to redefine it in your code. Don't! If you want to drive a fellow Haskell programmer insane and gift them hours of debugging to figure out why theotherwise
branch of a function definition doesn't run, you could defineotherwise = False
. ↩