Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The usage of monad transformers

Tags:

haskell

I am reading about monad transformers in the Haskell book.

The author mentioned following:

What about Monad? Thereā€™s no problem composing two arbitrary datatypes that have Monad instances. We saw this already when we used Compose with Maybe and list, which both have Monad instances defined. However, the result of having done so does not give you a Monad.

The issue comes down to a lack of information. Both types Compose is working with are polymorphic, so when you try to write bind for the Monad, youā€™re trying to combine two polymorphic binds into a single combined bind. This, it turns out, is not possible:

{-# LANGUAGE InstanceSigs #-}
-- impossible.
instance (Monad f, Monad g) => Monad (Compose f g) where
  return = pure
  (>>=) :: Compose f g a
  -> (a -> Compose f g b)
  -> Compose f g b
  (>>=) = ???

These are the types weā€™re trying to combine, because š¯‘“ and š¯‘” are necessarily both monads with their own Monad instances:

Monad f => f a -> (a -> f b) -> f b
Monad g => g a -> (a -> g b) -> g b

From those, we are trying to write this bind:

(Monad f, Monad g) => f (g a) -> (a -> f (g b)) -> f (g b)

Or formulated differently:

(Monad f, Monad g) => f (g (f (g a))) -> f (g a)

And this is not possible. Thereā€™s not a good way to join that final š¯‘“ and š¯‘”. Itā€™s a great exercise to try to make it work, because the barriers youā€™ll run into are instructive in their own right. You can also read Composing monads1 by Mark P. Jones and Luc Duponcheel to see why itā€™s not possible.

I could not figure it out. What does he mean? What, exactly, is a monad transformer and what is it good for?

like image 854
softshipper Avatar asked Jan 04 '23 07:01

softshipper


1 Answers

The author tries to say that the composition of any two monads isn't possible. This isn't because language is bad, but because there are monads whose composition isn't a monad.

For instance, IsntMonad isn't a monad:

newtype IsntMonad a = IsntMonad (Maybe (IO a))

instance Monad IsntMonad where
    m >>= k = ???

But, IsMonad is a monad:

newtype IsMonad a = IsMonad { runIsMonad :: IO (Maybe a) }

instance Monad IsMonad where
    (IsMonad ioma) >>= k = IsMonad $ do
        ma <- ioma
        case ma of
          Just a -> runIsMonad $ k a
          Nothing -> return Nothing

So, monad transformers it's just a way to make possibility of composition. But how it works, if we know that it isn't possible in general case? Yes, it's not possible for any two monads, but it's possible for some concrete monad and any other monad.

So, there are monads which can be compose with any other monad. Such monads in the first approximation are monad transformers.

For instance, the monad Maybe can be compose with any other monad like this: Monad m => m (Maybe a), so m (Maybe a) is a monad. But how we can do instance of monad for it?

instance Monad m => Monad ??? where ...

Then MaybeT appears as helper, it is called a monad transformer (the suffix T hints on that).

newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }

instance Monad m => Monad (MaybeT m) where
    m >>= k = MaybeT $ do
        ma <- runMaybeT m
        case ma of
          Just a -> runMaybeT $ k a
          Nothing -> return Nothing

After that, automatically MaybeT IO, MaybeT [], MaybeT STM ... are monads. You don't need to write instances for them. This is what they are good for.


We understood what monad transformers are just way to composition of monads. But it isn't an answer on: What is the composition of monads good for? Why did we spend our time and energy on finding a way to combine monads?

Well, as you known, any monad associated with some sort of effects. If we compose two monads we also compose them effects and this result we will be get automatically.

For example:

StateT s Maybe a -- a computation with a state `s` which can fail
ReaderT e Maybe a -- a computation with access to an environment `e` and option to fail
ReaderT e (StateT s Maybe) a -- a computation with state `s`, access to an environment `e` and option to fail
...

Composition of monads isn't commutative. For example, MaybeT (State s) isn't same thing as StateT s Maybe. A simple way to understand what effects will be produced by the composition is to remember that effects are produced from the inner monad to the outer monad. MaybeT (State s) the first effect will be produced by State s monad then by Maybe. But in StateT s Maybe the first effect will be produced by Maybe then by State s. This means that in cases of failure in StateT s Maybe, we lose state, but inMaybeT (State s)we save the state in which the error occurred.

So, the composition of monads is a simple way to build more complex monads just on type level.

How can this be applied in practice? For example, let's image situation. You have something and options to transform it. Imperative code can look like this:

thing = defaultValue;
if (transformByOption0)
    doTransformationByOption0(thing);
if (transformByOption1)
    doTransformationByOption1(thing);
...

In Haskell we can write:

thing <- flip execStateT defaultValue $ do
    when transformByOption0 $
        modify doTransformationByOption0
    when transformByOption1 $
        modify doTransformationByOption1
    when transformByOption2 $
        put =<< doMonadicTransformationByOption2 =<< get
    ...

What happend here? I locally wrapped some of my monad (it could be any) a monad State MyThing so that it would be easy and simple to solve this problem. It's just a one from billion examples where composition of monads can easily solve your problems.

like image 188
freestyle Avatar answered Jan 12 '23 02:01

freestyle