# IO
# Getting the 'a' "out of" 'IO a'
A common question is "I have a value of IO a
, but I want to do something to that a
value: how do I get access to it?" How can one operate on data that comes from the outside world (for example, incrementing a number typed by the user)?
The point is that if you use a pure function on data obtained impurely, then the result is still impure. It depends on what the user did! A value of type IO a
stands for a "side-effecting computation resulting in a value of type a
" which can only be run by (a) composing it into main
and (b) compiling and executing your program. For that reason, there is no way within pure Haskell world to "get the a
out".
Instead, we want to build a new computation, a new IO
value, which makes use of the a
value at runtime. This is another way of composing IO
values and so again we can use do
-notation:
-- assuming
myComputation :: IO Int
getMessage :: Int -> String
getMessage int = "My computation resulted in: " ++ show int
newComputation :: IO ()
newComputation = do
int <- myComputation -- we "bind" the result of myComputation to a name, 'int'
putStrLn $ getMessage int -- 'int' holds a value of type Int
Here we're using a pure function (getMessage
) to turn an Int
into a String
, but we're using do
notation to make it be applied to the result of an IO
computation myComputation
when (after) that computation runs. The result is a bigger IO
computation, newComputation
. This technique of using pure functions in an impure context is called lifting.
# IO defines your program's main
action
To make a Haskell program executable you must provide a file with a main
function of type IO ()
main :: IO ()
main = putStrLn "Hello world!"
When Haskell is compiled it examines the IO
data here and turns it into a executable. When we run this program it will print Hello world!
.
If you have values of type IO a
other than main
they won't do anything.
other :: IO ()
other = putStrLn "I won't get printed"
main :: IO ()
main = putStrLn "Hello world!"
Compiling this program and running it will have the same effect as the last example. The code in other
is ignored.
In order to make the code in other
have runtime effects you have to compose it into main
. No IO
values not eventually composed into main
will have any runtime effect. To compose two IO
values sequentially you can use do
-notation:
other :: IO ()
other = putStrLn "I will get printed... but only at the point where I'm composed into main"
main :: IO ()
main = do
putStrLn "Hello world!"
other
When you compile and run this program it outputs
Hello world!
I will get printed... but only at the point where I'm composed into main
Note that the order of operations is described by how other
was composed into main
and not the definition order.
# Checking for end-of-file conditions
A bit counter-intuitive to the way most other languages' standard I/O libraries do it, Haskell's isEOF
does not require you to perform a read operation before checking for an EOF condition; the runtime will do it for you.
import System.IO( isEOF )
eofTest :: Int -> IO ()
eofTest line = do
end <- isEOF
if end then
putStrLn $ "End-of-file reached at line " ++ show line ++ "."
else do
getLine
eofTest $ line + 1
main :: IO ()
main =
eofTest 1
Input:
Line #1.
Line #2.
Line #3.
Output:
End-of-file reached at line 4.
# Reading all contents of standard input into a string
main = do
input <- getContents
putStr input
Input:
This is an example sentence.
And this one is, too!
Output:
This is an example sentence.
And this one is, too!
Note: This program will actually print parts of the output before all of the input has been fully read in. This means that, if, for example, you use getContents
over a 50MiB file, Haskell's lazy evaluation and garbage collector will ensure that only the parts of the file that are currently needed (read: indispensable for further execution) will be loaded into memory. Thus, the 50MiB file won't be loaded into memory at once.
# Role and Purpose of IO
Haskell is a pure language, meaning that expressions cannot have side effects. A side effect is anything that the expression or function does other than produce a value, for example, modify a global counter or print to standard output.
In Haskell, side-effectful computations (specifically, those which can have an effect on the real world) are modelled using IO
. Strictly speaking, IO
is a type constructor, taking a type and producing a type. For example, IO Int
is the type of an I/O computation producing an Int
value. The IO
type is abstract, and the interface provided for IO
ensures that certain illegal values (that is, functions with non-sensical types) cannot exist, by ensuring that all built-in functions which perform IO have a return type enclosed in IO
.
When a Haskell program is run, the computation represented by the Haskell value named main
, whose type can be IO x
for any type x
, is executed.
# Manipulating IO values
There are many functions in the standard library providing typical IO
actions that a general purpose programming language should perform, such as reading and writing to file handles. General IO
actions are created and combined primarily with two functions:
(>>=) :: IO a -> (a -> IO b) -> IO b
This function (typically called bind) takes an IO
action and a function which returns an IO
action, and produces the IO
action which is the result of applying the function to the value produced by the first IO
action.
return :: a -> IO a
This function takes any value (i.e., a pure value) and returns the IO computation which does no IO and produces the given value. In other words, it is a no-op I/O action.
There are additional general functions which are often used, but all can be written in terms of the two above. For example, (>>) :: IO a -> IO b -> IO b
is similar to (>>=)
but the result of the first action is ignored.
A simple program greeting the user using these functions:
main :: IO ()
main =
putStrLn "What is your name?" >>
getLine >>= \name ->
putStrLn ("Hello " ++ name ++ "!")
This program also uses putStrLn :: String -> IO ()
and getLine :: IO String
.
Note: the types of certain functions above are actually more general than those types given (namely >>=
, >>
and return
).
# IO semantics
The IO
type in Haskell has very similar semantics to that of imperative programming languages. For example, when one writes s1 ; s2
in an imperative language to indicate executing statement s1
, then statement s2
, one can write s1 >> s2
to model the same thing in Haskell.
However, the semantics of IO
diverge slightly of what would be expected coming from an imperative background. The return
function does not interrupt control flow - it has no effect on the program if another IO
action is run in sequence. For example, return () >> putStrLn "boom"
correctly prints "boom" to standard output.
The formal semantics of IO
can given in terms of simple equalities involving the functions in the previous section:
return x >>= f ≡ f x, ∀ f x
y >>= return ≡ return y, ∀ y
(m >>= f) >>= g ≡ m >>= (\x -> (f x >>= g)), ∀ m f g
These laws are typically referred to as left identity, right identity, and composition, respectively. They can be stated more naturally in terms of the function
(>=>) :: (a -> IO b) -> (b -> IO c) -> a -> IO c
(f >=> g) x = (f x) >>= g
as follows:
return >=> f ≡ f, ∀ f
f >=> return ≡ f, ∀ f
(f >=> g) >=> h ≡ f >=> (g >=> h), ∀ f g h
# Lazy IO
Functions performing I/O computations are typically strict, meaning that all preceding actions in a sequence of actions must be completed before the next action is begun. Typically this is useful and expected behaviour - putStrLn "X" >> putStrLn "Y"
should print "XY". However, certain library functions perform I/O lazily, meaning that the I/O actions required to produce the value are only performed when the value is actually consumed. Examples of such functions are getContents
and readFile
. Lazy I/O can drastically reduce the performance of a Haskell program, so when using library functions, care should be taken to note which functions are lazy.
# IO and do
notation
Haskell provides a simpler method of combining different IO values into larger IO values. This special syntax is known as do
notation* and is simply syntactic sugar for usages of the >>=
, >>
and return
functions.
The program in the previous section can be written in two different ways using do
notation, the first being layout-sensitive and the second being layout insensitive:
main = do
putStrLn "What is your name?"
name <- getLine
putStrLn ("Hello " ++ name ++ "!")
main = do {
putStrLn "What is your name?" ;
name <- getLine ;
putStrLn ("Hello " ++ name ++ "!")
}
All three programs are exactly equivalent.
*Note that do
notation is also applicable to a broader class of type constructors called monads.
# Reading a line from standard input
main = do
line <- getLine
putStrLn line
Input:
This is an example.
Output:
This is an example.
# Reading words from an entire file
In Haskell, it often makes sense not to bother with file handles at all, but simply read or write an entire file straight from disk to memory†, and do all the partitioning/processing of the text with the pure string data structure. This avoids mixing IO and program logic, which can greatly help avoiding bugs.
-- | The interesting part of the program, which actually processes data
-- but doesn't do any IO!
reverseWords :: String -> [String]
reverseWords = reverse . words
-- | A simple wrapper that only fetches the data from disk, lets
-- 'reverseWords' do its job, and puts the result to stdout.
main :: IO ()
main = do
content <- readFile "loremipsum.txt"
mapM_ putStrLn $ reverseWords content
If loremipsum.txt
contains
Lorem ipsum dolor sit amet,
consectetur adipiscing elit
then the program will output
elit
adipiscing
consectetur
amet,
sit
dolor
ipsum
Lorem
Here, mapM_
(opens new window) went through the list of all words in the file, and printed each of them to a separate line with putStrLn
(opens new window).
†If you think this is wasteful on memory, you have a point. Actually, Haskell's laziness can often avoid that the entire file needs to reside in memory simultaneously... but beware, this kind of lazy IO causes its own set of problems. For performance-critical applications, it often makes sense to enforce the entire file to be read at once, strictly; you can do this with the Data.Text
version of readFile
. (opens new window)
# Writing to stdout
Per the Haskell 2010 Language Specification (opens new window) the following are standard IO functions available in Prelude, so no imports are required to use them.
# putChar :: Char -> IO ()
- writes a char
to stdout
Prelude> putChar 'a'
aPrelude> -- Note, no new line
# putStr :: String -> IO ()
- writes a String
to stdout
Prelude> putStr "This is a string!"
This is a string!Prelude> -- Note, no new line
# putStrLn :: String -> IO ()
- writes a String
to stdout
and adds a new line
Prelude> putStrLn "Hi there, this is another String!"
Hi there, this is another String!
# print :: Show a => a -> IO ()
- writes a
an instance of Show
to stdout
Prelude> print "hi"
"hi"
Prelude> print 1
1
Prelude> print 'a'
'a'
Prelude> print (Just 'a') -- Maybe is an instance of the `Show` type class
Just 'a'
Prelude> print Nothing
Nothing
Recall that you can instantiate Show
for your own types using deriving
:
-- In ex.hs
data Person = Person { name :: String } deriving Show
main = print (Person "Alex") -- Person is an instance of `Show`, thanks to `deriving`
Loading & running in GHCi:
Prelude> :load ex.hs
[1 of 1] Compiling ex ( ex.hs, interpreted )
Ok, modules loaded: ex.
*Main> main -- from ex.hs
Person {name = "Alex"}
*Main>
# Parsing and constructing an object from standard input
readFloat :: IO Float
readFloat =
fmap read getLine
main :: IO ()
main = do
putStr "Type the first number: "
first <- readFloat
putStr "Type the second number: "
second <- readFloat
putStrLn $ show first ++ " + " ++ show second ++ " = " ++ show ( first + second )
Input:
Type the first number: 9.5
Type the second number: -2.02
Output:
9.5 + -2.02 = 7.48
# Reading from file handles
Like in several other parts of the I/O library, functions that implicitly use a standard stream have a counterpart in System.IO
that performs the same job, but with an extra parameter at the left, of type Handle
, that represents the stream being handled. For instance, getLine :: IO String
has a counterpart hGetLine :: Handle -> IO String
.
import System.IO( Handle, FilePath, IOMode( ReadMode ),
openFile, hGetLine, hPutStr, hClose, hIsEOF, stderr )
import Control.Monad( when )
dumpFile :: Handle -> FilePath -> Integer -> IO ()
dumpFile handle filename lineNumber = do -- show file contents line by line
end <- hIsEOF handle
when ( not end ) $ do
line <- hGetLine handle
putStrLn $ filename ++ ":" ++ show lineNumber ++ ": " ++ line
dumpFile handle filename $ lineNumber + 1
main :: IO ()
main = do
hPutStr stderr "Type a filename: "
filename <- getLine
handle <- openFile filename ReadMode
dumpFile handle filename 1
hClose handle
Contents of file example.txt
:
This is an example.
Hello, world!
This is another example.
Input:
Type a filename: example.txt
Output:
example.txt:1: This is an example.
example.txt:2: Hello, world!
example.txt:3: This is another example
# Reading from stdin
As-per the Haskell 2010 Language Specification (opens new window), the following are standard IO functions available in Prelude, so no imports are required to use them.
# getChar :: IO Char
- read a Char
from stdin
-- MyChar.hs
main = do
myChar <- getChar
print myChar
-- In your shell
runhaskell MyChar.hs
a -- you enter a and press enter
'a' -- the program prints 'a'
# getLine :: IO String
- read a String
from stdin
, sans new line character
Prelude> getLine
Hello there! -- user enters some text and presses enter
"Hello there!"
# read :: Read a => String -> a
- convert a String to a value
Prelude> read "1" :: Int
1
Prelude> read "1" :: Float
1.0
Prelude> read "True" :: Bool
True
Other, less common functions are:
getContents :: IO String
- returns all user input as a single string, which is read lazily as it is neededinteract :: (String -> String) -> IO ()
- takes a function of type String->String as its argument. The entire input from the standard input device is passed to this function as its argument