IO and do-notation
Prerequisites: Monads
A value of type IO a is a description of an action that, when the
runtime executes it, may interact with the world and produces an a.
Building such a description does nothing; only actions reachable from
main ever run.
main :: IO ()
main = do
let greet = putStrLn "hello"
putStrLn "before"
greet
greet
let actions = [putStrLn "one", putStrLn "two"]
sequence_ (reverse actions)
before
hello
hello
two
one
greet is a value — an action — that ran twice because it was used
twice in main. Actions can be stored in lists and combined like any
other value.
Rules of do blocks
import Data.Char (toUpper)
shout :: String -> IO String
shout s = do
let loud = map toUpper s
putStrLn ("Shouting: " ++ loud)
return (loud ++ "!")
main :: IO ()
main = do
result <- shout "hey"
print result
n <- return 5
if n > 3
then putStrLn "big"
else putStrLn "small"
mapM_ print [length result, n]
Shouting: HEY
"HEY!"
big
4
5
x <- actionruns an action and binds its result.let x = exprbinds a pure value.- A line on its own runs an action and ignores its result.
- The last line is the block's result, so its type decides the type of the whole block.
- Both branches of
ifinsidedomust be actions of the same type.
Pure core, IO shell
-- Try it: printf '3 5 7\n10 20\n' | runghc main.hs
stats :: [Int] -> String
stats xs = "sum=" ++ show (sum xs) ++ " max=" ++ show (maximum xs)
processLine :: String -> String
processLine = stats . map read . words
main :: IO ()
main = do
contents <- getContents
mapM_ (putStrLn . processLine) (lines contents)
sum=15 max=7
sum=30 max=20
All the logic is in pure functions (stats, processLine) that you can
test in GHCi. main just reads, calls them, and prints. This structure
is the most important design habit in Haskell.
Loops in IO
import Control.Monad (forM, forM_)
import Data.IORef
main :: IO ()
main = do
squares <- forM [1 .. 5] $ \i -> do
return (i * i)
print squares
counter <- newIORef (0 :: Int)
forM_ [1 .. 10] $ \i -> modifyIORef' counter (+ i)
readIORef counter >>= print
[1,4,9,16,25]
55
IORef is a mutable variable that lives in IO. You rarely need one —
folds and recursion usually express the same thing — but it's there
when an imperative style is genuinely clearer.
Practice
Write a program that reads lines until it sees quit, then prints how
many lines it read (use recursion with a counter).
Show solution
-- Try it: printf 'hello\nworld\nquit\n' | runghc main.hs
loop :: Int -> IO Int
loop n = do
line <- getLine
if line == "quit"
then return n
else loop (n + 1)
main :: IO ()
main = do
count <- loop 0
putStrLn ("Read " ++ show count ++ " lines")
Read 2 lines