我尝试惰性 I/O,但整个文件都被消耗了

问题描述 投票:0回答:2

我是 Haskell 新手。我只想将文本文件的 N 个字符读入内存。所以我写了这段代码:

main :: IO()
main = do
  inh <- openFile "input.txt" ReadMode
  transformedList <- Control.Monad.liftM (take 4) $ transformFileToList inh
  putStrLn "transformedList became available"
  putStrLn transformedList
  hClose inh

transformFileToList :: Handle -> IO [Char]
transformFileToList h = transformFileToListAcc h []

transformFileToListAcc :: Handle -> [Char] -> IO [Char]
transformFileToListAcc h acc = do
  readResult <- tryIOError (hGetChar h)
  case readResult of
    Left e -> if isEOFError e then return acc else ioError e
    Right c -> do let acc' = acc ++ [transformChar c]
                  putStrLn "got char"
                  unsafeInterleaveIO $ transformFileToListAcc h acc'

我的输入文件有几行,第一行是“hello world”,当我运行这个程序时,我得到这个输出:

got char
transformedList became available
got char
["got char" a bunch of times]
hell

我的预期是“got char”只发生 4 次。相反,会读取整个文件,一次一个字符,然后仅读取前 4 个字符。

我做错了什么?

haskell lazy-evaluation
2个回答
1
投票

我承认我不明白

unsafeInterLeaveIO
是如何工作的,但我怀疑这里的问题与它有某种关系。也许通过这个例子,你试图理解
unsafeInterLeaveIO
,但如果我是你,我会尽量避免直接使用它。以下是我针对您的具体情况的做法。

main :: IO ()
main = do
    inh <- openFile "input.txt" ReadMode
    charList <- replicateM 4 $ hGetChar inh
    let transformedList = map transformChar charList
    putStrLn "transformedList became available"
    putStrLn transformedList
    hClose inh

这应该只读取文件的前 4 个字符。

如果您正在寻找真正有效的流媒体解决方案,我会研究

pipes
conduit
而不是
unsafeInterLeaveIO


0
投票

按照

transformFileToListAcc
的书写方式,在完成 all IO 之前,您无法计算出前四个字符。要看到这一点,请考虑这个修改后的形式:

transformFileToListAcc :: Handle -> [Char] -> IO [Char]
transformFileToListAcc h acc = do
  readResult <- tryIOError (hGetChar h)
  case readResult of
    Left e -> if isEOFError e then return ("answer: " ++ acc) else ioError e
    Right c -> do let acc' = acc ++ [transformChar c]
                  putStrLn "got char"
                  unsafeInterleaveIO $ transformFileToListAcc h acc'

这里的前四个字符是

"answ"
,但是直到你到达文件末尾你才会发现它。如果您希望能够延迟使用文件,则必须承诺返回递归调用之外的第一个字符,例如

transformFileToList :: Handle -> IO [Char]
transformFileToList h = do
  readResult <- tryIOError (hGetChar h)
  case readResult of
    Left e -> if isEOFError e then return [] else ioError e
    Right c -> do putStrLn "got char"
                  rest <- unsafeInterleaveIO $ transformFileToList h
                  return (transformChar c : rest)

现在我不需要进行递归调用来查看结果的第一个字符是什么。

(也就是说,不执行惰性 I/O 可能会更好。只是想将此答案添加到所述问题中。)

© www.soinside.com 2019 - 2024. All rights reserved.