我有以下代码段,我将其传递给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,就像我应该的那样。
如果我自己openFile
和hClose
,我也遇到同样的问题。为什么会这样?我怎样才能干净利落地解决它?
由于
答案 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
在打开和关闭文件之间执行所有操作。你的第一个动作类似于bad
。 withFile
应该管理您想要完成的所有操作,这取决于句柄。
如果您正在处理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不合适的(巨大的)类别案例之一,请查看enumerator
,conduit
和co。,一切都很棒。
答案 1 :(得分:9)
hGetContents
使用惰性IO;它只会在您强制执行更多字符串时从文件中读取,并且只在评估它返回的整个字符串时才关闭文件句柄。问题是你将它括在withFile
;相反,只需直接使用openFile
和hGetContents
(或者更简单地说,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 equivalents,String
类型有as does ByteString
(如果您处理的是二进制数据,而不是Unicode文本)。
答案 2 :(得分:0)
您可以使用
强制评估text
的内容
length text `seq` return code
作为最后一行。