Learn You a Haskell for Great Good! - Miran Lipovaca [70]
When we do name <- getLine, name is just a normal string, because it represents what’s inside the box. For example, we can have a really complicated function that takes your name (a normal string) as a parameter and tells you your fortune based on your name, like this:
main = do
putStrLn "Hello, what's your name?"
name <- getLine
putStrLn $ "Zis is your future: " ++ tellFortune name
The tellFortune function (or any of the functions it passes name to) does not need to know anything about I/O—it’s just a normal String -> String function!
To see how normal values differ from I/O actions, consider the following line. Is it valid?
nameTag = "Hello, my name is " ++ getLine
If you said no, go eat a cookie. If you said yes, drink a bowl of molten lava. (Just kidding—don’t!) This doesn’t work because ++ requires both its parameters to be lists over the same type. The left parameter has a type of String (or [Char], if you will), while getLine has a type of IO String. Remember that you can’t concatenate a string and an I/O action. First, you need to get the result out of the I/O action to get a value of type String, and the only way to do that is to do something like name <- getLine inside some other I/O action.
If we want to deal with impure data, we must do it in an impure environment. The taint of impurity spreads around much like the undead scourge, and it’s in our best interest to keep the I/O parts of our code as small as possible.
Every I/O action that is performed yields a result. That’s why our previous example could also have been written like this:
main = do
foo <- putStrLn "Hello, what's your name?"
name <- getLine
putStrLn ("Hey " ++ name ++ ", you rock!")
However, foo would just have a value of (), so doing that would be kind of moot. Notice that we didn’t bind the last putStrLn to anything. That’s because in a do block, the last action cannot be bound to a name as the first two were. You’ll see exactly why that is so when we venture off into the world of monads, starting in Chapter 13. For now, the important point is that the do block automatically extracts the value from the last action and yields that as its own result.
Except for the last line, every line in a do block that doesn’t bind can also be written with a bind. So putStrLn "BLAH" can be written as _ <- putStrLn "BLAH". But that’s useless, so we leave out the <- for I/O actions that don’t yield an important result, like putStrLn.
What do you think will happen when we do something like the following?
myLine = getLine
Do you think it will read from the input and then bind the value of that to name? Well, it won’t. All this does is give the getLine I/O action a different name called myLine. Remember that to get the value out of an I/O action, you must perform it inside another I/O action by binding it to a name with <-.
I/O actions will be performed when they are given a name of main or when they’re inside a bigger I/O action that we composed with a do block. We can also use a do block to glue together a few I/O actions, and then we can use that I/O action in another do block, and so on. They will be performed if they eventually fall into main.
There’s also one more case when I/O actions will be performed: when we type out an I/O action in GHCi and press enter.
ghci> putStrLn "HEEY"
HEEY
Even when we just punch in a number or call a function in GHCi and press enter, GHCi will apply show to the resulting value, and then it will print it to the terminal by using putStrLn.
Using let Inside I/O Actions
When using do syntax to glue together I/O actions, we can use let syntax to bind pure values to names. Whereas <- is used to perform I/O actions and bind their results