Haskell series part 10
This is the tenth and last article of a series on the functional language Haskell for beginners
Thank you for joining us for the tenth and last part of our Haskell series, you will find the previous article here where I explain modules and exceptions.
In this article we are going to cover Maybe
, Just
and Nothing
as an alternative to handling exceptions and errors.
Data Type
But before jumping into the usage of Maybe
, let's have a look at how we can create our own data types. We are going to rewrite the module we wrote previously for currency conversion:
-- This is CurrencyConverterB.hs
module CurrencyConverterB (
CurrencyRate (Rate),
convert
) where
data CurrencyRate = Rate String String Float
convert :: CurrencyRate -> Float -> Float
convert (Rate _ _ rate) amount = amount * rate
-- This is Main.hs
import CurrencyConverterB
eurToUsd :: CurrencyRate
eurToUsd = Rate "eur" "usd" 1.12
usdToEur :: CurrencyRate
usdToEur = Rate "usd" "eur" 0.88
main = do
let eur = convert eurToUsd 10
putStrLn ("10 EUR = " ++ show eur ++ " USD")
let usd = convert usdToEur 10
putStrLn ("10 USD = " ++ show usd ++ " EUR")
Ok let's go step by step, first the CurrencyConvertB
file:
- Just like we saw previously, we use the
module
keyword to define the name of our module and what we want to export out of it. - You will note one subtlety with
CurrencyRate (Rate)
in themodule
, this is because of our data type which we created calledCurrencyRate
.Rate
is the name of the constructor, sometimes developers use the same name for both type and constructor. In order to clarify the difference here I used different names. - Now our type
CurrencyRate
is composed of three things: aString
, anotherString
and aFloat
so that we can keep the base currency, the target currency and the exchange rate. - Finally we create a simple
convert
method which takes aCurrencyRate
and an amount. Then it will simply multiply this amount with the exchange rate. You will note that when usingdata
to create new types, the only way to access fields is through pattern matching. I recommend that you look into "records" to make this part easier, especially if you need more fields.
Now that we understand this module, let's have a look at our new Main
:
- First we import our module
CurrencyConvertB
. - Next we define two functions which uses our
Rate
constructor to create newCurrencyRate
. The syntax is pretty simple:ConstructorName field field
. - Then we call our
convert
method using the rates we defined above and get the same results as our previous code.
Let's run it in GHCI:
ghci> :l Main
[1 of 2] Compiling CurrencyConverterB ( CurrencyConverterB.hs, interpreted )
[2 of 2] Compiling Main ( Main.hs, interpreted )
Ok, two modules loaded.
ghci> main
10 EUR = 11.2 USD
10 USD = 8.8 EUR
Neat, everything gets automatically compiled based on the Main
's imports. Note that you can also compile it and run it like so:
➜ ghc --make Main.hs
[1 of 2] Compiling CurrencyConverterB ( CurrencyConverterB.hs, CurrencyConverterB.o )
[2 of 2] Compiling Main ( Main.hs, Main.o )
Linking Main ...
➜ ./Main
10 EUR = 11.2 USD
10 USD = 8.8 EUR
Maybe
Now, to answer the question: "Why did we learn about data types before Maybe ?"
It is because Maybe
would probably look something like this:
data Maybe a = Just a | Nothing
A few things to note:
- The definition of
Maybe
is parameterized with a type variablea
so we can use it with any type we want. Maybe
has two possible constructor:Just a
orNothing
. When creating a new data type you may add different constructors and separate them with a pipe|
.
The idea behind Maybe
is mainly to check for the presence of a value, if you want to compare it to some other languages it is similar to: "This function returns a user or null". So translated to Haskell it would be: "My function returns Just a user or Nothing".
Let's look at some simple code to understand of usage of Maybe
:
-- this is maybe.hs
import Data.List
main = do
let a = [1, 2, 3, 4, 5]
let result = find (<5) a
putStrLn (show result)
This is a very typical example, we are loading the standard module Data.List
to use the find function. I copied the definition below:
Thefind
function takes a predicate and a structure and returns the leftmost element of the structure matching the predicate, orNothing
if there is no such element.
Let's verify this:
ghci> :l maybe.hs
[1 of 1] Compiling Main ( maybe.hs, interpreted )
Ok, one module loaded.
ghci> main
Just 1
Indeed, we have Just
the first element of the list, being 1. Now let's change the code from <5
to >5
and recompile:
ghci> :l maybe.hs
[1 of 1] Compiling Main ( maybe.hs, interpreted )
Ok, one module loaded.
ghci> main
Nothing
This time we get Nothing
.
Now, let's try to programmatically handle both cases, we will add a few lines to our code in maybe.hs:
-- this is maybe.hs
import Data.List
main = do
let a = [1, 2, 3, 4, 5]
let result = find (<5) a
case result of
Nothing -> putStrLn "Sorry, could not find what you were looking for"
Just b -> putStrLn ("Found your " ++ show b)
We used the same method with a case
statement as in our try example in our previous article. Now let's recompile and run it:
ghci> :l maybe.hs
[1 of 1] Compiling Main ( maybe.hs, interpreted )
Ok, one module loaded.
ghci> main
Found your 1
Let's change again the <
sign:
ghci> :l maybe.hs
[1 of 1] Compiling Main ( maybe.hs, interpreted )
Ok, one module loaded.
ghci> main
Sorry, could not find what you were looking for
Great, everything works as expected !
Conclusion
Ten down ! It took me more than 6 months but we are finally there, 10 articles to introduce important concepts of Haskell to get beginners started in this hidden gem of a language. There are still many concepts to discover such as: Functor
, Monoid
, Monad
and so on. If you want to continue your Haskell experience, I encourage you to subscribe to Haskell Weekly newsletter to get interesting content every week. Until then, good luck in your learning adventure !
If you have a problem and no one else can help. Maybe you can hire the Kalvad-Team.