Reading Files in Haskell

Reading and writing files are basic tasks needed for many Haskell programs. First we’ll look at some examples of reading files.

import System.IO
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as C8
import Control.Exception (try, SomeException)

-- Reading files requires checking most calls for errors.
-- This helper will streamline our error checks below.
check :: Either SomeException a -> IO a
check result = case result of
    Left e -> error $ show e
    Right value -> return value

main :: IO ()
main = do
    -- Perhaps the most basic file reading task is
    -- slurping a file's entire contents into memory.
    content <- check $ try $ BS.readFile "/tmp/dat"
    C8.putStr content

    -- You'll often want more control over how and what
    -- parts of a file are read. For these tasks, start
    -- by opening a file to obtain a Handle.
    handle <- check $ try $ openFile "/tmp/dat" ReadMode

    -- Read some bytes from the beginning of the file.
    -- Allow up to 5 to be read but also note how many
    -- actually were read.
    b1 <- check $ try $ BS.hGet handle 5
    let n1 = BS.length b1
    putStrLn $ show n1 ++ " bytes: " ++ C8.unpack b1

    -- You can also seek to a known location in the file
    -- and read from there.
    check $ try $ hSeek handle AbsoluteSeek 6
    b2 <- check $ try $ BS.hGet handle 2
    let n2 = BS.length b2
    putStrLn $ show n2 ++ " bytes @ 6: " ++ C8.unpack b2

    -- Other methods of seeking are relative to the
    -- current cursor position,
    check $ try $ hSeek handle RelativeSeek 4

    -- and relative to the end of the file.
    check $ try $ hSeek handle SeekFromEnd (-10)

    -- The bytestring package provides some functions that may
    -- be helpful for file reading. For example, reads
    -- like the ones above can be more robustly
    -- implemented with hGetSome.
    check $ try $ hSeek handle AbsoluteSeek 6
    b3 <- check $ try $ BS.hGetSome handle 2
    let n3 = BS.length b3
    putStrLn $ show n3 ++ " bytes @ 6: " ++ C8.unpack b3

    -- There is no built-in rewind, but
    -- hSeek handle AbsoluteSeek 0 accomplishes this.
    check $ try $ hSeek handle AbsoluteSeek 0

    -- The bytestring package implements efficient
    -- reading methods that may be useful both for its efficiency
    -- with many small reads and because of the additional
    -- reading methods it provides.
    b4 <- check $ try $ BS.hGet handle 5
    putStrLn $ "5 bytes: " ++ C8.unpack b4

    -- Close the file when you're done (usually this would
    -- be scheduled immediately after opening with
    -- bracket).
    hClose handle

To run this program:

$ echo "hello" > /tmp/dat
$ echo "haskell" >>   /tmp/dat
$ runhaskell reading-files.hs
hello
haskell
5 bytes: hello
2 bytes @ 6: ha
2 bytes @ 6: ha
5 bytes: hello

Next we’ll look at writing files.

Note: In Haskell, we use the System.IO module for basic file operations and the Data.ByteString module for efficient handling of binary data. The try function from Control.Exception is used for error handling. The Haskell version doesn’t have an exact equivalent of Go’s Peek function, so we’ve used hGet instead.