13

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

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

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

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

我得到了一个正义,因为我应该。

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

谢谢

4

3 回答 3

13

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在打开和关闭文件之间做所有事情。你的第一个动作类似于bad. withFile应该管理您想要完成的所有操作,这取决于句柄。

如果您正在处理 .、小文件等,则不需要严格执行String器,只需了解组合的工作原理即可。同样,在bad关闭文件之前我所做的一切都是exposeContentsToEvaluationAsNeededForTheRestOfTheAction. 在我构思的其余动作中进行good撰写,然后关闭文件。exposeContentsToEvaluationAsNeededForTheRestOfTheAction

Patrick提到的熟悉的length+技巧,或+值得了解;你的第二个动作是一个变体。但是重组更好,除非惰性 IO 不适合您的情况。seqlengthevaluateputStrLn txt

$ 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 不合适的(大量)类案例之一,请查看enumerator,conduit和 co.,一切都很棒。

于 2012-05-07T18:57:37.583 回答
9

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

import Control.Exception (evaluate)

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

像这种不直观的情况是当今人们倾向于避免惰性 IO 的原因之一,但不幸的是,您无法更改hGetContents. 在stricthGetContents包中提供了一个严格的 IO 版本,但可能不值得仅仅为那个功能而依赖包。

如果您想避免在这里遍历字符串两次所带来的开销,那么您可能应该考虑使用比 更有效的类型String,无论如何;对于许多基于 - 的 IO 功能,该Text类型具有严格的 IO 等效项,同样如此(如果您正在处理二进制数据,而不是 Unicode 文本)。StringByteString

于 2012-05-07T16:56:43.583 回答
0

您可以使用强制text评估的内容

length text `seq` return code

作为最后一行。

于 2012-05-07T16:51:29.883 回答