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

Hen*_*nes 3 file-io haskell lazy-evaluation

在这里,我又回来了(对我而言)我最新杰作的奇怪行为......

此代码应该读取文件,但它不会:

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.")
Run Code Online (Sandbox Code Playgroud)

结果是

Read 0 Bytes input data.
Run Code Online (Sandbox Code Playgroud)

现在我改变了第一个函数并添加了一个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
          )
Run Code Online (Sandbox Code Playgroud)

结果是

hGetContents gave 3479360 Bytes of input data.
Read 3479360 Bytes input data.
Run Code Online (Sandbox Code Playgroud)

WTF ??? 嗯,我知道,Haskell是懒惰的.但是我不知道我必须像这样踢它.

lef*_*out 6

你是对的,这是一种痛苦.出于这个原因,避免使用旧的标准文件IO模块 - 除了简单地读取一个不会改变的整个文件,就像你做的那样; 这可以做得很好readFile.

readCsvContents :: Filepath -> IO String
readCsvContents fileName = do
   contents <- readFile fileName
   return contents
Run Code Online (Sandbox Code Playgroud)

需要注意的是,由单子法律,这是完全一样的1作为

readCsvContents = readFile
Run Code Online (Sandbox Code Playgroud)

您尝试的问题是,当monad退出时withFile,句柄无条件关闭,而不检查延迟评估contents是否实际上强制文件读取.那当然太可怕了; 我自己也懒得用手柄.readFile通过将句柄的关闭链接到原始结果thunk的垃圾收集来避免问题; 这也不是很好,但通常效果很好.

要正确使用文件IO,请检查管道管道库.前者更注重性能,后者更注重优雅(但实际上,差异并不大).


1 你的第一次尝试与之相同readCsvContents fn = withFile fn ReadMode hGetContents.