[Haskell-beginners] lazy IO in readFile

Andrew Sackville-West andrew at swclan.homelinux.org
Fri May 7 22:47:14 EDT 2010

I'm trying to suss out the best (heh, maybe most idiomatic?) way to
handle laziness in a particular file operation. I'm reading a file
which contains a list of rss feed items that have been seen
previously. I use this list to eliminate feed items I've seen before
and thus filter a list of new items. (it's a program to email me feed
items from a couple of low frequency feeds).

So, the way I do this is to open the history file with readFile, read
it into a list and then use that as a parameter to a filter
function. Instead of getting confusing, here is some simple code that
gets at the nut of the problem:

import Control.Monad

isNewItem :: [String] -> String -> Bool
isNewItem [] = \_ -> True
isNewItem ts = \x -> not (any (== x) ts)

filterItems :: [String] -> [String] -> [String]
filterItems old is = filter (isNewItem old) is

getOldData :: IO [String]
getOldData = catch (liftM lines $ readFile "testfile") (\_ -> return [])

main = do
  let testData = ["a", "b", "c", "d"] :: [String]
  currItems <- getOldData 
  let newItems = filterItems currItems $ testData

  print newItems -- this is important, it mimics another IO action I'm
  		 --  doing in the real code...

  appendFile "testfile" . unlines $ newItems

Please ignore, for the moment, whatever *other* problems (idiomatic or
just idiotic) that may exist above and focus on the IO problem. 

This code works fine *if* the file "testfile" has in it some subset of the
testData list. If it has the complete set, it fails with a "resource
busy" exception. 

Okay, I can more or less understand what's going on here. Each letter
in the testData list gets compared to the contents of the file, but
because they are *all* found, the readFile call never has to try and
fail to read the last line of the file. Thus the file handle is kept
open lazily waiting around not having reached EOF.  Fair enough. 

But what is the best solution? One obvious one, and the one I'm using
now, is to move the appendFile call into a function with guards to
prevent trying to append an empty list to the end of the file. This
solves the problem not by forcing the read on EOF, but by not
bothering to open the file for appending:

writeHistory [] = return ()
writeHistory ni = appendFile "testfile" . unlines $ ni

And this makes some sense. It's silly to try to write nothing to a

But it also rubs me the wrong way. It's not solving the problem
directly -- closing that file handle. So there's my question, how can
I close that thing? Is there some way to force it? Do I need to rework
the reading to read one line ahead of whatever I'm testing against
(thereby forcing the read of EOF and closing the file)? 



-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 198 bytes
Desc: Digital signature
Url : http://www.haskell.org/pipermail/beginners/attachments/20100507/da9e1c97/attachment-0001.bin

More information about the Beginners mailing list