我是 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 个字符。
我做错了什么?
我承认我不明白
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
。
按照
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 可能会更好。只是想将此答案添加到所述问题中。)