3

When I try to run this code...

module Main where

import qualified Data.Text.Lazy.IO as LTIO
import qualified Data.Text.Lazy as LT
import System.IO (IOMode(..), withFile)

getFirstLine :: FilePath -> IO String
getFirstLine path =
        withFile path ReadMode (\f -> do
                contents <- LTIO.hGetContents f
                return ("-- "++(LT.unpack . head $ LT.lines contents)++" --"))

main::IO()
main = do
        firstLine <- getFirstLine "/tmp/foo.csv"
        print firstLine

I get

"-- *** Exception: Prelude.head: empty list

... where I would expect it to print the first line of "/tmp/foo.csv". Could you please explain why? Ultimately, I'm trying to figure out how to create a lazy list of Texts from file input.

brooks94
  • 3,836
  • 4
  • 30
  • 57

2 Answers2

4

As Daniel Lyons mentions in a comment, this is due to IO and laziness interacting.

Imagine, if you will:

  • withFile opens the file, to file handle f.
  • Thunk using contents of f is returned.
  • withFile closes the file.
  • Thunk is evaluated. There are no contents in a closed file.

This trap is mentioned on the HaskellWiki / Maintaining laziness page.

To fix, you can either read the whole file contents within withFile (possibly by forcing it with seq) or lazily close the file instead of using withFile.

ephemient
  • 198,619
  • 38
  • 280
  • 391
1

I think it's like this: withFile closes the file after executing the function. hGetContents reads the contents lazily (lazy IO), and by the time it needs to read the stuff, the file is closed.

Instead of using withFile, try just using openFile, and not closing it. hGetContents will place the file in semi-closed state after it's reading from it. Or better, just read the contents directly using readFile

newacct
  • 119,665
  • 29
  • 163
  • 224