Haskell IO
Input/Output vs. Pure Functions
Haskell is a pure functional language.
- The only effect of a function call is to return a result value.
- There are no side effects.
- Global variables cannot be changed.
- Data structures cannot be modified.
But, IO naturally changes the state of the world.
- Contents of files are modified.
- File pointers are advanced.
- Different results are produced by each call to an IO action.
The Haskell IO Action Concept
In order to model IO using a functional approach, Haskell focuses on the idea of IO actions as values.
IO a
represents IO actions that return a value of typea
.IO String
is the type of the IO actiongetLine
.IO ()
represents IO actions that return an empty tuple (avoid
value in other languages).
Stdio Functions
For example, here are the type signatures of some standard Haskell IO functions.
putChar :: Char -> IO ()
Write a character to the standard output device (same as hPutChar stdout
).
putStr :: String -> IO ()
Write a string to the standard output device (same as hPutStr stdout
).
putStrLn :: String -> IO ()
The same as putStr
, but adds a newline character.
print :: Show a => a -> IO ()
Print to standard output a value of a showable type a
.
getChar :: IO Char
Read a character from the standard input device (same as hGetChar stdin
).
getLine :: IO String
Read a line from the standard input device (same as hGetLine stdin
).
Sequencing IO Actions
Haskell IO actions can be composed using the operators >>
and
>>=
.
(>>) :: IO a -> IO b -> IO b
(>>=) :: IO a -> (a -> IO b) -> IO b
Using the >>
operator takes two IO actions and returns a new IO action.
action1 >> action2
is an IO action that means:- first perform
action1
, ignoring the result - then perform
action2
, returning the result.
- first perform
How is >>=
different? The first result is not ignored; it is passed as an argument to the second action.
Executing IO actions
- IO actions are not executed during evaluation of Haskell function calls.
- Instead, Haskell executes only the IO action defined by the
main
program.
main :: IO ()
So, a Haskell Hello World
program could be written:
main = putStrLn "Hello" >> putStrLn "World"
Passing Results of IO Actions Along
The second operator >>=
(also called bind
) is more interesting.
(>>=) :: IO a -> (a -> IO b) -> IO b
This operator lets us take the result of an IO action and pass it into a function which produces another IO action.
Using >>=
we can build a simple interactive IO action.
main = putStrLn "Before we get started, what is your name?"
>> getLine
>>= \name -> putStrLn ("Hello, " ++ name ++ "!")
Why can't we write:
putStrLn ("Hello, " ++ getLine ++ "!")
Because the result type of getLine
is IO String
, not String
.
Do Notation
In order to simplify programming with the operators (>>
) and (>>=
), we can use do
notation instead.
For example, the interactive program above can be translated to use
do
syntax as follows.
main = do
putStrLn "Before we get started, what is your name?"
name <- getLine
putStrLn ("Hello, " ++ name ++ "!")
This use of do
syntax allows you to use an imperative programming style in a purely functional language!
Summary
- As a pure functional language, a Haskell function cannot have the side effect commonly associated with input-output operations.
- Instead, Haskell functions can produce IO actions as values.
- IO actions can be chained together with (
>>
) and (>>=
) to give composite IO actions. - Haskell programs are executed by defining one
main
IO action and performing that action. - The
do
syntax allows all of this to be expressed in a familiar imperative programming style.