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?
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:
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:
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:
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:
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:
helperFunction :: String -> Int -> IO ()