简体   繁体   English

Haskell从文件IO返回惰性字符串

[英]Haskell return lazy string from file IO

Here I'm back again with a (for me) really strange behaviour of my newest masterpiece... 在这里,我又回来了(对我而言)我最新杰作的奇怪行为......

This code should read a file, but it doesn't: 此代码应该读取文件,但它不会:

readCsvContents :: String -> IO ( String )
readCsvContents fileName = do
     withFile fileName ReadMode (\handle -> do
          contents <- hGetContents handle
          return contents
          )

main = do
    contents <- readCsvContents "src\\EURUSD60.csv"
    putStrLn ("Read " ++ show (length contents) ++ " Bytes input data.")

The result is 结果是

Read 0 Bytes input data.

Now I changed the first function and added a putStrLn : 现在我更改了第一个函数并添加了一个putStrLn

readCsvContents :: String -> IO ( String )
readCsvContents fileName = do
     withFile fileName ReadMode (\handle -> do
          contents <- hGetContents handle
          putStrLn ("hGetContents gave " ++ show (length contents) ++ " Bytes of input data.")
          return contents
          )

and the result is 结果是

hGetContents gave 3479360 Bytes of input data.
Read 3479360 Bytes input data.

WTF ??? WTF ??? Well, I know, Haskell is lazy. 嗯,我知道,Haskell是懒惰的。 But I didn't know I had to kick it in the butt like this. 但是我不知道我必须像这样踢它。

You're right, this is a pain. 你是对的,这是一种痛苦。 Avoid using the old standard file IO module, for this reason – except to simply read an entire file that won't change, as you did; 出于这个原因,避免使用旧的标准文件IO模块 - 除了简单地读取一个不会改变的整个文件,就像你做的那样; this can be done just fine with readFile . 这可以通过readFile

readCsvContents :: Filepath -> IO String
readCsvContents fileName = do
   contents <- readFile fileName
   return contents

Note that, by the monad laws, this is exactly the same 1 as 请注意,根据monad定律,这与1完全相同

readCsvContents = readFile

The problem with what you tried is that the handle is closed unconditionally when the monad exits withFile , without checking whether lazy-evaluation of contents has actually forced the file reads. 您尝试的问题是当monad退出withFile时句柄无条件关闭,而不检查contents惰性评估是否实际上强制文件读取。 That is of course horrible; 那当然太可怕了; I would never bother to use handles myself. 我自己也懒得用手柄。 readFile avoids the problem by linking the closing of the handle to garbage-collection of the original result thunk; readFile通过将句柄的关闭链接到原始结果thunk的垃圾收集来避免问题; this isn't altogether nice either but often works quite well. 这也不是很好,但通常效果很好。

For proper work with file IO, check out either the conduit or pipes library. 要正确使用文件IO,请检查管道管道库。 The former focuses a bit more on performance, the latter more on elegance (but really, the difference isn't that big). 前者更注重性能,后者更注重优雅(但实际上,差异并不大)。


1 And your first try is the same as readCsvContents fn = withFile fn ReadMode hGetContents . 1 你的第一次尝试与readCsvContents fn = withFile fn ReadMode hGetContents

This is a problem with lazy IO. 这是懒惰IO的问题。 What happens in your code is that withFile opens the file, passes the handle to the lambda. 您的代码中发生的事情是withFile打开文件,将句柄传递给lambda。 This lambda returns a lazy list containing the contents of the file. 此lambda返回包含文件内容的惰性列表。 Then withFile notices that the callback finished and closes the file. 然后withFile注意到回调已完成并关闭文件。

Since the returned list is lazy, the file contents will only be read when the list is evaluated. 由于返回的列表是惰性的,因此只有在评估列表时才会读取文件内容。 This happens in the call to length . 这发生在对length的调用中。 However, at this point the file handle is already closed and therefore you can't read anything from the file. 但是,此时文件句柄已经关闭,因此您无法从文件中读取任何内容。

The modified version of your call forces the file contents in the withFile argument, at which point the file is still available, and therefore it works. 调用的修改版本会强制使用withFile参数中的文件内容,此时文件仍然可用,因此它可以正常工作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM