Input
Just as programs scatter output, so must they 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
> :t getChar getChar :: IO Char > :t getLine getLine :: IO String
They don't look like functions since there's no ->
. But they are functions. They just don't have any parameters. The only way they can return different values on every call is because they rely on the implicit environment passed to them.
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. It's not just these two functions that are tainted. All input and output functions return tainted values.
We can't directly perform any operations on tainted values. Let's try. Suppose we have this function that returns a tainted 1 as an IO Int
:
getTaintedOne :: IO Int
getTaintedOne = do
return 1
getTaintedOne :: IO Int getTaintedOne = do return 1
If we load this function in GHCi and try to use the value it returns 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
> 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. We can only do this in the land of impurity—in another do
block. The command <-
opens the box, extracts the pure value within, and binds a name to it. It's 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)
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 <-
command unboxes an IO String
from getLine
into a String
.
Suppose we want the rating as an Int
to do math on it. We must unbox it to a String
first and then use the read
function to convert the String
to an Int
. Outside of a do
block, in a pure context, we would write this conversion expression:
read ratingString :: Int
read ratingString :: Int
To do a pure assignment inside of a do block, we don't use <-
. Instead we use a let
statement that looks just like a declaration we might see in an imperative language:
main = do
putStr "On a log scale from 1 to 10, how are you? "
ratingString <- getLine
putStrLn ("I'm sorry you are at " ++ ratingString)
let rating = read ratingString :: Int
putStrLn ("I'm at " ++ show (1 + rating))
main = do putStr "On a log scale from 1 to 10, how are you? " ratingString <- getLine putStrLn ("I'm sorry you are at " ++ ratingString) let rating = read ratingString :: Int putStrLn ("I'm at " ++ show (1 + rating))
Haskell do
blocks contain a mix of unboxing statements that use the <-
command and the pure assignment statements that use the =
operator. Be careful! It's easy to mix them up.