防止" getCurrentDirectory:资源耗尽(打开的文件过多)"错误

时间:2014-04-06 11:08:10

标签: haskell lazy-evaluation parsec lazy-io

我正在尝试在一大堆小文件上运行Parsec解析器,并收到错误消息,说我有太多打开的文件。我知道我需要使用严格的IO,但我不知道该怎么做。这是有问题的代码:

files = getDirectoryContents historyFolder

hands :: IO [Either ParseError [Hand]]
hands = join $ sequence <$> parseFromFile (many hand) <<$>> files

注意:我的<<$>>功能是:

(<<$>>) :: (Functor f1, Functor f2) => (a -> b) -> f1 (f2 a) -> f1 (f2 b)
a <<$>> b = (a <$>) <$> b

2 个答案:

答案 0 :(得分:5)

我不知道你的parseFromFile函数现在看起来是什么样的(在问题中包含它可能是一个好主意),但我猜你正在使用Prelude.readFile, @ Markus1189指出包括懒惰的I / O.要获得严格的I / O,您只需要一个严格的readFile,例如Data.Text.IO.readFile

pipesconduit这样的流数据库可以让你避免一次将整个文件读入内存,但据我所知 - parsec没有提供流媒体接口来允许这个即将发生。另一方面,attoparsec确实包含这样的流接口,并且管道和管道都有attoparsec适配器库(例如,Data.Conduit.Attoparsec)。

tl; dr:您可能只需要以下辅助函数:

import qualified Data.Text as T
import qualified Data.Text.IO as TIO

readFileStrict :: FilePath -> IO String
readFileStrict = fmap T.unpack . TIO.readFile

答案 1 :(得分:0)

您可以使用BangPatterns语言扩展来强制执行IO操作的严格性,在本例中为parseFromFile。例如,可以在以下位置更改函数hands

hands :: [String] → IO [Either ParseError [Hand]]
hands [] = return []
hands (f:fs) = do
  !res ← parseFromFile hand f
  others ← hands fs
  return (res:others)

在移动到列表中的下一个文件之前,此版本的手等待parseFromFile每次调用的结果。一旦你有了这个,问题就会消失。一个完整的玩具示例是:

{-# LANGUAGE BangPatterns #-}
import Control.Monad
import Control.Applicative hiding (many)
import Data.Char (isDigit)
import System.Directory (getDirectoryContents)
import System.FilePath ((</>))
import Text.ParserCombinators.Parsec

data Hand = Hand Int deriving Show

hand :: GenParser Char st [Hand]
hand = do
  string "I'm file "
  num ← many digit
  newline
  eof
  return [Hand $ read num]

files :: IO [String]
files = map ("manyfiles" </>)
      ∘ filter (all isDigit) <$> getDirectoryContents "manyfiles"

hands :: [String] → IO [Either ParseError [Hand]]
hands [] = return []
hands (f:fs) = do
  !res ← parseFromFile hand f
  others ← hands fs
  return (res:others)

main :: IO 
main = do
  results ← files >≥ hands
  print results