withFile与openFile

时间:2012-02-23 02:50:11

标签: haskell

当给出由\ 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

5 个答案:

答案 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 + withFilehGetContents + 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。这样,你的程序实际上可以利用惰性文件读取,只保留内存中所需的数量。如果你正在处理一个非常大的文件,那么强制将整个文件读入内存通常是一个坏主意。

如果您需要更细粒度的资源控制,那么您应该考虑使用ResourceTconduit包附带的)或类似的。

[编辑:使用$!!中的Control.DeepSeq(而不是$!)来确保强制整个值。感谢小费,@ benmachine]