Monad transformer

This week, let’s talk about Haskell. I has been learning Haskell in my free time for a while and i am really enjoy it. I recommend everybody to learn it, whether you can use it for your day job or not, because it will give you a very different view and way of thinking to improve your programming skills.

Let’s start with a very simple problem likes this: given a user id, we want to find all posts of that user.

First, we find the user by calling findUser. If we find the user, we will call findPost to get all posts of that user. Notice the implementation of getPosts, we need to unwrap Maybe, pattern match on it.

In case you are new to Haskell, IO is the monad that allows you do IO side effect in Haskell, read file, print to console, make network, api call. Maybe is another monad to represent computation that might go wrong by not returning a value. So when you see Maybe User, it means this function may return a user, may be not. That’s why we need to check if the Maybe value is Just or Nothing. And IO (Maybe User) means this function will make some IO call to outside, can read a file, query database and returns a Maybe User.

Let’s have another example to see the problem more clearly. Now we has another requirement, only friends can see all posts of each other. So we need to check friend relationship before return all posts.

Now, the code looks really ugly now. We have a deep nested code, each step we need to manual unwrap Maybe value, process if it has value, otherwise return Nothing. This looks really bad not only because of nested steps, but also because we can’t leverage Maybe monad feature.

Let’s recall the Monad instance of Maybe and IO.

In Maybe monad, the computation chains will stop when Nothing is returned from any computation, so Nothing >>= f will return Nothing. In IO monad, it will run each computation sequencely. So if we mix them together, our IO Maybe mond which behave like IO normal monad. But in this case, we want our IO Maybe to also have behaviour of Maybe monad. If any computation returns Nothing, it should stop and return Nothing.

do notation is a syntax sugar in haskell


So the main problem here is we use two monad IO and Maybe together, and right now they don’t work well with each other. We only use feature of IO monad and not Maybe monad. To illustrate the idea, here is what will happens if we write our code with only Maybe monad.

It looks nicer, right? We don’t need to check for Just and Nothing at every step, we depend on the monad instance of Maybe to take care of it.

If only we can make our IO Maybe behave like both IO, which can make IO call to outside world, and Maybe, represent a computation may return nothing and stop as soon as one function returns Nothing, we will be able to make the code above less verbose and more expressive. Let’s try to create that new monad together, we will call it MaybeIO monad.

Now before we rewrite getFriendPosts, we need to provide monad instance for our MaybeIO.

Basically remember our MaybeIO is actually IO Maybe, so we write our new monad based on functionality of these two. For the (>>=) function, we called runMaybeIO x, which return us a IO Maybe. Then we use (>>=) instance of IO to get back a, which is Maybe monad.

If you confuse because of runMaybeIO and MaybeIO constructor, here are their signatures:

We use them to convert between our new MaybeIO monad and the original IO Maybe.

Now it’s time to rewrite our getFriendPosts

It looks like the example with Maybe monad above, right? We’ve just combined IO and Maybe together into a single monad, which behave like Maybe and have IO capability. If we go one step further, generalize it, instead of MaybeIO, we want to use Maybe with any other monad, add Maybe behaviour to it.

Instead of MaybeIO, we have MaybeT m with m is another monad such as IO. Our MaybeIO above become MaybeT IO. We have finished written our first Monad Transformer, MaybeT.

Monad Transformer allows us to stack multiple monads together and behave likes one single monad, combined all functionalities of these monads inside the stack. For example, if we want to build a real application, we need to read global config (Reader monad), write log (Writer monad), store application state (State monad), handling exception (Either monad), and of course IO monad. We can write a ReaderT AppConfig (WriterT String (StateT AppState (ExceptT String IO))) monad to represent our application and reuse all functionality of these monads.

That’s all for this post. We saw two real world problems. We learn to write a custom MaybeIO monad which combined Maybe and IO together to solve the problem. After that we generalize it into MaybeT monad transformer and learn how monad transformer can help us in real application by stack multiple monad together.

Next time, we will look at mtl and transformers, two monad transformer libraries in Haskell ecosystem, which provide us the transformer version of all standard monads so we don’t need to write our own. We will also look at the order of the monad in the stack and how it will effect behaviour of the whole stack.

Thank you.

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.