当给出由\ n:
分隔的文本输入文件时,该程序产生我期望的输出import System.IO
main :: IO ()
main = do h <- openFile "test.txt" ReadMode
xs <- getlines h
sequence_ $ map putStrLn xs
getlines :: Handle -> IO [String]
getlines h = hGetContents h >>= return . lines
用withFile替换openFile并稍微重新排列
import System.IO
main :: IO ()
main = do xs <- withFile "test.txt" ReadMode getlines
sequence_ $ map putStrLn xs
getlines :: Handle -> IO [String]
getlines h = hGetContents h >>= return . lines
我设法完全没有输出。我很难过。
编辑:不再难过了:感谢一个人和所有人的深思熟虑和发人深省的答案。我在文档中做了一些阅读,并了解到 withFile 可以理解为括号的部分应用。
这就是我最终的结果:
import System.IO
main :: IO ()
main = withFile "test.txt" ReadMode $ \h -> getlines h >>= mapM_ putStrLn
getlines :: Handle -> IO [String]
getlines h = lines `fmap` hGetContents h
答案 0 :(得分:29)
该文件过早关闭。来自documentation:
从withFile
退出时句柄将关闭
这意味着只要withFile
函数返回,文件就会关闭。
由于hGetContents
和朋友都很懒,所以在用putStrLn
强制执行之前,它不会尝试读取文件,但到那时,withFile
会关闭文件已经
要解决问题,请将整个内容传递给withFile
:
main = withFile "test.txt" ReadMode $ \handle -> do
xs <- getlines handle
sequence_ $ map putStrLn xs
这是有效的,因为当withFile
关闭文件时,您已经打印过了。
答案 1 :(得分:11)
main :: IO ()
main = do xs <- fmap lines $ readFile "test.txt"
mapM_ putStrLn xs
当您可以使用openFile
时,请勿使用hGetContents
+ withFile
或hGetContents
+ readFile
。使用readFile
,你不能过早地关闭文件,从而使自己陷入困境。
答案 2 :(得分:7)
他们完全不同的事情。
openFile
打开一个文件并返回一个文件句柄:
openFile :: FilePath -> IOMode -> IO Handle
withFile
用于包装带有文件句柄的IO计算,确保句柄在之后关闭:
withFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r
在您的情况下,使用withFile将如下所示:
main = withFile "test.txt" ReadMode $ \h -> do
xs <- getlines h
sequence_ $ map putStrLn xs
您当前拥有的版本将打开该文件,调用getlines
,然后关闭该文件。由于getlines
是惰性的,因此在文件打开时不会读取任何输出,一旦文件关闭,它就不能。
答案 3 :(得分:5)
你正在遇到懒惰IO的常见障碍......懒惰的IO听起来是一个很好的主意,使得流式传输变得轻而易举,直到你开始遇到那些可怕的问题。
并不是说你的特定情况不会成为经验丰富的Haskeller的红色鲱鱼:这是为什么懒惰的IO出现问题的教科书示例。
main = do xs <- withFile "test.txt" ReadMode getlines
sequence_ $ map putStrLn xs
withFile采用FilePath,模式和操作来处理使用此模式打开此文件路径所产生的句柄。 withFile中有趣的部分是它使用括号和保证实现,即使在异常情况下,也会在句柄执行操作后关闭文件 。这里的问题是有问题的行为(getLines)根本不读取文件!它只承诺在真正需要内容时这样做!这是懒惰的IO(用unsafeInterleaveIO实现,猜猜“不安全”部分意味着......)。当然,当这个内容需要 时(putStrLn),句柄由withFile按照承诺关闭。
所以你有几个解决方案:你可以明确地使用open和close(并放弃异常安全),或者你可以使用惰性IO,但是每个动作都会触及受withFile保护的范围内的文件内容:
main = withFile "test.txt" ReadMode$ \h -> do
xs <- getlines h
mapM_ putStrLn xs
在这种情况下,这并不是太糟糕,但如果您忽略何时需要内容,您应该会发现问题可能会变得更加烦人。懒惰的IO在一个庞大而复杂的程序中可能会很快变得非常烦人,并且当打开的句柄数量的进一步限制开始变得重要时...这就是为什么Haskell社区的新运动要想出解决流媒体内容问题的原因(而不是读取内存中的整个文件,以牺牲内存使用为代价来解决问题,而不是懒惰的IO)。有一段时间,似乎Iteratee将成为标准的解决方案,但它非常复杂且难以理解,即使对于经验丰富的Haskeller,所以其他候选人最近也悄悄上升:目前最有希望或至少成功的似乎是是"conduit"。
答案 4 :(得分:3)
正如其他人所说,hGetContents
是懒惰的。但是,如果你愿意,你可以增加严格性:
import Control.DeepSeq
forceM :: (NFData a, Monad m) => m a -> m a
forceM m = do
val <- m
return $!! val
main = do xs <- withFile "text.txt" ReadMode (forceM . getlines)
...
虽然通常建议您执行与withFile
块内部文件内容相关的所有IO。这样,你的程序实际上可以利用惰性文件读取,只保留内存中所需的数量。如果你正在处理一个非常大的文件,那么强制将整个文件读入内存通常是一个坏主意。
如果您需要更细粒度的资源控制,那么您应该考虑使用ResourceT
(conduit包附带的)或类似的。
[编辑:使用$!!
中的Control.DeepSeq
(而不是$!
)来确保强制整个值。感谢小费,@ benmachine]