Thank you for joining us for the fifth part of our Haskell series, you will find the previous article here where I explain tuples and pattern matching.
In this article we are going to continue a little bit more on pattern matching and then discover guards.
Lists and Pattern Matching
Pattern matching is a great feature in Haskell, on the previous article we applied it on a simple value and on tuples, but this is most of the times used on lists.
Let's try it on something simple, we are going to write a function which determines if a list is empty or not:
-- Let's create a new file pattern.hs and paste this : isEmpty :: [a] -> Bool isEmpty  = True isEmpty list = False
So we create a function called
isEmpty which takes as a parameter a list with a type variable
a (meaning our list can contain anything, we do not really care here) and will return a boolean (True = empty).
We define 2 patterns here:
: This represents an empty list
list: This is the name of our parameter for
[a]from the function definition. We put this one last, otherwise we would always "fall" into this condition.
Let's try it out in GHCI:
Prelude> :l pattern.hs [1 of 1] Compiling Main ( pattern.hs, interpreted ) Ok, one module loaded. *Main> a =  *Main> b = [1, 2, 3] *Main> isEmpty a True *Main> isEmpty b False
Everything goes as expected, neat.
If you remember the second article, we were discovering lists for the first time and found out that a helper was available to get the first (
head) element of a list. Let's try to rewrite it using pattern matching:
theFirst :: [a] -> a theFirst (x:_) = x
So we create a function called
theFirst which takes as a parameter a list with a type variable
a and will return the first element of the same type
We define a single pattern here
(x:_) which deconstructs the lists into:
xas the first element of the list (
xsare frequently used in list based pattern matching in Haskell even though anything else can be used).
_is the remaining of the list and as we discussed on the previous article an underscore in Haskell is a convention for an unused part of a pattern.
Let's try it out in GHCI:
Prelude> :l pattern.hs [1 of 1] Compiling Main ( pattern.hs, interpreted ) Ok, one module loaded. *Main> b = [1, 2, 3] *Main> theFirst b 1
Great, we got our first element from the list
b. But, what would happen on an empty list ?
*Main> a =  *Main> theFirst a *** Exception: pattern.hs:7:1-18: Non-exhaustive patterns in function theFirst
Yep, we forgot to take in account the case where a list would be empty and GHCI throws a pretty explicit exception. Let's try to address that:
-- This is the amended version of theFirst theFirst :: [a] -> a theFirst  = error "Sorry, empty lists are not supported" theFirst (x:_) = x
We added a new line with a pattern matching an empty array and we used
error to throw a runtime error with a message. Let's try it out:
Prelude> :l pattern.hs [1 of 1] Compiling Main ( pattern.hs, interpreted ) Ok, one module loaded. *Main> b =  *Main> theFirst b *** Exception: Sorry, empty lists are not supported CallStack (from HasCallStack): error, called at pattern.hs:7:15 in main:Main
The program still crashes but at least, it was our decision. If it makes you curious about what is the actual behavior of
head , there it is:
*Main> head b *** Exception: Prelude.head: empty list
As you can see
head throws a runtime error as well.
The concept of guards is also common in the functional programming world, we talked about it in our Elixir article. The idea with guards is closer to the classic if/else scenarios where we test if a value is True or False, whereas pattern matching deconstructs data and binds it to variables inside the function (Remember
theFirst (x:_) = x, automatically the first element was bound to
To make it clearer let's try to rewrite our function
isItTheWeekEndAlready we wrote in the previous article using guards:
-- Let's paste this in a new file guards.hs isItTheWeekEndAlready3 :: Int -> Bool isItTheWeekEndAlready3 day | day == 0 || day == 6 = True | otherwise = False
So a few things to note:
- It is really similar to the if/else version we wrote
- We declare the variable name
dayon top so we can use it the function body (which we do not do in pattern matching for instance)
- There is a "catch all" called
otherwise(again, like an
elsein the good old if/else approach)
Let's try to run it:
*Main> :l guards.hs [1 of 1] Compiling Main ( guards.hs, interpreted ) Ok, one module loaded. *Main> isItTheWeekEndAlready3 1 False *Main> isItTheWeekEndAlready3 0 True *Main> isItTheWeekEndAlready3 6 True
There's one more thing which is pretty helpful when using guards called
where. It allows us to define a context (using variables) once and use it in the guards. Let's demonstrate it in a function which calculates the ratio of the number of breakfasts you should have eaten in a year (we will assume that everyone understands it is the most important meal of the day). You will get a message based on said ratio:
-- Let's add this to guards.hs numberOfBreakfasts :: Float -> String numberOfBreakfasts nb | ratio < 0.5 = "Oh no" | ratio < 1 = "Not bad" | ratio == 1 = "Perfect" | otherwise = "Tell me your secret" where ratio = nb / 365
As you can see, instead of writing three time this
nb / 365 in each guard, we used
where to define it once. Let's try it out:
*Main> :l guards.hs [1 of 1] Compiling Main ( guards.hs, interpreted ) Ok, one module loaded. *Main> numberOfBreakfasts 100 "Oh no" *Main> numberOfBreakfasts 200 "Not bad" *Main> numberOfBreakfasts 365 "Perfect" *Main> numberOfBreakfasts 500 "Tell me your secret"
Looks good, I personally got "Perfect" but I do not want to brag.
Five down, around five more articles to go, we are half way ! We are done with pattern matching and guards this time, I promise. In our next article we are going to discuss about something a bit more complex but essential to understand Haskell: higher order functions.
If you have a problem and no one else can help. Maybe you can hire the Kalvad-Team.