【问题标题】:Combining Persistent with RIO logging to dump a table将 Persistent 与 RIO 日志记录相结合以转储表
【发布时间】:2020-07-02 07:20:30
【问题描述】:

我正在编写一个玩具示例来学习使用 Persistent 库访问 Haskell 数据库。为了玩,我想看看数据库中有什么(内存中的 SQLite):

import qualified Database.Persist.Sql          as PSQL
import qualified Data.Conduit.List             as CL
import           Data.Conduit                   ( ($$) )
import           Control.Monad.IO.Class         (liftIO)

dumpTable :: Text -> IO ()
dumpTable tableName = PSQL.rawQuery "select * from " <> tableName [] $$ CL.mapM_ (liftIO . print)

(取自 Haskell 学院)

因为我想为我的应用程序使用 RIO 库,所以上述方法不起作用:我需要使用 RIO 日志记录函数之一而不是打印,并且该函数必须在 RIO monad 中运行。这是我的尝试:

{-# LANGUAGE NoImplicitPrelude          #-}
{-# LANGUAGE OverloadedStrings          #-}
[..]

import           RIO
import qualified Database.Persist.Sql          as PSQL
import           Data.Conduit                   ( ($$) )
import qualified Data.Conduit.List             as CL

dumpTable :: (HasLogFunc env) => Text -> RIO env ()
dumpTable tableName =
    let query = "select * from " <> tableName
    in  PSQL.rawQuery query [] $$ CL.mapM_ (logInfo . displayShow)

但是,此代码不进行类型检查。我收到以下错误:

    • Could not deduce (PSQL.BackendCompatible PSQL.SqlBackend env)
        arising from a use of ‘PSQL.rawQuery’
      from the context: HasLogFunc env
        bound by the type signature for:
                   dumpTable :: forall env. HasLogFunc env => Text -> RIO env ()
        at src/Persistence/DbInspect.hs:13:1-51
    • In the first argument of ‘($$)’, namely ‘PSQL.rawQuery query []’
      In the expression:
        PSQL.rawQuery query [] $$ CL.mapM_ (logInfo . displayShow)
      In the expression:
        let query = "select * from " <> tableName
        in PSQL.rawQuery query [] $$ CL.mapM_ (logInfo . displayShow)
   |
16 |     in  PSQL.rawQuery query [] $$ CL.mapM_ (logInfo . displayShow)
   |         ^^^^^^^^^^^^^^^^^^^^^^

我不明白这个错误是什么意思。如果有人能给我一些关于如何继续分析这个错误的提示,那就太好了,从而提高我对所涉及的类型类和 monad 的理解。

【问题讨论】:

标签: haskell persistent conduit rio


【解决方案1】:

首先代替

dumpTable :: Text -> IO ()
dumpTable tableName = PSQL.rawQuery "select * from <> tableName" [] $$ CL.mapM_ (liftIO . print)

你可能想要这个

dumpTable :: Text -> IO ()
dumpTable tableName = PSQL.rawQuery ("select * from " <> tableName) [] $$ CL.mapM_ (liftIO . print)

现在假设这个版本,你在这里所做的是你为dumpTable选择了一个具体类型IO不应该类型检查。

函数应该这样写

dumpTable
  :: (MonadResource m, MonadReader env m,
      BackendCompatible SqlBackend env) =>
     Text -> m ()
dumpTable tableName = PSQL.rawQuery ("select * from " <> tableName)  [] $$ CL.mapM_ (liftIO . print)

我不知道您可能指的是哪个具体示例,但runQuery 的简单示例看起来像这样

main :: IO ()
main = runSqlite ":memory:" $ do
    buildDb
    dumpTable

buildDb
  :: ReaderT SqlBackend (NoLoggingT (ResourceT IO)) (Key Tutorial)
buildDb = do
    runMigrationSilent migrateTables
    insert $ Tutorial "Basic Haskell" "https://fpcomplete.com/school/basic-haskell-1" True
    insert $ Tutorial "A monad tutorial" "https://fpcomplete.com/user/anne/monads" False
    insert $ Tutorial "Yesod usage" "https://fpcomplete.com/school/basic-yesod" True
    insert $ Tutorial "Putting the FUN in functors" "https://fpcomplete.com/user/anne/functors" False
    insert $ Tutorial "Basic Haskell" "https://fpcomplete/user/anne/basics" False


dumpTable
  :: ReaderT SqlBackend (NoLoggingT (ResourceT IO)) ()
dumpTable = rawQuery "select * from Tutorial" [] $$ CL.mapM_ (liftIO . print)

以上示例来自Dumping a table

无需过多介绍,满足这些约束ReaderT SqlBackend (NoLoggingT (ResourceT IO)) 的方法是使用每个monad 转换器的run 函数。对于ReaderT,这将是runReaderT,即runReaderT configData $ monadReaderConstraiendFunction

无论如何,在深入研究这个库之前,您可能想看看Monad Transformers 是如何工作的。不会花太长时间,一旦你掌握了它的要点,你就可以调试任何进一步的问题。

说到这里,现在让我们来看看这部分错误信息

• Could not deduce (PSQL.BackendCompatible PSQL.SqlBackend env)
    arising from a use of ‘PSQL.rawQuery’
  from the context: HasLogFunc env

和你的函数类型

dumpTable :: (HasLogFunc env) => Text -> RIO env ()

monad env 受限于 HasLogFunc 但函数 rawQuery 需要其他几个上下文才能工作,正如我们在上面看到的那样。

rawQuery的函数类型(MonadResource m, MonadReader env m, BackendCompatible SqlBackend env)可以看出。

基本上,我们需要通过在其类型签名中明确定义这些来帮助 GHC。我不知道你是如何与RIO monad 交互的,但最一般的情况应该是这样的

dumpTable :: (HasLogFunc env
             , PSQL.BackendCompatible PSQL.SqlBackend env
             , MonadResource (RIO env)
             )
          => Text
          -> RIO env ()
dumpTable tableName =
    let query = "select * from " <> tableName
    in  PSQL.rawQuery query [] $$ CL.mapM_ (logInfo . displayShow)

现在这将输入检查。

【讨论】:

  • 非常感谢您的详细解释!我的结论是,堆栈 ReaderT - LoggerT - ResourceT - RIO 似乎存在更普遍的问题。 RIO 是 ReaderT over IO。上面堆叠了另一个 ReaderT,中间有更多的转换器,而 RIO 提倡使用 typeclass 方法来添加效果。我不明白这两种方法是如何结合在一起的——这令人惊讶,因为它们是由同一位作者编写的。所以,我想我在这里遗漏了一些东西。
  • 您可以尝试按顺序“展开”每个转换器。 dumpTable 将需要限制在通用 m 上,您可以在 RIO 内展开它,它本身不是变压器。类似{- query runner function -} $ flip ($) appConfig . runReaderT . {- more unwrapping -} . runReaderT {- backend -}
猜你喜欢
  • 2011-06-11
  • 1970-01-01
  • 2019-01-11
  • 1970-01-01
  • 1970-01-01
  • 2017-09-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多