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.

type Post = String type UserId = String data User = User { _userId :: UserId } findUser :: String -> IO ( Maybe User ) = undefined findUser findPost :: User -> IO ( Maybe [ Post ]) ]) = undefined findPost getPosts :: UserId -> IO ( Maybe [ Post ]) ]) = do getPosts userId <- findUser userId maybeUserfindUser userId case maybeUser of maybeUser Just user -> findPost user userfindPost user Nothing -> return Nothing

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.

isFriend :: User -> User -> IO ( Maybe Bool ) = undefined isFriend getFriendPosts :: UserId -> UserId -> IO ( Maybe [ Post ]) ]) = do getFriendPosts userId friendId <- findUser userId maybeUserfindUser userId case maybeUser of maybeUser Just user -> do user <- findUser friendId maybeFriendUserfindUser friendId case maybeFriendUser of maybeFriendUser Just friendUser -> do friendUser <- getRelationShip user friendUser maybeIsFriendgetRelationShip user friendUser case maybeIsFriend of maybeIsFriend -- I really want to give up this blog post at this point. Just isFriend -> do isFriend if isFriend isFriend then getPosts friendId else return Nothing Nothing -> return Nothing Nothing -> return Nothing Nothing -> return Nothing

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 .

instance Monad Maybe where Nothing >>= f = Nothing Just x >> f = f x f x -- This is not the real implementation of IO monad but you can get the idea instance Monad IO where >>= action2) world0 = (action1action2) world0 let (a, world1) = action1 world0 (a, world1)action1 world0 = action2 a world1 (b, world2)action2 a world1 in (b, world2) (b, world2)

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

= do run runA runB

is

= runA >>= \_ -> runB runrunA\_runB

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.

type Post = String type UserId = String data User = User { _userId :: UserId } findUser :: String -> Maybe User = undefined findUser findPost :: User -> Maybe [ Post ] = undefined findPost getPosts :: UserId -> Maybe [ Post ] = do getPosts userId <- findUser userId userfindUser userId findPost user isFriend :: User -> User -> Maybe Bool = undefined isFriend getFriendPosts :: UserId -> UserId -> Maybe [ Post ] = do getFriendPosts userId friendId <- findUser userId userfindUser userId <- findUser friendId friendfindUser friendId <- isFriend user friend friendCheckisFriend user friend if friendCheck friendCheck then getPosts friendId else Nothing

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.

newtype MaybeIO a = MaybeIO { runMaybeIO :: IO ( Maybe a) } a) } findUser :: String -> MaybeIO User = undefined findUser findPost :: User -> MaybeIO [ Post ] = undefined findPost isFriend :: User -> User -> MaybeIO Bool = undefined isFriend

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

instance Functor MaybeIO where fmap f = MaybeIO . fmap ( fmap f) . runMaybeIO f)runMaybeIO instance Applicative MaybeIO where pure a = MaybeIO $ return ( Just a) a) f <*> a = MaybeIO $ ( <*> ) <$> (runMaybeIO f) <*> (runMaybeIO a) (runMaybeIO f)(runMaybeIO a) instance Monad MaybeIO where return a = MaybeIO $ return ( Just a) a) x >>= f = MaybeIO $ runMaybeIO x >>= \a -> case a of runMaybeIO x\a Just value -> runMaybeIO $ f value valuerunMaybeIOf value Nothing -> return Nothing

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:

MaybeIO :: IO ( Maybe a) -> MaybeIO a a) runMaybeIO :: MaybeIO a -> IO ( Maybe a) a)

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

Now it’s time to rewrite our getFriendPosts

getFriendPosts :: UserId -> UserId -> MaybeIO [ Post ] = do getFriendPosts userId friendId <- findUser' userId userfindUser' userId <- findUser' friendId friendfindUser' friendId <- isFriend' user friend friendCheckisFriend' user friend if friendCheck friendCheck then getPosts' friendId else return mempty

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.

newtype MaybeT m a = MaybeT { runMaybeT :: m( Maybe a) } m am(a) } instance Monad m => Functor ( MaybeT m) where m) fmap f = MaybeT . fmap ( fmap f) . runMaybeT f)runMaybeT instance Monad m => Applicative ( MaybeT m) where m) pure a = MaybeT $ return ( Just a) a) f <*> a = MaybeT $ ( <*> ) <$> (runMaybeT f) <*> (runMaybeT a) (runMaybeT f)(runMaybeT a) instance Monad m => Monad ( MaybeT m) where m) return a = MaybeT $ return ( Just a) a) x >>= f = MaybeT $ runMaybeT x >>= \a -> case a of runMaybeT x\a Just value -> runMaybeT $ f value valuerunMaybeTf value Nothing -> return Nothing

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.