Skip to content
AgileLogics
6 min read

Basic Input and Output

Prerequisites: Strings and characters

So far every program has only printed. To read input, you use more IO actions inside do.

getLine and <-

-- Try it: printf 'Ada\n36\n' | runghc main.hs
main :: IO ()
main = do
  putStrLn "What is your name?"
  name <- getLine
  putStrLn "How old are you?"
  ageText <- getLine
  let age = read ageText :: Int
  putStrLn ("Hello, " ++ name ++ "! Next year you'll be " ++ show (age + 1) ++ ".")
What is your name?
How old are you?
Hello, Ada! Next year you'll be 37.
  • getLine :: IO String is an action that, when run, produces a String. You can't use it as a string directly.
  • name <- getLine runs the action and names its result. Use <- for results of IO actions and let for ordinary values.
  • The first comment line shows how to feed input from the shell instead of typing it.

Validating input

read crashes on bad input. Use readMaybe and a recursive action to ask again:

-- Try it: printf 'abc\n-4\n7\n' | runghc main.hs
import Text.Read (readMaybe)

askNumber :: IO Int
askNumber = do
  putStrLn "Enter a positive number:"
  input <- getLine
  case readMaybe input of
    Just n | n > 0 -> return n
    _ -> do
      putStrLn ("Not a positive number: " ++ input)
      askNumber

main :: IO ()
main = do
  n <- askNumber
  putStrLn ("Its square is " ++ show (n * n))
Enter a positive number:
Not a positive number: abc
Enter a positive number:
Not a positive number: -4
Enter a positive number:
Its square is 49
  • return n wraps a plain value as an IO action's result. Unlike other languages, return doesn't exit the function — it just produces the value.
  • Just n | n > 0 combines a pattern with a guard.
  • The loop is recursion: askNumber calls itself.

Reading all input with interact

interact passes the whole standard input to a pure function and prints the string it returns — perfect for filters:

-- Try it: printf 'banana\napple\ncherry\n' | runghc main.hs
import Data.List (sort)

main :: IO ()
main = interact (unlines . sort . lines)
apple
banana
cherry

Command-line arguments

-- illustrative: run as  runghc greet.hs Ada Grace
import System.Environment (getArgs, getProgName)

main :: IO ()
main = do
  args <- getArgs
  name <- getProgName
  putStrLn (name ++ " got " ++ show (length args) ++ " arguments")
  mapM_ (\a -> putStrLn ("Hello, " ++ a)) args
greet got 2 arguments
Hello, Ada
Hello, Grace

Keep IO at the edges

A good Haskell program has a thin main that reads input, calls pure functions to do the real work, and prints the result. Pure functions are easy to test in GHCi and can't be broken by the outside world.