【问题标题】:How to fix missing instance of IO for a function constrained on MonadReader and MonadIO?如何为受 MonadReader 和 MonadIO 约束的函数修复丢失的 IO 实例?
【发布时间】:2020-10-03 05:09:35
【问题描述】:

我一直在尝试通过将mtlpersistent 结合使用来构建一个项目来更好地理解它。

该项目的一个模块有一个使用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

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

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’

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

【问题讨论】:

    标签: haskell monad-transformers haskell-persistent


    【解决方案1】:

    一个问题是ExceptT ApplicationError IO 没有——事实上也不能——有一个MonadUnliftIO 实例。很少有 monad 有这种情况:IO(微不足道的情况)以及 IdentityReader 类似的转换器,而不是 IO

    解决方案是“剥离”ExceptT 构造函数之前service 传递给withPostgresqlConn,而不是之后。也就是说,传递 SqlBackend -&gt; m (Either ApplicationError ()) 值而不是 SqlBackend -&gt; ExceptT ApplicationError m () 值。您可以通过使用 runExceptT 组合函数来获得它。


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

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

    解决方案是使m 类似于ReaderT ApplicationConfig (LoggingT IO)runService 函数应采用额外的ApplicationConfig 参数,并在调用runStdLoggingT 之前使用配置调用runReader


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

    【讨论】:

    • 尤其是runService appConf connStr = either print return =&lt;&lt; (flip runReaderT appConf $ runStderrLoggingT $ withPostgresqlConn connStr $ runExceptT . runReaderT service) 应该进行类型检查。
    猜你喜欢
    • 2020-01-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多