Haskell
Data Input
Functional Programming
Programming Languages
Code Examples

Inputting Data with Haskell

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Input in Haskell is different from input in many imperative languages because side effects are isolated inside the IO type. That means reading from the keyboard or a file is still straightforward, but the code makes the boundary between pure logic and effectful input explicit.

Reading a Line from Standard Input

The most common starting point is getLine, which returns an IO String.

haskell
1main :: IO ()
2main = do
3    putStrLn "What is your name?"
4    name <- getLine
5    putStrLn ("Hello, " ++ name)

The <- syntax extracts the value from an IO action inside a do block. Outside IO, you cannot treat getLine as a plain string because it represents an action that has not yet been run.

That is one of the core ideas in Haskell input handling: reading input is not just a value lookup, it is an effectful computation.

Parsing Input into Other Types

Raw input is text, so you often need to parse it into numbers or other structures. A simple version uses read, but read crashes if the input is invalid.

haskell
1main :: IO ()
2main = do
3    putStrLn "Enter a number:"
4    line <- getLine
5    let n = read line :: Int
6    print (n * 2)

For safer programs, use readMaybe from Text.Read:

haskell
1import Text.Read (readMaybe)
2
3main :: IO ()
4main = do
5    putStrLn "Enter a number:"
6    line <- getLine
7    case readMaybe line :: Maybe Int of
8        Just n  -> print (n * 2)
9        Nothing -> putStrLn "That was not a valid integer."

This pattern is important because it keeps parsing failures in ordinary program logic rather than turning them into runtime exceptions.

Reading Multiple Values

When input contains several values on one line, use words to split on whitespace and then parse the parts.

haskell
1import Text.Read (readMaybe)
2
3main :: IO ()
4main = do
5    putStrLn "Enter two integers separated by spaces:"
6    line <- getLine
7    let parts = words line
8    case mapM readMaybe parts :: Maybe [Int] of
9        Just [a, b] -> print (a + b)
10        _           -> putStrLn "Please enter exactly two integers."

mapM readMaybe is a useful idiom because it converts a list of Maybe values into one Maybe list that succeeds only if every element parses successfully.

Reading from Files

File input follows the same IO model:

haskell
1main :: IO ()
2main = do
3    contents <- readFile "input.txt"
4    putStrLn contents

You can then pass contents into pure functions for parsing and transformation. That separation is a strength of Haskell style: the input action stays small, while the business logic remains pure and testable.

Keep Pure Logic Separate

A good Haskell design usually reads input in main, converts it into ordinary values, and then hands those values to pure functions.

haskell
squareList :: [Int] -> [Int]
squareList xs = map (\x -> x * x) xs

This makes testing easier because squareList does not depend on the console or files. The input layer becomes a thin wrapper around pure computation instead of mixing everything together.

Thinking in IO Instead of Fighting It

Beginners often treat IO as an inconvenience, but it is really a design tool. By forcing input and output to stay explicit, Haskell prevents effectful code from leaking silently into otherwise pure functions. Once that model clicks, reading data in Haskell becomes less unusual and more disciplined.

That discipline pays off when programs grow. Small input actions remain easy to change, while pure functions stay easy to test and reason about.

Common Pitfalls

  • Treating IO String as though it were just String. Input actions must be executed inside IO.
  • Using read on untrusted input and crashing on invalid text.
  • Mixing too much parsing and business logic directly into main.
  • Forgetting that words splits on whitespace, not commas or other separators.
  • Assuming file and console input work differently at the type level. Both are just IO actions returning values.

Summary

  • Haskell input lives inside the IO type because reading data is an effect.
  • 'getLine is the basic way to read console input.'
  • Use readMaybe instead of read when invalid input is possible.
  • Keep effectful input code small and move real logic into pure functions.
  • File input and console input follow the same underlying pattern in Haskell.

Course illustration
Course illustration

All Rights Reserved.