Monad and Monad Transformer Templates

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.

class Functor f where
  fmap :: (a -> b) -> f a -> f b

class Functor f => Applicative f where
  -- | Lift a value.
  pure :: a -> f a

  -- | Sequential application.
  (<*>) :: f (a -> b) -> f a -> f b
  (<*>) = liftA2 id

  -- | Lift a binary function to actions.
  liftA2 :: (a -> b -> c) -> f a -> f b -> f c
  liftA2 f x = (<*>) (fmap f x)

  -- | Sequence actions, discarding the value of the first argument.
  (*>) :: f a -> f b -> f b
  a1 *> a2 = (id <$ a1) <*> a2
  
  -- | Sequence actions, discarding the value of the second argument.
  (<*) :: f a -> f b -> f a
  (<*) = liftA2 const

class Applicative m => Monad m where
  -- | Sequentially compose two actions, passing any value produced
  -- by the first as an argument to the second.
  (>>=)       :: forall a b. m a -> (a -> m b) -> m b

  -- | Sequentially compose two actions, discarding any value produced
  -- by the first, like sequencing operators (such as the semicolon)
  -- in imperative languages.
  (>>)        :: forall a b. m a -> m b -> m b
  m >> k = m >>= \_ -> k

  -- | Inject a value into the monadic type.
  return :: a -> m a
  return = pure

  -- | Fail with a message.
  fail :: String -> m a
  fail s = errorWithoutStackTrace s

join :: (Monad m) => m (m a) -> m a
join x =  x >>= id

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.

import Control.Applicative (liftA2, Alternative(..))
import Control.Monad (liftM, ap, MonadPlus(..))
import Control.Monad.IO.Class (MonadIO(..))
import Control.Monad.Trans.Class (MonadTrans(..), lift)

data Option a = None | Some a deriving (Eq, Ord, Show)

instance Functor Option where
  fmap _ None     = None
  fmap f (Some a) = Some (f a)

instance Applicative Option where
  pure = Some

  Some f <*> m  = fmap f m
  None   <*> _m = None

  liftA2 f (Some x) (Some y) = Some (f x y)
  liftA2 _ _ _               = None

  Some _m1 *> m2  = m2
  None *> _m2     = None

instance Monad Option where
  (Some x) >>= k      = k x
  None     >>= _      = None
  (>>)                = (*>)
  fail _              = None

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

residentToAddress :: String -> Option String
residentToAddress "Foo" = Some "10 Downing Street"
residentToAddress "Bar" = Some "1600 Pennsylvania Ave NW"
residentToAddress _     = None

addressToPhoneNumber :: String -> Option String
addressToPhoneNumber "10 Downing Street"        = Some "+44 20 7925 0918"
addressToPhoneNumber "1600 Pennsylvania Ave NW" = Some "+1 202-456-1111"
addressToPhoneNumber _                          = None

phoneNumberToAccountNumber :: String -> Option String
phoneNumberToAccountNumber "+44 20 7925 0918" = Some "1111-1111-1111-1111"
phoneNumberToAccountNumber "+1 202-456-1111"  = Some "2222-2222-2222-2222"
phoneNumberToAccountNumber _                  = None 

Without using monads it looks like this:

residentToAccountNumber :: String -> Option String
residentToAccountNumber r =
  case residentToAddress r of
    Some a ->
      case addressToPhoneNumber a of
        Some p -> phoneNumberToAccountNumber p
        None -> None
    None   -> None

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

residentToAccountNumberDoSyntaxSugar :: String -> Option String
residentToAccountNumberDoSyntaxSugar r = do
  address <- residentToAddress r
  phoneNumber <- addressToPhoneNumber address
  residentToAccountNumber phoneNumber  

residentToAccountNumberNoSyntaxSugar :: String -> Option String
residentToAccountNumberNoSyntaxSugar r =
  residentToAddress r >>= \address -> addressToPhoneNumber address >>= \phoneNumber -> residentToAccountNumber phoneNumber

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