This post is part of the series Monad Transformers
  1. 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
Other programming languages usually represent this type with 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 (<-).

In practice, we would define map' as map' = fmap . fmap instead. But here, we want to demonstrate the difference between a simple monad vs. transformer version.

1map' :: (a -> b) -> [Maybe a] -> [Maybe b]
2map' f xs = do
3  x <- xs          -- 1
4  return $ f <$> x -- 2
Example 1.1: List of optional values ([Maybe a]).

  1. The first thing we do is to use <- arrow to access Maybe a inside []. i.e. x :: Maybe a.
  2. Since x is Maybe a, we can use fmap :: (a -> b) -> f a -> f b to apply function to a 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).

  1. This is similar to what we did on previous example on [Maybe a]. We use binding arrow (<-) to access Maybe String value.
  2. 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.