使用不同类型的 Monad Transformer 的策略是什么?

What is the strategy for working with different types of Monad Transformers?

我正在尝试实现一个简单的 Web 服务器,它与其他 API 交互并在进行一些处理后存储响应。

为了封装失败的可能性(空响应、不正确的请求等),我使用 ExceptT 如下:

getExample
  :: (MonadIO m, MonadReader ApplicationConfig m)
  => ExceptT ApplicationError m [Example]
getExample = do

  partOfReq <- asks requestForSometing

  fn1 =<< fn2 partOfReq

我还有另一个函数,使用来自 Persistent 的 insertMany_ 将响应存储在数据库中。

storeExample
  :: ( MonadIO m
     , PersistStoreWrite backend
     , PersistEntityBackend Example ~ BaseBackend backend
     )
  => [Example]
  -> ReaderT backend m ()
storeExample = insertMany_

现在我想写一个函数

getResponseAndStore = ... {- A combination of getExample and storeExample -}

这将完成这两件事,并将 ApplicationConfigPersistEntityBackend 要求提升到顶部,用户可以在其中捆绑提供它们。

可以吗?

如果是这样 - strategy/implementation 会是什么?

如果否 - 我应该考虑哪些改变?

编辑:这就是我目前正在做的事情。

getResponseAndStore
  :: ( MonadIO m
     , MonadReader ApplicationConfig m
     , PersistStoreWrite backend
     , PersistEntityBackend Example ~ BaseBackend backend
     )
  => ReaderT backend (ExceptT ApplicationError m) ()
getResponseAndStore = storeExample =<< lift getExample

你不能改用 MonadError 语法吗?

getExample
  :: (MonadIO m, MonadReader ApplicationConfig m, MonadError ApplicationError m)
  => [Example]
getExample = -- ...

getResponseAndStore :: (MonadIO m, MonadReader ApplicationConfig m, PersistStoreWrite backend, PersistEntityBackend Example ~ BaseBackend backend, MonadError ApplicationError m) => -- etc.

我能够制作一个我想要的功能。秘诀是使用 withPostgresqlConn.

process :: ReaderT ApplicationConfig IO (Either ApplicationError ())
process = do

  appConfig <- ask
  connStr   <- asks connectionString

  runStdoutLoggingT
    $ withPostgresqlConn connStr
    $ flip ($) appConfig
    . runReaderT
    . runExceptT
    . runReaderT getResponseAndStore