Просто думаю о дизайне API. Что такое «общее» в Haskell? Трансформеры в подписи типа или точнее "спрятанные"?
findById :: ID -> IO (Maybe User)
findById x = runMaybeT $ do
...
return User
или
findById :: ID -> MaybeT IO User
findById x = do
...
return User
Если это для чего-то простого, и всего несколько функций делают это возможно-в-IO, я бы просто сделал тип IO (Maybe User)
.
Если это шаблон, который распространяется на всю вашу библиотеку, я бы дал полуабстрактное имя монаде tfm-stack:
type Request = MaybeT IO
findById :: ID -> Request User
... или даже
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
newtype Request a = Request (runRequest :: MaybeT IO a)
deriving (Functor, Applicative, Monad)
Делать подпись ID -> MaybeT IO User
не очень хорошо: трансформер помогает, только если вы делаете целую кучу действий в этой монаде, но в этом случае всегда выписывание MaybeT IO
нарушает принцип DRY.
Третий вариант для рассмотрения: один из
(MonadIO m, Alternative m) => ID -> m User
, если действительно нет интересной информации об ошибках, о которой вы могли бы сообщить, или(MonadIO m, MonadError MyFancyError m) => ID -> m User
, если она есть.