haskell join multi-level monad

322 Views Asked by At

I'm learning haskell, and trying to use applicative functors as much as possible instead of monad. It is very neat and easy to compose. However, occasionally some types like IO [IO [a]] or IO Maybe IO Maybe a will rise in the code, which cause me big trouble. Apparently monad become inevitable in these scenarios.

I know there is a flat operation like join:: m (m a) -> m a for single level monads. Is there anything similar for multi-level monads? Anything in monad transformers?

Thanks a lot!

2

There are 2 best solutions below

7
On BEST ANSWER

If you note that m (n _) is a monad transformer then you can define this operation. This is certainly the case when we notice that IO (Maybe a) is the same as MaybeT IO a: we then just use MaybeT's join. We can do this in no small part due to the fact that Maybe and IO "layer" especially nicely.

On the other hand, not all "composed" monads are monad transformers. In particular, IO [a] isn't really one. The true ListT transformer we'd like to have and to join looks like

newtype ListT m a = ListT { runListT :: m (Maybe (a, ListT m a)) }

It is the case that we can turn IO [a] into ListT IO a and visa-versa, but it is not the case that these operations invert one another. Truly, IO [a] is not a monad in its own right and cannot be joined.

1
On

Monads do not commute in general, but you can provide all particular cases, that you need:

{-# LANGUAGE MultiParamTypeClasses #-}

import Control.Monad

class (Monad m, Monad n) => Swappable m n where
    swap :: m (n a) -> n (m a)

instance Swappable [] IO where
    swap = sequence

instance Swappable Maybe IO where
    swap  Nothing  = return Nothing
    swap (Just mx) = fmap return mx

cut :: Swappable m n => m (n (m a)) -> n (m a)
cut = liftM join . swap

squash :: Swappable m n => n (m (n (m a))) -> n (m a)
squash = (>>= cut)

An example:

x :: IO [IO [()]]
x = return $ map (\s -> putStrLn s >> return [()]) ["ab","c"]

y :: IO (Maybe (IO (Maybe ())))
y = return $ Just $ putStrLn "z" >> return (Just ())

main = squash x >> squash y

prints

ab
c
z

EDIT

You can avoid defining new typeclass by providing an instance of Traversable (there are instances for Maybe and [] in Data.Traversable):

import Data.Traversable as T
import Control.Monad

cut :: (Traversable t, Monad t, Monad m) => t (m (t a)) -> m (t a)
cut = liftM join . T.sequence

squash :: (Traversable t, Monad t, Monad m) => m (t (m (t a))) -> m (t a)
squash = (>>= cut)