0

我正在尝试扩展常规降价,使其能够引用其他文件,以便在“主”文件中的相应位置呈现被引用文件中的内容。

但我来的最远的是实施

createF :: FTree -> IO String
createF Null = return ""
createF (Node f children) = ifNExists f (_id f)
                              (do childStrings <- mapM createF children
                                  withFile (_path f) ReadMode $ \handle ->
                                    do fc <- lines <$> hGetContents handle
                                       return $ merge fc childStrings)

ifNExists只是一个可以忽略的助手,真正的问题发生在句柄的读取中,它只是返回空字符串,我认为这是由于惰性 IO。

我认为使用withFile filepath ReadMode $ \handle -> {-do stutff-}hGetContents handle将是正确的解决方案,因为我读过fcontent <- withFile filepath ReadMode hGetContents是一个坏主意。

让我感到困惑的另一件事是功能

createFT :: File -> IO FTree
createFT f = ifNExists f Null
               (withFile (_path f) ReadMode $ \handle ->
                  do let thisParse = fparse (_id f :_parents f)
                     children <-rights . map ( thisParse . trim) . lines <$> hGetContents handle
                     c <- mapM createFT children
                     return $ Node f c)

奇迹般有效。

那么为什么createF只返回一个空字符串呢?

整个项目和要测试的目录/文件可以在github上找到


这是数据类型定义

type ID = String

data File = File {_id :: ID, _path :: FilePath, _parents :: [ID]}
          deriving (Show)
data FTree = Null
           | Node { _file :: File
                  , _children :: [FTree]} deriving (Show)
4

2 回答 2

3

正如您所怀疑的,惰性 IO 可能是问题所在。这是您必须遵循的(可怕的)规则才能正确使用它而不会完全发疯:

直到执行了完全评估其结果所需的withFile所有(惰性)I/O 之后,计算才能完成。

如果在句柄关闭后有东西强制 I/O,你不能保证得到一个错误,即使那会非常好。相反,您会得到完全未定义的行为。

你用 打破了这个规则return $ merge fc childStrings,因为这个值是在它被完全评估之前返回的。你可以做的是模糊的事情

let retVal = merge fc childStrings
deepSeq retVal $ return retVal

一个可以说是更干净的替代方法是将依赖于这些结果的所有其余代码放入withFile参数中。不这样做的唯一真正原因是,如果您在完成该文件后对结果进行大量其他工作。例如,如果您正在处理一堆不同的文件并累积它们的结果,那么您希望确保在完成后关闭它们中的每一个。如果您只是读取一个文件然后对其进行操作,则可以将其保持打开状态直到完成。


顺便说一句,我刚刚向 GHC 团队提交了一个功能请求,看看他们是否愿意让这类程序更有可能提前失败并提供有用的错误消息。


更新

功能请求被接受,这样的程序现在更有可能产生有用的错误消息。请参阅是什么导致了这个“关闭句柄上的延迟读取”错误?详情。

于 2014-06-24T17:28:06.643 回答
2

我强烈建议您避免惰性 IO,因为它总是会产生这样的问题,如惰性 I/O 有什么不好?与您的情况一样,您需要在完全读取文件之前保持文件打开状态,但这意味着在实际使用内容时,在纯代码中的某处关闭文件。

一种可能性是使用 strictByteString并使用readFile. 这也将使许多操作更有效率。

另一种选择是使用解决惰性 IO 问题的库之一(请参阅Enumerators vs. Conduits vs. Pipes 的优缺点是什么?)。这些库允许您将内容生产与其处理或消费分开。因此,您可以有一个生产者读取输入文件并产生一些令牌流,以及一个纯消费者(不依赖于IO)消费流并产生一些结果。例如,conduit-extra中有一个模块可以将atto-parsec解析器转换为消费者。另请参阅是否有更好的方法来遍历目录树?

于 2014-06-24T18:25:08.730 回答