withFile与openFile

ric*_*k8r 27 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
Run Code Online (Sandbox Code Playgroud)

用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  
Run Code Online (Sandbox Code Playgroud)

我设法完全没有输出.我很难过.

编辑:不再难过了:感谢一个人和所有人的深思熟虑和发人深省的答案.我在文档中做了一些阅读,并了解到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
Run Code Online (Sandbox Code Playgroud)

Lam*_*iry 30

该文件过早关闭.从文档:

句柄将在withFile退出时关闭

这意味着withFile函数返回后文件将立即关闭.

因为hGetContents和朋友是懒惰的,它不会尝试读取文件,直到它被强制使用putStrLn,但到那时,withFile就已经关闭了文件.

要解决这个问题,请将整个内容传递给withFile:

main = withFile "test.txt" ReadMode $ \handle -> do
           xs <- getlines handle
           sequence_ $ map putStrLn xs
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为withFile到了关闭文件的时候,你已经打印过了.

  • 并非所有IO在Haskell中都是懒惰的.只是这样实现的IO,包括`hGetContents`. (7认同)

Rei*_*ton 12

呃,没有人给出简单的解决方案吗?

main :: IO ()
main = do xs <- fmap lines $ readFile "test.txt"
          mapM_ putStrLn xs
Run Code Online (Sandbox Code Playgroud)

你可以使用时不要使用openFile+ hGetContentswithFile+ .随着你不能过早地关闭文件搬起石头砸自己的脚.hGetContentsreadFilereadFile

  • 我不相信解决比提问者要求的更普遍的问题.你可以完成任务的复杂程度是没有限制的,所以这就是疯狂.只需解决你面前的任务.这里没有迹象表明OP想要使用特殊模式寻找或打开文件或打开多个文件.`readFile`是一种非常方便的日常功能,我常常看到人们使用自己的复杂代码来读取文件,这可能是因为他们习惯于那些不提供与`readFile`等效的单一功能的语言. (5认同)
  • 其他答案解决"为什么我的程序以这种令人惊讶的方式表现",这一切都很好.然后他们都以各种复杂的方式重新实施OP的计划.我刚刚发现这是一个奇怪的遗漏,没有人提到简单的2线方式来做OP想要做的事情. (2认同)

por*_*ges 7

他们完全不同的事情. openFile打开一个文件并返回一个文件句柄:

openFile :: FilePath -> IOMode -> IO Handle
Run Code Online (Sandbox Code Playgroud)

withFile 用于包装带有文件句柄的IO计算,确保句柄在之后关闭:

withFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r
Run Code Online (Sandbox Code Playgroud)

在您的情况下,使用withFile将如下所示:

main = withFile "test.txt" ReadMode $ \h -> do
      xs <- getlines h
      sequence_ $ map putStrLn xs
Run Code Online (Sandbox Code Playgroud)

您当前拥有的版本将打开文件,调用getlines,然后关闭该文件.由于getlines是懒惰的,因此在文件打开时不会读取任何输出,一旦文件关闭,它就不能.


Jed*_*dai 5

你正在遇到懒惰IO的常见障碍......懒惰的IO听起来像是一个很好的主意,使得流式传输变得轻而易举,直到你开始遇到那些可怕的问题.

并不是说你的特殊情况不会是一个经验丰富的Haskeller的红色鲱鱼:这是为什么懒惰IO是一个问题的教科书示例.

main = do xs <- withFile "test.txt" ReadMode getlines
          sequence_ $ map putStrLn xs
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

在这种情况下,这不是太糟糕,但如果您忽略何时需要内容,您应该会发现问题可能会变得更加烦人.懒惰的IO在一个庞大而复杂的程序中可能会很快变得非常烦人,并且当打开的句柄数量的进一步限制开始变得重要时...这就是为什么Haskell社区的新运动要想出解决流媒体内容问题的原因(而不是读取内存中的整个文件,以牺牲内存使用为代价来解决问题,而不是懒惰的IO).有一段时间,似乎Iteratee将成为标准的解决方案,但它非常复杂且难以理解,即使对于经验丰富的Haskeller,所以其他候选人最近也悄悄上升:目前最有希望或至少成功的似乎是是"管道".

  • 另请参阅:[懒惰的IO有什么坏处?](http://stackoverflow.com/questions/5892653/whats-so-bad-about-lazy-io) (3认同)