Haskell 他太懒了

Haskell 他太懒了,haskell,file-io,Haskell,File Io,我有以下代码片段,我将其传递给with file: text <- hGetContents hand let code = parseCode text return code 我得到了一个公正的,我应该得到的 如果我自己做了openFile和hClose,我也会遇到同样的问题。为什么会这样?我怎样才能彻底解决它 谢谢您可以使用 length text `seq` return code 作为最后一行。hGetContents使用惰性IO;它仅在强制执行更多字符串时读取文件,并且仅

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

text <- hGetContents hand 
let code = parseCode text
return code
我得到了一个公正的,我应该得到的

如果我自己做了
openFile
hClose
,我也会遇到同样的问题。为什么会这样?我怎样才能彻底解决它


谢谢

您可以使用

length text `seq` return code

作为最后一行。

hGetContents
使用惰性IO;它仅在强制执行更多字符串时读取文件,并且仅在计算返回的整个字符串时关闭文件句柄。问题是,您将它封装在
中的文件
;相反,只需直接使用
openFile
hGetContents
(或者更简单地说,
readFile
)。完全计算字符串后,文件仍将关闭。类似这样的操作应该可以做到这一点,通过预先强制整个字符串来确保文件被完全读取并立即关闭:

import Control.Exception (evaluate)

readCode :: FilePath -> IO Code
readCode fileName = do
    text <- readFile fileName
    evaluate (length text)
    return (parseCode text)
导入控制。异常(评估) readCode::文件路径->IO代码 readCode fileName=do
文本
hGetContents
不是太懒,它只是需要与其他内容适当组合才能达到预期效果。如果将其重命名为操作所需的ExposeContentsToEvaluation,或者仅将其重命名为侦听,情况可能会更清楚

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
在文件执行任何操作之前打开和关闭文件<代码>好
在打开和关闭文件之间执行所有操作。您的第一个操作类似于
bad
withFile
应该控制所有需要执行的操作,这取决于句柄

如果您正在处理
字符串
、小文件等,则不需要严格的强制执行器,只要了解组合的工作原理即可。同样,在
bad
中,在关闭文件之前,我所做的一切都是
exposeContentsToEvaluation,这是操作所需的。在
good
中,我编写了操作所需的
exposeContentsToEvaluation
以及我想到的其余操作,然后关闭文件

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是值得了解的,但是考虑到评估的重组更好,因为即使使用它们,惰性变体通常也是您所需要的,并且它们涉及到掌握构图形式之间的相同区别。如果您正在处理此类IO不合适的(大量)案例之一,请查看
枚举器
管道
和公司,它们都很好。

我想说,仅仅对于严格的
hGetContents
,依赖
严格的
是值得的;这正是这个包裹的目的!不要传播NIH综合征。
System.IO.Strict中
hGetContents
的定义是熟悉的
hGetContents h=IO.hGetContents h>=\s->length s`seq`return s
;这是本书中最古老的技巧,不是来自
strict-0.3
的新想法,在读取
字符串时使用
evaluate
是毫无意义的,因为
evaluate
只计算WHNF,即第一个
(:)
构造函数。但是,如果解析文件的结果取决于文件的全部内容,则可能适合使用它;我提到它是因为这里的其他地方提到了它。这些
长度
黑客真的很讨厌。这难道不意味着对于大多数程序来说,你的处境比仅仅使用
openFile“filename”ReadMose>=>hGetContents
然后再也不关闭句柄更糟糕吗?因为使用withFile时,您必须在回调中完成整个程序的IO,否则您可能会导致延迟读取,除非句柄保证在最新的可能点关闭,而不是在任何您希望强制关闭并报告错误的点(如果可能出现后续延迟读取),您可以显示在您自己使用hClose的地方编码?听起来好像是在需要输入之前关闭它。这只需要计算元素的数量,由于length永远不会计算元素,因此haskell实现很容易只计算到文件的stating,而使用该实现时,在尝试使用数据时仍然会出现IO错误
(让lseq=liftA2 seq in(length.filter(='\0'))`lseq`return)
将更接近完成,但仍可能存在惰性字符串源,它们将对最后一个字节进行编码,其中(='\0')不需要读取编码最后一个字符的所有字节。
$ 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