Input
Programs also need a way to gather input. Haskell provides the getLine
and getChar
functions to read text from standard input. Consider their type signatures:
> :t getChar
getChar :: IO Char
> :t getLine
getLine :: IO String
When we call them, we don't get back a plain Char
or a plain String
. We get back IO Char
and IO String
. To retrieve the input from the user, these functions had to pass into the land of impurity—where new state is made. Any value we bring back from this land is packaged up in a box marked IO
, which means the value is “tainted” by state. We can't perform any operations on values inside IO
boxes.
Suppose we have this function that returns a tainted 1 as an IO Int
:
getTaintedOne :: IO Int
getTaintedOne = do
return 1
If we load this function in ghci and try to use it in an expression, the code fails to typecheck:
> getTaintedOne + 2
<interactive>:3:15: error:
• No instance for (Num (IO Int)) arising from a use of ‘+’
• In the expression: getTaintedOne + 2
In an equation for ‘it’: it = getTaintedOne + 2
To work with a value tainted by IO
, we must first open up the box, which we can only do in the land of impurity—in another do
block. The binding operator <-
opens the box and extracts the pure value within. This operator is a bit like an assignment operator, but it turns an IO Char
into a normal Char
or an IO Int
into a normal Int
. In this program, it is used to collect a string from the user:
main = do
putStr "On a log scale from 1 to 10, how are you? "
ratingString <- getLine
putStrLn ("I'm sorry you are at " ++ ratingString)
Here the <-
operator unpacks an IO String
into a String
. If we want the rating as an Int
, we must unpack it to a String
first and then use the read
function to convert the String
to an Int
. Outside a do
block, in a pure context, we would write this conversion expression:
read ratingString :: Int
To assign the value of this expression to a variable, we don't use <-
, which is strictly for opening boxes tainted by state and not a general assignment operator. To do a normal assignment, we could use a where
clause. However, a let
statement looks more like the declarations we've seen in imperative languages:
main = do
putStr "On a log scale from 1 to 10, how are you? "
ratingString <- getLine
let rating = read ratingString :: Int
putStrLn ("I'm at " ++ show (1 + rating))
Haskell do
blocks contain a mix of unboxing statements that use the <-
operator and the pure assignment statements that use the =
operator. Be careful! It's easy to mix them up.