File I/O: reading from, writing to, same file in one function

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? In a main do block I have tried: contents <- readFile fileName -- do stuff to contents-- writeFile fileName result 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 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. 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?? Many thanks, Geoffrey

On Thu, Feb 05, 2015 at 02:51:52PM -0500, Geoffrey Bays wrote:
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?
Try `withFile` [1], there is an example of its usage in chapter 9 of LYAH [2]. [1] https://downloads.haskell.org/~ghc/6.12.2/docs/html/libraries/base-4.2.0.1/S... [2] http://learnyouahaskell.com/input-and-output#files-and-streams

Francesco:
Unfortunately, using withFile I get the same error as before with handles:
hPutStr: illegal operation (handle is closed).
Any other suggestions for reading and writing to the same file in the same
function?
Thanks
On Thu, Feb 5, 2015 at 3:26 PM, Francesco Ariis
On Thu, Feb 05, 2015 at 02:51:52PM -0500, Geoffrey Bays wrote:
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?
Try `withFile` [1], there is an example of its usage in chapter 9 of LYAH [2].
[1] https://downloads.haskell.org/~ghc/6.12.2/docs/html/libraries/base-4.2.0.1/S... [2] http://learnyouahaskell.com/input-and-output#files-and-streams _______________________________________________ Beginners mailing list Beginners@haskell.org http://www.haskell.org/mailman/listinfo/beginners

On Thu, Feb 5, 2015 at 10:30 PM, Geoffrey Bays
Francesco:
Unfortunately, using withFile I get the same error as before with handles: hPutStr: illegal operation (handle is closed).
This error suggests you're forgetting to reopen your file in WriteMode once withFile closed it (you *did *read the documentation of withFile ?). Trying to write on a closed handle is an exercise in futility. -- Jedaï

If you're still wedded to your notion (I strongly urge you to use the proper way with temp file and renaming, even making it a function if you need to repeat this several times) : readModifyWrite fileName modify = do contents <- readFile fileName evaluate (length contents) -- ensure that contents is evaluated and the handle closed writeFile fileName (modify contents) This should work (but may consume a fair bit of memory, you should probably use Text or ByteString anyway and use the proper way instead). -- Jedaï

Chaddai:
Thanks for your excellent explanation of why using a temp file is necessary
and a good thing to do.
The problem also with using withFile and a lambda is that in my infinite
Haskell beginnerness, I do not know how to get the
contents out of the lambda for use later for the second try of withFile in
WriteMode.
As in LYAH
1. withFile "girlfriend.txt" ReadMode (\handle -> do
2. contents <- hGetContents handle
3. putStr contents)
how to retrieve contents for later use? Scope of contents variable in
inside lambda only it would appear.
Can I declare an empty contents IO String before the withFile lambda
somehow?
Thanks
On Thu, Feb 5, 2015 at 4:33 PM, Chaddaï Fouché
On Thu, Feb 5, 2015 at 10:30 PM, Geoffrey Bays
wrote: Francesco:
Unfortunately, using withFile I get the same error as before with handles: hPutStr: illegal operation (handle is closed).
This error suggests you're forgetting to reopen your file in WriteMode once withFile closed it (you *did *read the documentation of withFile ?). Trying to write on a closed handle is an exercise in futility.
-- Jedaï
_______________________________________________ Beginners mailing list Beginners@haskell.org http://www.haskell.org/mailman/listinfo/beginners

On Thu, Feb 5, 2015 at 10:43 PM, Geoffrey Bays
The problem also with using withFile and a lambda is that in my infinite Haskell beginnerness, I do not know how to get the contents out of the lambda for use later for the second try of withFile in WriteMode.
As in LYAH
1. withFile "girlfriend.txt" ReadMode (\handle -> do 2. contents <- hGetContents handle 3. putStr contents)
how to retrieve contents for later use? Scope of contents variable in inside lambda only it would appear.
Well withFile type is "FilePath -> Mode -> (Handle -> IO a) -> IO a", note that "IO a", this "a" is a type variable that can be any type, this means that the action you do in your lambda may return any type, and the type returned by the whole withFile action is also "a", since this can be anything, withFile can't invent it, so this must be the same thing that the action in your lambda returned. Thus you can return the content you wished for : contents <- withFile fileName ReadMode $ \h -> do contentsInside <- hGetContents handle evaluate (length contentsInside) -- still the same, you have to evaluate the whole contents now since withFile will close the handle as soon as you exit your lambda, use a strict variant of hGetContents to avoid this line return contentsInside Note this is convoluted, using a strict variant of hGetContents would allows you to just go : contents <- withFile fileName ReadMode $ \h -> hGetContents h Text for instance provide such a strict variant in Data.Text.IO (but you should then just use its strict variant of readFile which is exactly equivalent to what I just wrote). -- Jedaï

Thanks, Chaddai. It seems quite obvious once you explain it. And I now know
that ghci :t is my friend indeed.
Geoffrey
On Thu, Feb 5, 2015 at 4:55 PM, Chaddaï Fouché
On Thu, Feb 5, 2015 at 10:43 PM, Geoffrey Bays
wrote: The problem also with using withFile and a lambda is that in my infinite Haskell beginnerness, I do not know how to get the contents out of the lambda for use later for the second try of withFile in WriteMode.
As in LYAH
1. withFile "girlfriend.txt" ReadMode (\handle -> do 2. contents <- hGetContents handle 3. putStr contents)
how to retrieve contents for later use? Scope of contents variable in inside lambda only it would appear.
Well withFile type is "FilePath -> Mode -> (Handle -> IO a) -> IO a", note that "IO a", this "a" is a type variable that can be any type, this means that the action you do in your lambda may return any type, and the type returned by the whole withFile action is also "a", since this can be anything, withFile can't invent it, so this must be the same thing that the action in your lambda returned. Thus you can return the content you wished for :
contents <- withFile fileName ReadMode $ \h -> do contentsInside <- hGetContents handle evaluate (length contentsInside) -- still the same, you have to evaluate the whole contents now since withFile will close the handle as soon as you exit your lambda, use a strict variant of hGetContents to avoid this line return contentsInside
Note this is convoluted, using a strict variant of hGetContents would allows you to just go : contents <- withFile fileName ReadMode $ \h -> hGetContents h Text for instance provide such a strict variant in Data.Text.IO (but you should then just use its strict variant of readFile which is exactly equivalent to what I just wrote).
-- Jedaï
_______________________________________________ Beginners mailing list Beginners@haskell.org http://www.haskell.org/mailman/listinfo/beginners

Hello,
On Thu, Feb 5, 2015 at 8:51 PM, Geoffrey Bays
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ï
participants (3)
-
Chaddaï Fouché
-
Francesco Ariis
-
Geoffrey Bays