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 arepresents IO actions that return a value of typea.IO Stringis the type of the IO actiongetLine.IO ()represents IO actions that return an empty tuple (avoidvalue 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 >> action2is 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
mainprogram.
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
mainIO action and performing that action. - The
dosyntax allows all of this to be expressed in a familiar imperative programming style.