Использование стойких изнутри кабелепровода

Сначала, упрощенная версия задачи, которую я хочу выполнить: у меня есть несколько больших файлов (в размере 30 ГБ), которые я хочу обрезать для повторяющихся записей. С этой целью я устанавливаю базу данных хэшей данных и открываю файлы один за другим, хэшируя каждый элемент и записывая его в базу данных и выходной файл, если его хэш еще не был в базе данных.

Я знаю, как это сделать с помощью итераций, счетчиков, и я хотел попробовать каналы. Я также знаю, как это сделать с помощью кабелепроводов, но теперь я хочу использовать кабелепроводы и настойчивые. У меня проблемы с типами и, возможно, со всей концепцией ResourceT.

Вот некоторый псевдокод, чтобы проиллюстрировать проблему:

withSqlConn "foo.db" $ runSqlConn $ runResourceT $ 
     sourceFile "in" $= parseBytes $= dbAction $= serialize $$ sinkFile "out"

Проблема заключается в функции dbAction. Естественно, я хотел бы получить доступ к базе данных. Поскольку действие, которое он выполняет, в основном является только фильтром, я сначала подумал написать его так:

dbAction = CL.mapMaybeM p
     where p :: (MonadIO m, MonadBaseControl IO (SqlPersist m)) => DataType -> m (Maybe DataType)
           p = lift $ putStrLn "foo" -- fine
           insert $ undefined -- type error!
           return undefined

Конкретная ошибка, которую я получаю:

Could not deduce (m ~ b0 m0)
from the context (MonadIO m, MonadBaseControl IO (SqlPersist m))
  bound by the type signature for
             p :: (MonadIO m, MonadBaseControl IO (SqlPersist m)) =>
                           DataType -> m (Maybe DataType)
  at tools/clean-wac.hs:(33,1)-(34,34)
  `m' is a rigid type variable bound by
      the type signature for
        p :: (MonadIO m, MonadBaseControl IO (SqlPersist m)) =>
                      DataType -> m (Maybe (DataType))
      at tools/clean-wac.hs:33:1
Expected type: m (Key b0 val0)
  Actual type: b0 m0 (Key b0 val0)

Обратите внимание, что это может быть связано с неправильными предположениями, которые я сделал при разработке сигнатуры типа. Если я прокомментирую подпись типа и удалю оператор lift, сообщение об ошибке превратится в:

No instance for (PersistStore ResourceT (SqlPersist IO))
  arising from a use of `p'
Possible fix:
  add an instance declaration for
  (PersistStore ResourceT (SqlPersist IO))
In the first argument of `CL.mapMaybeM', namely `p'

Итак, это означает, что мы вообще не можем получить доступ к PersistStore через ResourceT?

Я тоже не могу написать свой собственный канал, не используя CL.mapMaybeM:

dbAction = filterP
filterP :: (MonadIO m, MonadBaseControl IO (SqlPersist m)) => Conduit DataType m DataType
filterP = loop
    where loop = awaitE >>= either return go
          go s = do lift $ insert $ undefined -- again, type error
                    loop

Это привело к еще одной ошибке типа, которую я не совсем понимаю.

Could not deduce (m ~ b0 m0)
from the context (MonadIO m, MonadBaseControl IO (SqlPersist m))
  bound by the type signature for
             filterP :: (MonadIO m,
                                 MonadBaseControl IO (SqlPersist m)) =>
                                Conduit DataType m DataType
     `m' is a rigid type variable bound by
      the type signature for
        filterP :: (MonadIO m,
                            MonadBaseControl IO (SqlPersist m)) =>
                           Conduit DataType m DataType
Expected type: Conduit DataType m DataType
  Actual type: Pipe
                 DataType DataType DataType () (b0 m0) ()
In the expression: loop
In an equation for `filterP'

Итак, мой вопрос: возможно ли использовать постоянное, как я предполагал, внутри канала? И если, как? Я знаю, что, поскольку я могу использовать liftIO внутри кабелепровода, я мог бы просто пойти и использовать, скажем HDBC, но я хотел использовать постоянное явно, чтобы понять, как он работает, и потому что мне нравится его db-backend агностицизм.

Ответ 1

Код ниже компилируется для меня. Возможно ли, что рамки переместились в это время, и теперь все работает?

Однако обратите внимание на следующие изменения, которые я должен был сделать, поскольку мир немного изменился или у меня не было всего вашего кода. Я использовал conduit-1.0.9.3 и persistent-1.3.0 с GHC 7.6.3.

  • Отключено parseBytes и serialise, поскольку у меня нет ваших определений и не определена DataType = ByteString.

  • Введен параметр Proxy и явная сигнатура типа для значения undefined, чтобы избежать проблем с инъекцией типа семейства. Вероятно, они не возникают в вашем реальном коде, потому что он будет иметь конкретный или внешний тип для val.

  • Используется await, а не awaitE и просто используется () как тип для замены для случая Left, поскольку awaitE был удален.

  • Передача фиктивной Connection функции создания в withSqlConn - возможно, я должен был использовать определенную специальную функцию Sqlite?

Здесь код:

{-# LANGUAGE FlexibleContexts, NoMonomorphismRestriction,
             TypeFamilies, ScopedTypeVariables #-}

module So133331988 where

import Control.Monad.Trans
import Database.Persist.Sql
import Data.ByteString
import Data.Conduit
import Data.Conduit.Binary
import Data.Proxy

test proxy =
    withSqlConn (return (undefined "foo.db")) $ runSqlConn $ runResourceT $ 
         sourceFile "in" $= dbAction proxy $$ sinkFile "out"

dbAction = filterP

type DataType = ByteString

filterP
    :: forall m val
     . ( MonadIO m, MonadBaseControl IO (SqlPersist m)
       , PersistStore m, PersistEntity val
       , PersistEntityBackend val ~ PersistMonadBackend m)
    => Proxy val
    -> Conduit DataType m DataType
filterP Proxy = loop
    where loop = await >>= maybe (return ()) go
          go s = do lift $ insert (undefined :: val)
                    loop