Prev: The List monad TOC: Contents Next: The State monad

The IO monad


Overview

Computation type: Computations which perform I/O
Binding strategy: I/O actions are executed in the order in which they are bound. Failures throw I/O errors which can be caught and handled.
Useful for: Performing I/O within a Haskell program.
Zero and plus: None.
Example type: IO a

Motivation

Input/Output is incompatible with a pure functional language because it is not referentially transparent and side-effect free. The IO monad solves this problem by confining computations that perform I/O within the IO monad.

Definition

The definition of the IO monad is platform-specific. No data constructors are exported and no functions are provided to remove data from the IO monad. This makes the IO monad a one-way monad and is essential to ensuring safety of functional programs by isolating side-effects and non-referentially transparent actions within the imperative-style computations of the IO monad.

Throughout this tutorial, we have referred to monadic values as computations. However, values in the IO monad are often called I/O actions and we will use that terminology here.

In Haskell, the top-level main function must have type IO (), so that programs are typically structured at the top level as an imperative-style sequence of I/O actions and calls to functional-style code. The functions exported from the IO module do not perform I/O themselves. They return I/O actions, which describe an I/O operation to be performed. The I/O actions are combined within the IO monad (in a purely functional manner) to create more complex I/O actions, resulting in the final I/O action that is the main value of the program.

The standard prelude and the IO module define many functions that can be used within the IO monad and any Haskell programmer will undoubtedly be familiar with some of them. This tutorial will only discuss the monadic aspects of the IO monad, not the full range of functions available to perform I/O.

The IO type constructor is a member of the Monad class and the MonadError class, where errors are of the type IOError. fail is defined to throw an error built from the string argument. Within the IO monad you can use the exception mechanisms from the Control.Monad.Error module in the Monad Template Library if you import the module. The same mechanisms have alternative names exported by the IO module: ioError and catch.

instance Monad IO where
    return a = ...   -- function from a -> IO a
    m >>= k  = ...   -- executes the I/O action m and binds the value to k's input  
    fail s   = ioError (userError s)

data IOError = ...

ioError :: IOError -> IO a
ioError = ...
   
userError :: String -> IOError
userError = ...

catch :: IO a -> (IOError -> IO a) -> IO a 
catch = ...

try :: IO a -> IO (Either IOError a)
try f = catch (do r <- f
                  return (Right r))
              (return . Left)

The IO monad is incorporated into the Monad Template Library framework as an instance of the MonadError class.

instance Error IOError where
  ...

instance MonadError IO where
    throwError = ioError
    catchError = catch

The IO module exports a convenience function called try that executes an I/O action and returns Right result if the action succeeded or Left IOError if an I/O error was caught.

Example

This example shows a partial implementation of the "tr" command that copies the standard input stream to the standard output stream with character translations controlled by command-line arguments. It demonstrates the use of the exception handling mechanisms of the MonadError class with the IO monad.

Code available in example14.hs
import Monad
import System
import IO
import Control.Monad.Error

-- translate char in set1 to corresponding char in set2
translate :: String -> String -> Char -> Char
translate []     _      c = c
translate (x:xs) []     c = if x == c then ' ' else translate xs []  c
translate (x:xs) [y]    c = if x == c then  y  else translate xs [y] c
translate (x:xs) (y:ys) c = if x == c then  y  else translate xs ys  c

-- translate an entire string
translateString :: String -> String -> String -> String
translateString set1 set2 str = map (translate set1 set2) str

usage :: IOError -> IO ()
usage e = do putStrLn "Usage: ex14 set1 set2"
             putStrLn "Translates characters in set1 on stdin to the corresponding"
             putStrLn "characters from set2 and writes the translation to stdout."

-- translates stdin to stdout based on commandline arguments
main :: IO ()
main = (do [set1,set2] <- getArgs
           contents    <- hGetContents stdin
           putStr $ translateString set1 set2 contents)
       `catchError` usage

Prev: The List monad TOC: Contents Next: The State monad