【问题标题】:reading files with references to other files in haskell读取引用了haskell中其他文件的文件
【发布时间】:2014-06-24 16:53:30
【问题描述】:

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

但我来的最远的是实施

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 -&gt; {-do stutff-}hGetContents handle 是正确的解决方案,因为我读过fcontent &lt;- 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)

【问题讨论】:

  • 如果您打开文件只是为了执行getContents,那么您最好执行fmap myPureFunction (readFile filename),这样所有这些都会消失。 readFile 将在读取文件内容后关闭文件。

标签: haskell lazy-io


【解决方案1】:

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

在完全评估其结果所需的所有(惰性)I/O 都已执行之前,withFile 计算不得完成。

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

你用return $ merge fc childStrings 打破了这条规则,因为这个值在它被完全评估之前就被返回了。相反,您可以做的只是模糊的类似

let retVal = merge fc childStrings
deepSeq retVal $ return retVal

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


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


更新

功能请求已被接受,此类程序现在更有可能产生有用的错误消息。详情请见What caused this "delayed read on closed handle" error?

【讨论】:

    【解决方案2】:

    我强烈建议您避免惰性 IO,因为它总是会产生这样的问题,如 What's so bad about Lazy I/O? 中所述当内容被实际使用时,该文件以纯代码形式存在。

    一种可能性是使用严格的ByteStrings 并使用readFile 读取文件。这也将使许多操作更有效率。

    另一种选择是使用解决惰性 IO 问题的库之一(请参阅What are the pros and cons of Enumerators vs. Conduits vs. Pipes?)。这些库允许您将内容生产与其处理或消费分开。因此,您可以有一个读取输入文件并生成一些令牌流的生产者,以及一个消费流并产生一些结果的纯消费者(不依赖于IO)。例如,在 conduit-extra 中有一个 moduleatto-parsec 解析器转换为消费者。 另见Is there a better way to walk a directory tree?

    【讨论】:

    • 我会推荐导管。您可以使用可恢复源暂停主文件,同时从包含的文件中获取源,然后在该文件结束时恢复。 See tutorial.
    猜你喜欢
    • 2011-12-13
    • 1970-01-01
    • 2012-06-16
    • 2011-07-21
    • 1970-01-01
    • 1970-01-01
    • 2014-02-23
    • 2017-12-03
    • 2022-01-11
    相关资源
    最近更新 更多