IO and Interacting with the World

21.1 The IO Type

IO a is a description of an action that, when executed by the runtime, may perform side effects and produce a value of type a. main :: IO () is the entry point; the runtime executes it.

Pure code can't execute IO actions. IO code can call pure functions freely. This separation is enforced by the type system.

import System.IO
import Control.Monad (unless)

main :: IO ()
main = do
  hSetBuffering stdout NoBuffering
  putStr "What's your name? "
  name <- getLine                       -- `<-` extracts the result of an IO action
  let greeting = "Hello, " ++ name      -- `let` for pure values
  putStrLn greeting
  putStr "Enter numbers separated by spaces: "
  line <- getLine
  let nums = map read (words line) :: [Int]
  print (sum nums)
  loop

loop :: IO ()
loop = do
  putStr "> "
  input <- getLine
  unless (input == "quit") $ do
    putStrLn ("You said: " ++ input)
    loop

21.2 Common IO Functions

putStrLn, putStr :: String -> IO ()
print :: Show a => a -> IO ()
getLine :: IO String
getContents :: IO String                     -- lazy: all of stdin
interact :: (String -> String) -> IO ()      -- interact (map toUpper)
readFile :: FilePath -> IO String
writeFile, appendFile :: FilePath -> String -> IO ()
readLn :: Read a => IO a

import System.Environment (getArgs, getProgName, lookupEnv)
import System.Exit (exitSuccess, exitFailure, exitWith, ExitCode (..))
import System.Directory (doesFileExist, listDirectory, createDirectoryIfMissing, removeFile)

main :: IO ()
main = do
  args <- getArgs
  case args of
    [file] -> do
      exists <- doesFileExist file
      if exists
        then readFile file >>= putStr . unlines . map reverse . lines
        else hPutStrLn stderr "file not found" >> exitFailure
    _ -> do
      prog <- getProgName
      hPutStrLn stderr ("usage: " ++ prog ++ " FILE")
      exitWith (ExitFailure 2)

21.3 Handles and Resource Safety

Lazy IO pitfalls: readFile/hGetContents read lazily, which can cause "handle closed" errors or leaks. Use strict Data.Text.IO/Data.ByteString reads, or streaming libraries (Module 44).

import System.IO
import Control.Exception (bracket)

copyUpper :: FilePath -> FilePath -> IO ()
copyUpper src dst =
  withFile src ReadMode $ \hIn ->            -- the handle is closed automatically, even on exceptions
    withFile dst WriteMode $ \hOut -> do
      contents <- hGetContents hIn
      hPutStr hOut (map toUpper contents)

-- bracket acquire release use
withResource :: IO ()
withResource = bracket (openFile "log.txt" AppendMode) hClose (\h -> hPutStrLn h "entry")

21.4 Mutable References in IO

import Data.IORef
counterDemo :: IO ()
counterDemo = do
  ref <- newIORef (0 :: Int)
  mapM_ (\x -> modifyIORef' ref (+ x)) [1 .. 100]
  readIORef ref >>= print                      -- 5050
-- Also: ST monad for local mutation with a pure interface (Module 22), MVar/TVar for concurrency