Haskell return 来自文件 IO 的惰性字符串
Haskell return lazy string from file IO
我又回来了,带着我最新杰作的(对我来说)非常奇怪的行为...
这段代码应该读取一个文件,但它没有:
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.")
结果是
Read 0 Bytes input data.
现在我改了第一个函数,加了一个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
)
结果是
hGetContents gave 3479360 Bytes of input data.
Read 3479360 Bytes input data.
卧槽???嗯,我知道,Haskell 是懒惰的。但我不知道我不得不这样踢它的屁股。
你说得对,这很痛苦。出于这个原因,避免使用旧的标准文件 IO 模块——除了像您所做的那样简单地读取一个不会改变的整个文件;这可以用 readFile
.
来完成
readCsvContents :: Filepath -> IO String
readCsvContents fileName = do
contents <- readFile fileName
return contents
请注意,根据 monad 法则,这与1 完全相同
readCsvContents = readFile
您尝试的问题是当 monad 退出时句柄被无条件关闭 withFile
,而没有检查 contents
的惰性求值是否实际上强制了文件读取。那当然是可怕的;我永远不会费心自己使用手柄。 readFile
通过将句柄的关闭与原始结果 thunk 的垃圾收集联系起来避免了这个问题;这也不是很好,但通常效果很好。
为了正确使用文件 IO,请查看 conduit or pipes 库。前者更注重性能,后者更注重优雅(但实际上,区别并不大)。
1而您的第一次尝试与 readCsvContents fn = withFile fn ReadMode hGetContents
相同。
这是惰性IO的问题。您的代码中发生的是 withFile
打开文件,将句柄传递给 lambda。这个 lambda returns 一个包含文件内容的惰性列表。然后 withFile
注意到回调完成并关闭文件。
由于返回的列表是惰性的,只有在评估列表时才会读取文件内容。这发生在对 length
的调用中。但是,此时文件句柄已经关闭,因此您无法从文件中读取任何内容。
您调用的修改版本在 withFile
参数中强制文件内容,此时文件仍然可用,因此它可以工作。
我又回来了,带着我最新杰作的(对我来说)非常奇怪的行为...
这段代码应该读取一个文件,但它没有:
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.")
结果是
Read 0 Bytes input data.
现在我改了第一个函数,加了一个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
)
结果是
hGetContents gave 3479360 Bytes of input data.
Read 3479360 Bytes input data.
卧槽???嗯,我知道,Haskell 是懒惰的。但我不知道我不得不这样踢它的屁股。
你说得对,这很痛苦。出于这个原因,避免使用旧的标准文件 IO 模块——除了像您所做的那样简单地读取一个不会改变的整个文件;这可以用 readFile
.
readCsvContents :: Filepath -> IO String
readCsvContents fileName = do
contents <- readFile fileName
return contents
请注意,根据 monad 法则,这与1 完全相同
readCsvContents = readFile
您尝试的问题是当 monad 退出时句柄被无条件关闭 withFile
,而没有检查 contents
的惰性求值是否实际上强制了文件读取。那当然是可怕的;我永远不会费心自己使用手柄。 readFile
通过将句柄的关闭与原始结果 thunk 的垃圾收集联系起来避免了这个问题;这也不是很好,但通常效果很好。
为了正确使用文件 IO,请查看 conduit or pipes 库。前者更注重性能,后者更注重优雅(但实际上,区别并不大)。
1而您的第一次尝试与 readCsvContents fn = withFile fn ReadMode hGetContents
相同。
这是惰性IO的问题。您的代码中发生的是 withFile
打开文件,将句柄传递给 lambda。这个 lambda returns 一个包含文件内容的惰性列表。然后 withFile
注意到回调完成并关闭文件。
由于返回的列表是惰性的,只有在评估列表时才会读取文件内容。这发生在对 length
的调用中。但是,此时文件句柄已经关闭,因此您无法从文件中读取任何内容。
您调用的修改版本在 withFile
参数中强制文件内容,此时文件仍然可用,因此它可以工作。