Hello,

On Thu, Feb 5, 2015 at 8:51 PM, Geoffrey Bays <charioteer7@gmail.com> wrote:
Question:

As I work my way through Learn You as Haskell, I have been writing small File IO programs.
Unlike the example in that book, where the author reads from one file, writes altered data to a temp file and then deletes the original file and renames the temp file, I have been trying to read from a given file, alter the data and then overwrite the same file with the new data. Is this possible?

It is certainly possible, whether it is a good idea is another thing entirely.
 

In a main  do block I have tried:

contents <- readFile fileName

-- do stuff to contents--

writeFile fileName result


This generally won't work because readFile is lazy, it only deliver a promise of content without reading it immediately, to allow simple streaming. To do that it has to hold on an opened handle to fileName, it will only close it when the the EOF is reached in contents, so when contents is completely evaluated, which depending on your code will probably only happen when result is completely evaluated... Except it won't be since writeFile would be the one doing this evaluation and writeFile won't even start because it needs to open fileName in WriteMode and fileName is still open in ReadMode...
 
After using appendFile to add items to the file, when I call the function to read and write I get this error:
openFile: resource busy, (file is locked)

I have also tried using the file handles like so:

    handle <- openFile fileName ReadWriteMode
    contents <- hGetContents handle
 -- do stuff to contents --

    hPutStr handle  results
   hClose handle

Interleaving lazy IO in the form of hGetContents and trying to write on the same handle strike me as a really bad idea... Supposing it worked, where exactly in the file did you think this would write ? ReadWriteMode is generally a very bad idea (whatever the language) except if you're manipulating a binary format with fixed length fields and, even then, it requires good discipline.
 

I have also tried opening with one handle to read, closing it, then using another handle for writing,
but the error message is that the handle to the file is closed.
 

That should work, provided you do it properly but you probably still used lazy IO so you're left with a promise of content but you closed the handle that was supposed to provide this content... If you were to use strict IO though that would mean you would have to get the entire content of your file in memory which depending on the size of this file may be bad or even catastrophic (hint : String is extremely wasteful, Text is better for text and ByteString for binary formats).
 

I am thinking that the the author of LYAH maybe has good reasons to use a temp file, or is there another way that is not too difficult to grasp??

Right !
Reading from a file and writing to a temp file then closing everything and renaming is the proper way to handle your workflow, it has only advantages :
  • you can stream the content of your file while modifying it, using far less memory (RAM) in exchange for an insignificant bump in disk usage,
  • your modification is atomic (in a proper filesystem) : if your program is interrupted for whatever reason, you're not left with a corrupted file but the intact original and an incomplete temp file you can use for diagnostic

This is actually true whatever language you're using (and is the recommended way by all experts) ! But in Haskell the other way is even trickier to get right if you don't understand when you should use strict IO (tip : getContents and readFile are both lazy IO and completely inappropriate if controlling when the IO happens is important, like here).

So given that the other way is both harder to get right and generally a bad idea anyway, I really would recommend to use the proper way.


--

Jedaï