Объединение Persistent и IO

Я пытаюсь выполнить рекурсию по каталогу, обрабатывая файлы и сохраняя результаты в базе данных, но столкнулся с проблемой.

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

{-# LANGUAGE QuasiQuotes, TemplateHaskell, TypeFamilies, OverloadedStrings #-}
{-# LANGUAGE GADTs, FlexibleContexts #-}
import Database.Persist
import Database.Persist.Sqlite
import Database.Persist.TH
import System.Environment (getArgs)
import System.Directory (canonicalizePath, getDirectoryContents, doesDirectoryExist, doesFileExist)
import System.FilePath (combine, takeExtension)
import Control.Monad (filterM, mapM_)
import Control.Monad.IO.Class (liftIO)

share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistUpperCase|
File                                                       
  path String
  deriving (Show)
|]
main :: IO ()
main = do
  args <- getArgs
  path <- canonicalizePath $ head args
  runSqlite "files.sqlite" $ do
    runMigration migrateAll
    liftIO $ processDirectory path
    return ()

processDirectory path = files >>= mapM_ processFile >>
                        directories >>= mapM_ processDirectory
  where contents  = getDirectoryContents path >>=
                    return . map (combine path) . filter (`notElem` [".", ".."]) 
        directories = contents >>= filterM doesDirectoryExist
        files = contents >>= filterM doesFileExist

processFile path = insert $ File path

Однако приведенное выше не компилируется, вместо этого получается:

No instance for (PersistStore IO)
      arising from a use of `processFile'
    Possible fix: add an instance declaration for (PersistStore IO)
    In the first argument of `mapM_', namely `processFile'
    In the second argument of `(>>=)', namely `mapM_ processFile'
    In the first argument of `(>>)', namely
      `files >>= mapM_ processFile'
Failed, modules loaded: none.

Это имеет смысл для меня, поскольку processFile — это просто вызов для вставки, которая является частью монады PersistStore (правильно?), а не IO. Я думаю, что мне нужен преобразователь монад, но в этот момент я упираюсь в кирпичную стену, что, возможно, означает, что я лаю не по тому дереву.


person Walton Hoops    schedule 26.03.2013    source источник


Ответы (1)


Единственная вещь, которую вы не хотите заключать в liftIO, — это действие базы данных, поэтому вам нужно изменить код, чтобы оставить processFile вне его.

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

{-# LANGUAGE QuasiQuotes, TemplateHaskell, TypeFamilies, OverloadedStrings #-}
{-# LANGUAGE GADTs, FlexibleContexts #-}
import Database.Persist
import Database.Persist.Sqlite
import Database.Persist.TH
import System.Environment (getArgs)
import System.Directory (canonicalizePath, getDirectoryContents, doesDirectoryExist, doesFileExist)
import System.FilePath (combine, takeExtension)
import Control.Monad (filterM, mapM_)
import Control.Monad.IO.Class (liftIO)

share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistUpperCase|
File                                                       
  path String
  deriving (Show)
|]
main :: IO ()
main = do
  args <- getArgs
  path <- canonicalizePath $ head args
  runSqlite "files.sqlite" $ do
    runMigration migrateAll
    processDirectory path
    return ()

processDirectory path = liftIO files >>= mapM_ processFile >>
                        liftIO directories >>= mapM_ processDirectory
  where contents  = getDirectoryContents path >>=
                    return . map (combine path) . filter (`notElem` [".", ".."])  
        directories = contents >>= filterM doesDirectoryExist
        files = contents >>= filterM doesFileExist

processFile path = insert $ File path
person Paul Rouse    schedule 26.03.2013
comment
Ах, да, это должно было быть очевидно, как только я нашел liftIO и то, что он сделал. Я думаю, что я смотрел на него слишком долго. Спасибо! - person Walton Hoops; 26.03.2013