Helper Functions

Dear Computer

Chapter 7: Immutability and I/O

Helper Functions

We've get just seen getChar and getLine functions for gathering text from the user. Haskell doesn't have input getters for any other type. Wouldn't it be nice if it provided a getInt function so that we could simplify the main function to this code?

Haskell
main = do
  putStr "On a log scale from 1 to 10, how are you? "
  rating <- getInt
  putStrLn ("I'm sorry you are at " ++ show rating)

We don't need Haskell to provide getInt; we can write it ourselves. We want the function to return an Int, but the type signature must reflect that the Int is tainted by statefulness:

Haskell
getInt :: IO Int

The function takes no parameters, so only the return type appears in the signature.

The body of getInt is another do block. As before, it reads a line of input and then parses out an Int with the help of read. To return the Int to main, it must be packaged up in a box that marks it tainted with IO. This is the job of the return function:

Haskell
getInt :: IO Int
getInt = do
  intString <- getLine
  let int = read intString :: Int
  return int

In this case, return packages up an Int into an IO Int. In general, return packages up an a into an IO a. It is the inverse of <-, which unpacks a box tainted by IO.

The name return is misleading. In an imperative language, return is a statement that controls the flow of the program. In Haskell, return is just a function that yields a value in an IO box. It does not exit from the do block. The function could have been written like this:

Haskell
getInt :: IO Int
getInt = do
  intString <- getLine
  let int = read intString :: Int
  let taintedInt = return int
  taintedInt

Inside a do block, we move back and forth between pure and impure contexts. We use the <- operator to take a value out of its impure context, allowing us to operate on it in a pure context. We can't return untainted values from do blocks. If we need to return the value to the caller, we must wrap it back up in an IO box using the return function.

Function getInt returns an IO Int. What if we want to write a helper function that performs I/O but doesn't need to return anything? Then we use this type signature:

Haskell
helperFunction :: IO ()

The value () is called unit. As a type, it means something like void in other languages. As a value, it means something like null or nil.

We add parameters as needed:

Haskell
helperFunction :: String -> Int -> IO ()
← InputMaybe Data →