Learn You a Haskell for Great Good! - Miran Lipovaca [74]
import Control.Monad
main = do
colors <- forM [1,2,3,4] (\a -> do
putStrLn $ "Which color do you associate with the number "
++ show a ++ "?"
color <- getLine
return color)
putStrLn "The colors that you associate with 1, 2, 3 and 4 are: "
mapM putStrLn colors
Here’s what we get when we try this out:
Which color do you associate with the number 1?
white
Which color do you associate with the number 2?
blue
Which color do you associate with the number 3?
red
Which color do you associate with the number 4?
orange
The colors that you associate with 1, 2, 3 and 4 are:
white
blue
red
orange
The (\a -> do ... ) lambda is a function that takes a number and returns an I/O action. Notice that we call return color in the inside do block. We do that so that the I/O action that the do block defines yields the string that represents our color of choice. We actually did not have to do that though, since getLine already yields our chosen color, and it’s the last line in the do block. Doing color <- getLine and then return color is just unpacking the result from getLine and then repacking it—it’s the same as just calling getLine.
The forM function (called with its two parameters) produces an I/O action, whose result we bind to colors. colors is just a normal list that holds strings. At the end, we print out all those colors by calling mapM putStrLn colors.
You can think of forM as saying, “Make an I/O action for every element in this list. What each I/O action will do can depend on the element that was used to make the action. Finally, perform those actions and bind their results to something.” (Although we don’t need to bind it; we could also just throw it away.)
We could have actually achieve the same result without forM, but using forM makes the code more readable. Normally, we use forM when we want to map and sequence some actions that we define on the spot using do notation.
I/O Action Review
Let’s run through a quick review of the I/O basics. I/O actions are values much like any other value in Haskell. We can pass them as parameters to functions, and functions can return I/O actions as results.
What’s special about I/O actions is that if they fall into the main function (or are the result in a GHCi line), they are performed. And that’s when they get to write stuff on your screen or play “Yakety Sax” through your speakers. Each I/O action can also yield a result to tell you what it got from the real world.
Chapter 9. More Input and More Output
Now that you understand the concepts behind Haskell’s I/O, we can start doing fun stuff with it. In this chapter, we’ll interact with files, make random numbers, deal with command-line arguments, and more. Stay tuned!
Files and Streams
Armed with the knowledge about how I/O actions work, we can move on to reading and writing files with Haskell. But first, let’s take a look at how we can use Haskell to easily process streams of data. A stream is a succession of pieces of data entering or exiting a program over time. For instance, when you’re inputting characters into a program via the keyboard, those characters can be thought of as a stream.
Input Redirection
Many interactive programs get the user’s input via the keyboard. However, it’s often more convenient to get the input by feeding the contents of a text file to the program. To achieve this, we use input redirection.
Input redirection will come in handy with our Haskell programs, so let’s take a look at how it works. To begin, create a text file that contains the following little haiku, and save it as haiku.txt:
I'm a lil' teapot
What's with that airplane food, huh?
It's so small, tasteless
Yeah, the haiku sucks—what of it? If anyone knows of any good haiku tutorials, let me know.
Now we’ll write a little program that continuously gets a line from the input and then prints