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
Nothing as an alternative to handling exceptions and errors.
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
- Just like we saw previously, we use the
modulekeyword 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 the
module, this is because of our data type which we created called
Rateis 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
CurrencyRateis composed of three things: a
Floatso that we can keep the base currency, the target currency and the exchange rate.
- Finally we create a simple
convertmethod which takes a
CurrencyRateand an amount. Then it will simply multiply this amount with the exchange rate. You will note that when using
datato 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
- First we import our module
- Next we define two functions which uses our
Rateconstructor to create new
CurrencyRate. The syntax is pretty simple:
ConstructorName field field.
- Then we call our
convertmethod 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
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
Maybeis parameterized with a type variable
aso we can use it with any type we want.
Maybehas two possible constructor:
Nothing. 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
-- 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:
findfunction takes a predicate and a structure and returns the leftmost element of the structure matching the predicate, or
Nothingif 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 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
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
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 !
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:
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.