Monad and Monad Transformer Templates

2019-01-01
haskellmonadmonad transformer

This is just a quick reference for implementing monad and monad transform type class instances. Monad transformers in particular require a lot of boilerplate code. Monad transformers allow us to combine multiple monads like Maybe, Reader and IO into a single monad stack and access the capabilities of each monad. A common stack is ReaderT Env IO, where Env may contain mutable references. This is generally recommended over using WriterT and StateT for performance and safety issues. You can read more about best practices in this FPComplete article: ReaderT Design Pattern.

Monad Template

You should be generally familiar with how Functor, Applicative and Monad are defined in GHC. Here is a slightly redacted version.

These are my one sentence motivations (not definitions) for using these type classes in Haskell:

  • Functor: apply a function to a value/values in a container without removing the container.

  • Applicative: build values from independent computations.

  • Monad: build values from interdependent computations.

Let’s define our own type and instances for these three type classes. I use the naming scheme from OCaml Option so we can compile and avoid name clashes with Maybe.

Here are some motivating functions for our Monad instance. We have three functions that we want to combine:

Without using monads it looks like this:

Using monads (particularly with do-syntax) we can simplify it:

Monad Transformer Template

newtype OptionT m a = OptionT { runOptionT :: m (Option a) }

mapOptionT :: (m (Option a) -> n (Option b)) -> OptionT m a -> OptionT n b
mapOptionT f = OptionT . f . runOptionT

instance (Functor m) => Functor (OptionT m) where
  fmap f = mapOptionT (fmap (fmap f))

instance (Functor m, Monad m) => Applicative (OptionT m) where
  pure = OptionT . pure . Some

  mf <*> mx = OptionT $ do
      mb_f <- runOptionT mf
      case mb_f of
        None -> pure None
        Some f  -> do
          mb_x <- runOptionT mx
          case mb_x of
            None -> pure None
            Some x  -> pure (Some (f x))

  m *> k = m >>= \_ -> k

instance (Monad m) => Monad (OptionT m) where
  return = OptionT . pure . Some

  x >>= f = OptionT $ do
      v <- runOptionT x
      case v of
        None   -> pure None
        Some y -> runOptionT (f y)

  fail _ = OptionT (pure None)

instance MonadTrans OptionT where
  lift = OptionT . liftM Some

instance (MonadIO m) => MonadIO (OptionT m) where
  liftIO = lift . liftIO

instance Monad m => Alternative (OptionT m) where
  empty   = OptionT $ pure None
  x <|> y = OptionT $ do
              ov <- runOptionT x
              case ov of
                None   -> runOptionT y
                Some _ -> pure ov

instance Monad m => MonadPlus (OptionT m) where 
  mzero = empty
  mplus = (<|>)

residentToAddressMT :: IO () -> String -> OptionT IO String
residentToAddressMT action "Foo" = do liftIO action; OptionT . pure $ Some "10 Downing Street"
residentToAddressMT action "Bar" = do liftIO action; OptionT . pure $ Some "1600 Pennsylvania Ave NW"
residentToAddressMT action _     = do liftIO action; OptionT . pure $ None

residentToAccountNumberMonadTransformer :: (OptionT IO String)
residentToAccountNumberMonadTransformer = do
  r  <- liftIO getLine
  _a <- residentToAddressMT (print "printing from residentToAddressMT") r

  -- you can wrap pure Maybe Monad
  OptionT . pure $ do
    address     <- residentToAddress r
    phoneNumber <- addressToPhoneNumber address
    phoneNumberToAccountNumber phoneNumber

main :: IO ()
main = do
  oaccount <- runOptionT residentToAccountNumberMonadTransformer
  print oaccount