Skip to content

Haskell IO Guide — Input/Output, File Handling, and Pure vs Impure Separation

DodaTech Updated 2026-06-28 2 min read

In this tutorial, you will learn about Haskell IO Guide. We cover key concepts, practical examples, and best practices to help you master this topic.

Haskell IO operations have the IO type, separating pure functions from side effects -- console I/O with getLine/putStrLn, file I/O with readFile/writeFile, and handle-based I/O with hGetLine for fine-grained control.

Console I/O

main :: IO ()
main = do
    putStrLn "What is your name?"
    name <- getLine
    putStrLn $ "Hello, " ++ name ++ "!"

    -- Print without newline
    putStr "Enter age: "
    age <- readLn :: IO Int
    print (age + 1)

File I/O

-- Simple file operations
main = do
    -- Read entire file
    content <- readFile "input.txt"
    putStrLn content

    -- Write file
    writeFile "output.txt" "Hello, Haskell!"

    -- Append
    appendFile "log.txt" "New log entry\n"

    -- Read and process
    contents <- readFile "data.txt"
    let lines' = lines contents
        count = length lines'
    writeFile "stats.txt" $ show count ++ " lines\n"

Handle-Based I/O

import System.IO

main = do
    -- Open file handle
    handle <- openFile "data.txt" ReadMode
    
    -- Read line by line
    line <- hGetLine handle
    putStrLn line
    
    -- Read rest
    rest <- hGetContents handle
    putStrLn rest
    
    hClose handle

    -- WithFile (automatic close)
    withFile "data.txt" ReadMode $ \handle -> do
        content <- hGetContents handle
        putStrLn content

Lazy I/O

-- readFile is lazy -- reads as needed
main = do
    content <- readFile "large.txt"
    -- Only first line is read from disk
    let firstLine = head (lines content)
    putStrLn firstLine
    -- Rest is read on demand

-- Process line by line without loading all
main = do
    content <- readFile "log.txt"
    mapM_ putStrLn $ filter ("ERROR" `isPrefixOf`) (lines content)

Common Mistakes

1. Mixing pure and impure code

Pure functions can't call IO functions. Pass IO results as arguments to pure functions.

2. Not closing file handles

Use withFile or readFile for automatic resource management. openFile requires explicit hClose.

3. Lazy I/O surprises

Lazy I/O reads file as content is used. If the file handle closes before content is evaluated, you get empty results.

Practice Questions

1. How do you read a file line by line? Use readFile and lines to split, or hGetLine with handle-based I/O for step-by-step reading.

2. What is the difference between putStr and putStrLn? putStr prints a string without a trailing newline. putStrLn adds a newline.

3. How do you read a number from input? readLn :: IO Int reads a line and parses it as an Int. Use readMaybe from Text.Read for safe Parsing.

FAQ

{{< faq question="What is the IO type?" >}} IO a is an action that, when executed, may perform I/O and returns a value of type a. It separates pure from impure code. {{< /faq >}}

{{< faq question="How do I safely close file handles?" >}} Use withFile which automatically closes the handle after the action completes, even on exceptions. {{< /faq >}}

{{< faq question="What is hGetContents?" >}} Reads the entire file from a handle, but lazily -- it reads as the content is consumed. Works with standard functions like lines. {{< /faq >}}

What's Next

Now learn about record syntax in Haskell.

Topic Description Link
Records Data record syntax {{< ref "13-records" >}}
Typeclasses Typeclass basics {{< ref "14-typeclasses" >}}

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro