首页 > 解决方案 > 如何为受 MonadReader 和 MonadIO 约束的函数修复缺少的 IO 实例?

问题描述

我一直在尝试通过将mtl 与persistent结合使用来构建项目,从而对mtl有一个很好的理解。

该项目的一个模块具有使用insertMany_的功能

service
  :: (MonadReader ApplicationConfig m, MonadIO m) =>
     ReaderT SqlBackend (ExceptT ApplicationError m) ()
service = insertMany_ =<< lift talkToAPI

这里talkToAPI可能会失败,因此响应包含在 中ExceptT,它的类型是

ExceptT ApplicationError m [Example]

简而言之,service的工作是与 API 对话,解析响应并将insertMany_响应存储到数据库中。

实际的存储操作由withPostgresqlConn处理

withPostgresqlConn
  :: (MonadUnliftIO m, MonadLogger m) =>
     ConnectionString -> (SqlBackend -> m a) -> m a

runReaderT在我的函数上使用service会产生

ghci> :t runReaderT service
ghci> (MonadReader ApplicationConfig m, MonadIO m) =>
       SqlBackend -> ExceptT ApplicationError m ()

所以要处理这个我相信我需要runExceptT像这样使用

runService :: ConnectionString -> IO ()
runService connStr = either print return
  =<< runStdLoggingT (runExceptT $ withPostgresqlConn connStr $ runReaderT service)

但我得到这两个错误

• No instance for (MonadUnliftIO (ExceptT ApplicationError IO))
        arising from a use of ‘withPostgresqlConn’

• No instance for (MonadReader ApplicationConfig IO)
        arising from a use of ‘service’

这里可能是什么问题?我可能有一个错误,但我不确定在哪里寻找。

标签: haskellmonad-transformershaskell-persistent

解决方案


一个问题是它没有ExceptT ApplicationError IO——事实上也不能——有一个MonadUnliftIO实例。很少有 monad 有这样的例子:(IO最简单的情况)IdentityReader类似的转换器 over IO.

解决方案是在传递给之前“剥离”ExceptT构造函数,而不是之后。也就是说,传递一个值而不是一个值。您可以通过将函数与.servicewithPostgresqlConnSqlBackend -> m (Either ApplicationError ())SqlBackend -> ExceptT ApplicationError m ()runExceptT


我们仍然必须选择一个具体的类型,m以便它满足所需的MonadReader ApplicationConfig m, MonadIO m约束service以及MonadUnliftIO m, MonadLogger m所需的约束withPostgresqlConn。(实际上,我们可以忘记MonadIO因为MonadUnliftIO无论如何都暗示它)。

在您的代码中,您调用runStdLoggingT并期望得到IO. 这意味着m预计将是LoggingT IO. 这很好,因为LoggingT有一个MonadUnliftIO实例,当然还有MonadLogger一个。但是有一个问题:什么满足MonadReader ApplicationConfig约束?哪里来的配置?这就是第二个错误的原因。

解决方案是制作m类似ReaderT ApplicationConfig (LoggingT IO). 该runService函数应该接受一个额外的ApplicationConfig参数,并runReader在调用之前使用配置调用runStdLoggingT


更一般的一点是,monad 转换器通常具有“直通”实例,例如“如果基本 monad 是类型类 C 的实例,则转换后的 monad 也是 C 的实例”。例如。MonadLogger m => MonadLogger (ReaderT r m)_ MonadUnliftIO m => MonadUnliftIO (ReaderT r m)但是这种实例并不总是存在于每个转换器类型类组合中。


推荐阅读