我正在尝试扩展常规markdown,能够引用其他文件,以便引用文件中的内容呈现在“master”文件中的相应位置。
但我来的最远的是实施
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)
答案 0 :(得分:3)
如您所料,懒惰的IO可能就是问题所在。这是(可怕的)规则,你必须遵循以正确使用它而不必完全坚定:
withFile
计算必须完成,直到完全评估其结果所需的所有(惰性)I / O都已完成。
如果在句柄关闭后某些东西强制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?。
答案 1 :(得分:2)
我强烈建议你避免使用惰性IO,因为它始终会产生这样的问题,如What's so bad about Lazy I/O?中所述。在你的情况下,你需要保持文件打开直到它被完全读取,但这会意味着在实际使用内容时,在纯代码中的某处关闭文件。
一种可能性是使用严格的ByteString
并使用readFile
读取文件。这也可以使许多操作更有效率。
另一种选择是使用其中一个解决延迟IO问题的库(参见What are the pros and cons of Enumerators vs. Conduits vs. Pipes?)。这些库允许您将内容制作与其处理或消费分开。因此,您可以让生产者读取输入文件并生成一些令牌流,以及一个消费流并产生一些结果的纯消费者(不依赖于IO
)。例如,在 conduit-extra 中,有一个module可将 atto-parsec 解析器转换为使用者。
另请参阅Is there a better way to walk a directory tree?