Not logged in. Login

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 type a.
  • IO String is the type of the IO action getLine.
  • IO () represents IO actions that return an empty tuple (a void 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.

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.
Updated Thu Oct. 04 2018, 06:34 by cameron.