Hopefully this will be the first in a series of posts about monad transformers. I’m not saying anything new here and there are other resources online that go through this area starting from more basic principles. My goal is to give an overview of transformers and then to explore some of the transformer libraries in later posts.
I’m trying to keep things short and sweet so it’ll be mostly code with a few comments:
module TransformersPart1 where
import Text.Read (readMaybe)
-- plain old Maybe example (avoiding division by zero)
ex1 :: Int -> Int -> Maybe Int
ex1 x y | y > 0 = Just (x `div` y)
| otherwise = Nothing
-- now we have two Monad layers. Maybe and IO.
ex2 :: IO (Maybe Int)
ex2 = readMaybe <$> getLine
-- using the previous example, we now want to map the Int to a String
-- we fmap twice; once on the IO and then on the Maybe.
ex3 :: IO (Maybe String)
ex3 = fmap (fmap show) ex2
-- we can actually do this for any two functors though
-- because functors compose
ex4 :: (Functor f, Functor g) => (a -> b) -> f (g a) -> f (g b)
ex4 f a = fmap (fmap f) a
-- but when we try to bind we have issues
-- we can do it if we know about one of the Monads.
ex5 :: Monad m => m (Maybe a) -> (a -> m (Maybe b)) -> m (Maybe b)
ex5 a f = a >>= (maybe (return Nothing) f)
-- but you can't write bind for any two arbitrary monads
ex6 :: (Monad m, Monad n) => m (n a) -> (a -> m (n b)) -> m (n b)
ex6 = error "try implementing it"
-- With transformers, you have a monad instance for the transformer
-- where there's a concrete monad and any other monad on top. Hence
-- Monad transformers such as MaybeT, EitherT, etc. The monad instance has to
-- be defined with regard to some known, conrete monad + an arbitrary monad.
-- the type is `newtype MaybeT m a` so we can use `ex2` to construct one
ex7 :: MaybeT IO Int
ex7 = MaybeT $ ex2
-- the monad instance for MaybeT lets us to a single bind
ex8 :: MaybeT IO Int
ex8 = do
x <- ex7
return (x + 1)