将 Persistent 与 RIO 日志记录相结合以转储 table

Combining Persistent with RIO logging to dump a table

我正在编写一个玩具示例来学习 Haskell 使用 Persistent 库访问数据库。为了玩,我想看看数据库中有什么(内存中的 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 的理解,那就太好了。

先代替

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 那将是 runReaderTrunReaderT 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 需要其他几个 contexts 才能工作,正如我们在上面看到的。

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)

现在这将进行类型检查。