hGetContents太懒了

时间:2012-05-07 16:35:06

标签: haskell file-io

我有以下代码段,我将其传递给withFile

text <- hGetContents hand 
let code = parseCode text
return code

这里hand是一个有效的文件句柄,用ReadMode打开,parseCode是我自己的函数,它读取输入并返回一个Maybe。实际上,函数失败并返回Nothing。如果,我写道:

text <- hGetContents hand 
putStrLn text
let code = parseCode text
return code

我得到一个Just,就像我应该的那样。

如果我自己openFilehClose,我也遇到同样的问题。为什么会这样?我怎样才能干净利落地解决它?

由于

3 个答案:

答案 0 :(得分:12)

hGetContents不是太懒,只需要与其他东西合适地组合以获得理想的效果。如果将其重命名为exposeContentsToEvaluationAsNeededForTheRestOfTheAction或仅listen,情况可能会更清晰。

withFile打开文件,执行某些操作(或者根本不做任何操作 - 无论如何都是您需要的),然后关闭文件。

摒弃'懒惰的IO'的所有神秘面纱是不够的,但现在考虑一下这种包围的区别

 good file operation = withFile file ReadMode (hGetContents >=> operation >=> print)
 bad file operation = (withFile file ReadMode hGetContents) >>= operation >>= print

-- *Main> good "lazyio.hs" (return . length)
-- 503
-- *Main> bad "lazyio.hs" (return . length)
-- 0

粗略地说,bad在它执行任何操作之前打开并关闭文件; good在打开和关闭文件之间执行所有操作。你的第一个动作类似于badwithFile应该管理您想要完成的所有操作,这取决于句柄。

如果您正在处理String,小文件等,则不需要严格执行者,只需了解合成的工作原理。同样,在关闭文件之前,bad所有“我做”都是exposeContentsToEvaluationAsNeededForTheRestOfTheAction。在good中,我将exposeContentsToEvaluationAsNeededForTheRestOfTheAction与我想到的其余操作组合在一起,然后关闭文件。

Patrick提到的熟悉的length + seq技巧或length + evaluate值得了解;你putStrLn txt的第二个动作是一个变种。但重组更好,除非懒惰IO对你的情况是错误的。

$ time ./bad
bad: Prelude.last: empty list  
                        -- no, lots of Chars there
real    0m0.087s

$ time ./good
'\n'                -- right
()
real    0m15.977s

$ time ./seqing 
Killed               -- hopeless, attempting to represent the file contents
    real    1m54.065s    -- in memory as a linked list, before finding out the last char

不言而喻,ByteString和Text值得了解,但是考虑到评估的重组更好,因为即使使用它们,Lazy变体通常也是你需要的,然后他们就会在构图形式之间理解相同的区别。如果您正在处理这类IO不合适的(巨大的)类别案例之一,请查看enumeratorconduit和co。,一切都很棒。

答案 1 :(得分:9)

hGetContents使用惰性IO;它只会在您强制执行更多字符串时从文件中读取,并且只在评估它返回的整个字符串时才关闭文件句柄。问题是你将它括在withFile;相反,只需直接使用openFilehGetContents(或者更简单地说,readFile)。完全评估字符串后,文件仍将关闭。像这样的东西应该做的,以确保文件完全读取并立即通过强制整个字符串关闭:

import Control.Exception (evaluate)

readCode :: FilePath -> IO Code
readCode fileName = do
    text <- readFile fileName
    evaluate (length text)
    return (parseCode text)

这种不直观的情况是人们现在倾向于避免懒惰IO的原因之一,但不幸的是你无法改变hGetContents的定义。 strict包中提供了hGetContents的严格IO版本,但根据该功能包的不同,它可能不值得。

如果你想避免在这里两次遍历字符串所带来的开销,那么你应该考虑使用比String更有效的类型,无论如何;对于基于Text的IO功能strict IO equivalentsString类型有as does ByteString(如果您处理的是二进制数据,而不是Unicode文本)。

答案 2 :(得分:0)

您可以使用

强制评估text的内容
length text `seq` return code

作为最后一行。