- Monad Transformers – Part 1: An Introduction
Once you’ve learned some basic of “Monad”. The next step in the journey is “Monad Transformers”. In this series, we’ll use the basic building block of Haskell to learn about “Monad Transformers”.
The basic
Let’s start with the most basic possible Monad. The Maybe
monad. Here’s the type signature for Maybe
type:
Maybe a :: Nothing | Just a
Option<T>
.We use Maybe
to represent an optional value. For example. The div :: a -> a -> a
function will throw exception when using 0
as divisor.
> 1 `div` 0
-- Exception: divide by zero
So, instead we could use Maybe
type to turn div
into total function.
div' :: Int -> Int -> Maybe Int
div' x 0 = Nothing
div' x d = Just $ x `div` d
With this, we can safely do a division by using any number as divisor!
> 1 `div'` 0
Nothing
Going further
Next, let’s go one step further. We will still be working with optional value (Maybe a
). But this time, we’ll put it in another type.
1. List of optional values [Maybe a]
If we want to apply some transformation to these list of optional values. How could we do that? Let’s think about it a little bit.
We all know that we could use map
to apply a function to a list of values e.g. [a]
:
map :: (a -> b) -> [a] -> [b]
But what about list of optional values ([Maybe a]
)?
(a -> b) -> [Maybe a] -> [Maybe b]
There’re many ways to implement this. But in order to demonstrate the purpose of Monad Transformers. We’ll implement it via do
notation with binding arrow (<-
).
map'
as map' = fmap . fmap
instead. But here, we want to demonstrate the difference between a simple monad vs. transformer version.[Maybe a]
).
- The first thing we do is to use
<-
arrow to accessMaybe a
inside[]
. i.e.x :: Maybe a
. - Since
x
isMaybe a
, we can usefmap :: (a -> b) -> f a -> f b
to apply function toa
directly.
Let’s see it in action!
> map' (+2) [Just 1, Just 5, Nothing]
[Just 3, Just 7, Nothing]
2. Action with optional value IO (Maybe a)
We’ll write a simple program that asks user to choose whether to continue or stop.
1import Data.Char (toLower)
2
3accept :: IO (Maybe String)
4accept = do
5 s <- getLine
6 if map toLower s == "y"
7 then return $ Just s
8 else return Nothing
9
10main = IO ()
11 result <- accept -- 1
12 case result of -- 2
13 Nothing -> putStrLn "Quit"
14 Just s -> putStrLn $ s ++ ": Continue"
Example 2.1: IO
with optional value (IO Maybe a
).
- This is similar to what we did on previous example on
[Maybe a]
. We use binding arrow (<-
) to accessMaybe String
value. - Next, we perform pattern matching on the result.
Can you spot a pattern?
We might have noticed that there’re some kind of repetitive pattern here. Whenever we want to get a hold of our optional value (Maybe a
). We have to always call <-
arrow first. Even though, we don’t really care what the outermost types are (In our example, []
and IO
respectively).
In example 1.1. We use <-
to extract Maybe a
out from []
.
3 x <- xs -- 1
4 return $ f <$> x
Here again, in example 2.1. We use <-
to extract Maybe a
out from IO
.
11 result <- accept -- 1
12 case result of
Before we introduce our first “Monad transformers”. Let’s take a sneak peek of the new Monad transformers version of our previous examples!
List of optional values
We can see that our transformer version on the right doesn’t need to call x <- xs
to get access to Maybe a
any more:
map' f xs = do
x <- xs -- 1. x :: Maybe a
return $ f <$> x
Before
map'' f xs = f <$> xs
-- `f` operates on `a` directly.
--
After
Action with optional value
Same thing here. The result <- accept
call on the right, now returns String
directly. No need for us to perform a pattern matching on the result like we did previously (before, it returns Maybe String
):
result <- accept -- 1. result :: Maybe String
case result of -- 2.
Nothing -> putStrLn "Quit"
Just s -> putStrLn $ s ++ ": Continue"
Before
result <- accept' -- 1. result :: String
lift $ putStrLn $ result ++ ": Continue"
--
--
After
Summary
In part 1. We started by showing what happened when we need to wrap our monadic values (i.e. Maybe a
) in another type ([]
and IO
). We also showed a glimpse of the transformer version that we’ll cover in next post.
In next post, we’ll take a look in more details on how does it actually work.