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?
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.
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.
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 Char
s) 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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With