Just thinking about an API design. What is "common" in Haskell? Transformers in type signature or rather "hidden"?
findById :: ID -> IO (Maybe User)
findById x = runMaybeT $ do
...
return User
or
findById :: ID -> MaybeT IO User
findById x = do
...
return User
If this is for something simple, and it's only a few functions that do this maybe-in-IO, I would just make the type IO (Maybe User)
.
If this is a pattern that stretches across your library, I would give a semi-abstract name to the tfm-stack monad:
type Request = MaybeT IO
findById :: ID -> Request User
... or even
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
newtype Request a = Request (runRequest :: MaybeT IO a)
deriving (Functor, Applicative, Monad)
Making the signature ID -> MaybeT IO User
isn't very good: the transformer only helps if you're doing a whole bunch of actions in that monad, but in that case always writing out MaybeT IO
violates the DRY principle.