- 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”.
Let’s start with the most basic possible Monad. The
Maybe monad. Here’s the type signature for
Maybe a :: Nothing | Just a
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
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
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.
map :: (a -> b) -> [a] -> [b]
But what about list of optional values (
(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' = fmap . fmapinstead. But here, we want to demonstrate the difference between a simple monad vs. transformer version.
- The first thing we do is to use
<-arrow to access
x :: Maybe a.
Maybe a, we can use
fmap :: (a -> b) -> f a -> f bto apply function to
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"
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 access
- 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,
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
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
map'' f xs = f <$> xs -- `f` operates on `a` directly. --
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
result <- accept -- 1. result :: Maybe String case result of -- 2. Nothing -> putStrLn "Quit" Just s -> putStrLn $ s ++ ": Continue"
result <- accept' -- 1. result :: String lift $ putStrLn $ result ++ ": Continue" -- --
In part 1. We started by showing what happened when we need to wrap our monadic values (i.e.
Maybe a) in another type (
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.