Skip to content
IO Monad
step 1/4

Reading — step 1 of 4

Learn

~1 min readMonads in Practice

IO is the monad for real-world side effects. Pure functions can't do I/O; functions that return IO a can.

main :: IO ()
main = do
    name <- getLine                    -- IO String — extract the line
    putStrLn ("Hello, " ++ name)        -- IO () — write

Common IO actions:

getLine :: IO String
putStr, putStrLn :: String -> IO ()
print :: Show a => a -> IO ()           -- print x = putStrLn (show x)
readLn :: Read a => IO a                 -- parse one line of typed input
getContents :: IO String                  -- read all of stdin

Mixing pure and IO — use <- to extract IO values, regular bindings (let) for pure work:

main :: IO ()
main = do
    line <- getLine
    let upper = map toUpper line       -- pure transform
    putStrLn upper

Loops in IO — no built-in for, use these:

import Control.Monad

forM_ [1..5] $ \n -> do
    putStrLn ("line " ++ show n)

replicateM_ 3 (putStrLn "hi")          -- print 3 times

mapM_ putStrLn ["a", "b", "c"]

-- with results:
results <- mapM readLn [(); (); ()]    -- read 3 things

Why IO is a monad — same do/>>= syntax as Maybe, but threading a different context (the world state, conceptually). The pattern of "a sequence of computations" works for both pure error handling and impure side effects.

Pure code can never "escape" IO — there's no unsafelyRunIO :: IO a -> a. This separation is what makes Haskell's purity work in practice.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…