The I/O system in Haskell is purely functional, yet has all of the expressive power found in conventional programming languages. To achieve this, Haskell uses a monad to integrate I/O operations into a purely functional context.
The I/O monad used by Haskell mediates between the values natural to a functional language and the actions that characterize I/O operations and imperative programming in general. The order of evaluation of expressions in Haskell is constrained only by data dependencies; an implementation has a great deal of freedom in choosing this order. Actions, however, must be ordered in a well-defined manner for program execution -- and I/O in particular -- to be meaningful. Haskell 's I/O monad provides the user with a way to specify the sequential chaining of actions, and an implementation is obliged to preserve this order.
The term monad comes from a branch of mathematics known as category theory. From the perspective of a Haskell programmer, however, it is best to think of a monad as an abstract datatype. In the case of the I/O monad, the abstract values are the actions mentioned above. Some operations are primitive actions, corresponding to conventional I/O operations. Special operations (methods in the class Monad, see Section 6.2.5) sequentially compose actions, corresponding to sequencing operators (such as the semi-colon) in imperative languages.
All I/O functions defined here are character oriented. The treatment of the newline character will vary on different systems. For example, two characters of input, return and linefeed, may read as a single newline character. These functions cannot be used portably for binary I/O.
For example, a program to print the first 20 integers and their
powers of 2 could be written as:
main = print ([(n, 2^n) | n <- [0..19]])
By default, these input functions echo to standard output. Functions in the I/O library provide full control over echoing.
The following program simply removes all non-ASCII characters from its
standard input and echoes the result on its standard output. (The
isAscii function is defined in a library.)
main = interact (filter isAscii)
The writeFile and appendFile functions write or append the string,
their second argument, to the file, their first argument.
The readFile function reads a file and
returns the contents of the file as a string. The file is read
lazily, on demand, as with getContents.
type FilePath = String
writeFile :: FilePath -> String -> IO ()
appendFile :: FilePath -> String -> IO ()
readFile :: FilePath -> IO String
Note that writeFile and appendFile write a literal string
to a file. To write a value of any printable type, as with print, use the
show function to convert the value to a string first.
main = appendFile "squares" (show [(x,x*x) | x <- [0,0.1..2]])
The do notation allows programming in a more imperative syntactic
style. A slightly more elaborate version of the previous example
would be:
main = do
putStr "Input file: "
ifile <- getLine
putStr "Output file: "
ofile <- getLine
s <- readFile ifile
writeFile ofile (filter isAscii s)
putStr "Filtering successful\n"
The return function is used to define the result of an I/O
operation. For example, getLine is defined in terms of getChar,
using return to define the result the monad:
getLine :: IO String
getLine = do c <- getChar
if c == '\n' then return ""
else do s <- getLine
return (c:s)
Exceptions are raised and caught using the following functions:
fail :: IOError -> IO a
catch :: IO a -> (IOError -> IO a) -> IO a
The fail function raises an exception;
the catch function establishes a handler that receives any
exception raised in the action protected by catch. An exception is
caught by the most recent handler established by catch. These
handlers are not selective: all exceptions are caught. Exception
propagation must be explicitly provided in a handler by re-raising any
unwanted exceptions. For example, in
f = catch g (\e -> if IO.isEOFError e then return [] else fail e)
the function f returns [] when an end-of-file exception occurs
in g; otherwise, the exception is propagated to the next
outer handler. The isEOFError function is part of IO library.
When an exception propagates outside the main program, the Haskell system prints the associated IOError value and exits the program.
The exceptions raised by the I/O functions in the Prelude are defined in the Library Report.