如何为受 MonadReader 和 MonadIO 约束的函数修复缺少的 IO 实例?
How to fix missing instance of IO for a function constrained on MonadReader and MonadIO?
我一直在努力更好地理解 mtl by building a project using it in combination with persistent。
该项目的一个模块具有使用 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’
这可能是什么问题?我这边可能有误,但我不确定去哪里找。
一个问题是 ExceptT ApplicationError IO
doesn't—and in fact can't—have an MonadUnliftIO
实例。很少有 monad 有这样的实例:IO
(平凡的情况)以及 Identity
和 Reader
-like transformers over IO
.
解决方案是 "peel" ExceptT
构造函数 在 将 service
传递给 withPostgresqlConn
之前,而不是之后。也就是说,传递 SqlBackend -> m (Either ApplicationError ())
值而不是 SqlBackend -> ExceptT ApplicationError m ()
值。您可以通过使用 runExceptT
.
组合函数来获得它
我们仍然需要 select 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 转换器通常有 "passthrough" 个实例,这些实例表示 "if the base monad is an instance of typeclass C, then the transformed monad is also an instance of C" 之类的东西。例如 MonadLogger m => MonadLogger (ReaderT r m)
之 MonadUnliftIO m => MonadUnliftIO (ReaderT r m)
。但是这样的实例并不总是存在于每个转换器类型类组合中。
我一直在努力更好地理解 mtl by building a project using it in combination with persistent。
该项目的一个模块具有使用 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’
这可能是什么问题?我这边可能有误,但我不确定去哪里找。
一个问题是 ExceptT ApplicationError IO
doesn't—and in fact can't—have an MonadUnliftIO
实例。很少有 monad 有这样的实例:IO
(平凡的情况)以及 Identity
和 Reader
-like transformers over IO
.
解决方案是 "peel" ExceptT
构造函数 在 将 service
传递给 withPostgresqlConn
之前,而不是之后。也就是说,传递 SqlBackend -> m (Either ApplicationError ())
值而不是 SqlBackend -> ExceptT ApplicationError m ()
值。您可以通过使用 runExceptT
.
我们仍然需要 select 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 转换器通常有 "passthrough" 个实例,这些实例表示 "if the base monad is an instance of typeclass C, then the transformed monad is also an instance of C" 之类的东西。例如 MonadLogger m => MonadLogger (ReaderT r m)
之 MonadUnliftIO m => MonadUnliftIO (ReaderT r m)
。但是这样的实例并不总是存在于每个转换器类型类组合中。