Сочетание Persistent с журналированием RIO для создания дампа таблицы

Я пишу игрушечный пример, чтобы изучить доступ к базе данных 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 вместо print, и эта функция должна выполняться в монаде RIO. Вот моя попытка сделать это:

{-# 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)
   |         ^^^^^^^^^^^^^^^^^^^^^^

Я не понимаю, что означает эта ошибка. Было бы здорово, если бы кто-нибудь мог дать мне несколько советов о том, как продолжить и проанализировать эту ошибку, тем самым улучшив мое понимание задействованных классов типов и монад.

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)

Теперь, предполагая эту версию, вы выбрали конкретный тип. IO за dumpTableкоторый не должен вводить проверку.

Функция должна быть написана так

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)

Приведенный выше пример взят из дампа таблицы

Не вдаваясь в подробности, способ удовлетворить эти ограничения ReaderT SqlBackend (NoLoggingT (ResourceT IO)) заключается в использовании преобразователей каждой монады ' 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 ()

Монада env ограничен HasLogFunc но функция rawQueryтребуется несколько других контекстов для работы, как мы видели выше.

Вы можете видеть это из rawQueryтип функции (MonadResource m, MonadReader env m, BackendCompatible SqlBackend env).

По сути, нам нужно помочь GHC, явно указав их в сигнатуре типа. Я не знаю, как вы взаимодействуете сRIO монада, но в самом общем случае должен выглядеть так

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)

Теперь это будет проверка типа.

Другие вопросы по тегам