File 在Haskell中读写文件

File 在Haskell中读写文件,file,haskell,io,File,Haskell,Io,我试图读取文件的内容,将文本转换为大写,然后写回 以下是我编写的代码: import System.IO import Data.Char main = do handle <- openFile "file.txt" ReadWriteMode contents <- hGetContents handle hClose handle writeFile "file.txt" (map toUpper contents) return ()

我试图读取文件的内容,将文本转换为大写,然后写回

以下是我编写的代码:

import System.IO
import Data.Char

main = do
    handle <- openFile "file.txt" ReadWriteMode
    contents <- hGetContents handle
    hClose handle
    writeFile "file.txt" (map toUpper contents)
    return ()
import System.IO
导入数据.Char
main=do

我认为你的问题在于hGetContents很懒惰。使用hGetContents时,不会立即读入文件的内容。当需要时,它们会被读入

在第一个示例中,您打开文件并说您需要内容,但在对其执行任何操作之前,请先关闭文件。然后,您写入该文件。当您写入现有文件时,内容将被清除,但由于您关闭了该文件,因此您不再有权访问文件内容

在第二个示例中,您打开该文件,然后尝试写入该文件,但由于内容直到需要时才真正读取(当它们被转换并写回时),因此您最终尝试同时写入和读取同一文件


您可以写入名为file2.txt的文件,然后在完成后,删除file.txt并将file2.txt重命名为file.txt。懒惰IO是不好的,这通常被认为是Haskell的一个难点。基本上,
内容
在您将其写回磁盘之前不会计算,此时无法计算,因为文件已关闭。您可以通过多种方式解决此问题,而无需使用额外的库。您可以使用
readFile
函数,然后在写回之前检查长度:

import Control.Monad (when)

main = do
    contents <- readFile "file.txt"
    let newContents = map toUpper contents
    when (length newContents > 0) $
        writeFile "file.txt" newContents
import Control.Monad(何时)
main=do
目录(0)$
writeFile“file.txt”newContents
我想说,这段代码实际上更好,因为您不会写回一个已经是空的文件,这是一个无意义的操作


另一种方法是使用流式库,<代码>管道是一个受欢迎的选择,有一些好的教程和坚实的数学基础,这也是我的选择。

这里有一些事情在

您在
ReadWriteMode
中打开了文件,但只读取了内容。为什么不为两者使用相同的手柄

main = do
    handle <- openFile "file.txt" ReadWriteMode
    contents <- hGetContents' handle
    hSeek handle AbsoluteSeek 0
    hPutStr handle (map toUpper contents)
    hClose handle
    return ()

@bwroga的回答完全正确。以下是建议方法的实现(写入临时文件并重命名):

import Data.Char(toUpper)
导入System.Directory(重命名文件,getTemporaryDirectory)
导入System.Environment(getArgs)
main=do
[文件]=写文件tmpFile。图珀地图
重命名文件tmpFile文件

第二次尝试失败,因为OP试图打开一个文件进行写入,而该文件已经被打开进行写入(因为
ReadWriteMode
)安东·古里亚诺夫:在我的实验中,即使文件刚用
ReadMode
打开,它也失败了,您正在强制在写入文件之前对newContents进行完全评估。对吗?@bwroga
length newContents
本身不会强制执行任何操作,当我们强制对整个表达式
length newContents>0
求值时,
newContents
才得到求值。由于第一个参数在第二个参数之前求值,请不要建议kludges让
Handle
工作,同时也不要提及一些更好的替代方法。FWIW,
hGetContents'
最好用实现(事实上,这是该页面上的示例)。或者只是
hGetContents'h=do{c0)然后返回c,否则返回“”}
。Will Ness:这不会阻止句柄处于半封闭状态是的,但返回的
c
已经包含整个文件的内容,
length
将其强制执行到末尾,因此不再需要从文件中读取任何内容。我用
c Will Ness测试了它:虽然这是OP问题的有效答案,但我的答案是关于使用单个句柄进行读写,所以我看不出相关性。你已经正确地指出了问题,但你的解决方案是不必要的复杂。@leftaroundabout是你推荐的贝克利尔方法吗?是的,<>代码> Read Frpult/Cord>是我推荐的,除非有一个理由不(性能等),或者你已经在项目中使用了<代码>管道<代码>或<代码>管道>代码。对新文件的写入和重命名方法实际上是更好的,因为在写入中间崩溃的情况下不会丢失数据。
main = do
    handle <- openFile "file.txt" ReadWriteMode
    contents <- hGetContents' handle
    hSeek handle AbsoluteSeek 0
    hPutStr handle (map toUpper contents)
    hClose handle
    return ()
hGetContents' :: Handle -> IO String
hGetContents' h = do
  eof <- hIsEOF h
  if eof
    then
      return []
    else do
      c <- hGetChar h
      fmap (c:) $ hGetContents' h
import Data.Char (toUpper)
import System.Directory (renameFile, getTemporaryDirectory)
import System.Environment (getArgs)

main = do
    [file] <- getArgs
    tmpDir <- getTemporaryDirectory
    let tmpFile = tmpDir ++ "/" ++ file
    readFile file >>= writeFile tmpFile . map toUpper
    renameFile tmpFile file