Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Write at the beginning of a file in Haskell

Tags:

file

haskell

I am trying to add a number at the beginning of a file but the function "appendFile" add at the end of a file.

I wrote this but it didn't work.

myAppendFile file = do x <- readFile file
                   writeFile file "1"
                   appendFile file x
                   return x

when I do:

*main> myAppendFile "File.txt"

the error is

the ressource is busy (file is locked)

so how can I write something at the beginning of a file?

like image 629
Jacko Avatar asked May 13 '15 17:05

Jacko


3 Answers

The traditional way to do this will work in Haskell too. Create a new temporary file, stream bytes from the old one into the temporary one, and then move the temporary one over the old one. You can/should use ByteString or some such for efficiency, but you can do the copying in chunks, rather than needing to read everything into memory and then spit it out again. I think pipes and conduit both offer interfaces that are supposed to make this more pleasant.

like image 123
dfeuer Avatar answered Oct 03 '22 05:10

dfeuer


I managed to make it works but I used ByteString which is a strict data type:

import Data.ByteString.Char8 as B

myAppendFile file = do
    x <- B.readFile file
    B.writeFile file $ B.pack "1"
    B.appendFile file x
    return $ B.unpack x

main = myAppendFile "c:\\test.txt"

Your code gave you that error because of Haskell laziness. When you tried to write something in file, the data was not completely read out in x because haskell don't need x value until that point of code. That's why Haskell still hangs the file.

One more observation, if you have another lazy read before this function, please replace it also with the strict version because you will run into troubles again.

like image 30
Gabriel Ciubotaru Avatar answered Oct 03 '22 05:10

Gabriel Ciubotaru


As Gabriel has pointed out, the problem is that readFile reads file content on demand, and it closes the underlying file handle only when the file content is fully consumed.

The solution is to demand the full file content so that the handler gets closed before doing writeFile.

Short answer: use readFile from strict package.

import qualified System.IO.Strict as SIO
import Data.Functor

myAppendFile :: FilePath -> IO String
myAppendFile file = do
    x <- SIO.readFile file
    writeFile file "1"
    appendFile file x
    return x

main :: IO ()
main = void $ myAppendFile "data"

Another simple "hack" to achieve this is to use seq:

myAppendFile :: FilePath -> IO String
myAppendFile file = do
    x <- readFile file
    length x `seq` writeFile file "1"
    appendFile file x
    return x

seq a b is basically just b plus that when b is demanded, a(length x in this case) is evaluated to WHNF before scrutinizing b, This is because length requires its argument (x in this case) to be traversed until the empty list [] can be seen, so the full file content is demanded.

Keep in mind that by using strict IO, your program will hold all file contents (as a list of Chars) in memory. For toy programs it is fine, but if you care about performance, you can take a look at bytestring or text depending on whether your program deals with bytes or texts(if you want to deal with unicode text). They are both far more efficient than String.

like image 45
Javran Avatar answered Oct 03 '22 06:10

Javran