Online Book Reader

Home Category

Learn You a Haskell for Great Good! - Miran Lipovaca [75]

By Root 436 0
it back in all uppercase:

import Control.Monad

import Data.Char

main = forever $ do

l <- getLine

putStrLn $ map toUpper l

Save this program as capslocker.hs and compile it.

Instead of inputting lines via the keyboard, we’ll have haiku.txt be the input by redirecting it into our program. To do that, we add a < character after our program name and then specify the file that we want to act as the input. Check it out:

$ ghc --make capslocker

[1 of 1] Compiling Main ( capslocker.hs, capslocker.o )

Linking capslocker ...

$ ./capslocker < haiku.txt

I'M A LIL' TEAPOT

WHAT'S WITH THAT AIRPLANE FOOD, HUH?

IT'S SO SMALL, TASTELESS

capslocker : hGetLine: end of file

What we’ve done is pretty much equivalent to running capslocker, typing our haiku at the terminal, and then issuing an end-of-file character (usually done by pressing ctrl-D). It’s like running capslocker and saying, “Wait, don’t read from the keyboard. Take the contents of this file instead!”

Getting Strings from Input Streams


Let’s take a look at an I/O action that makes processing input streams easier by allowing us to treat them as normal strings: getContents. getContents reads everything from the standard input until it encounters an end-of-file character. Its type is getContents :: IO String. What’s cool about getContents is that it does lazy I/O. This means that when we do foo <- getContents, getContents doesn’t read all of the input at once, store it in memory, and then bind it to foo. No, getContents is lazy! It will say, “Yeah yeah, I’ll read the input from the terminal later as we go along, when you really need it!”

In our capslocker.hs example, we used forever to read the input line by line and then print it back in uppercase. If we opt to use getContents, it takes care of the I/O details for us, such as when to read input and how much of that input to read. Because our program is about taking some input and transforming it into some output, we can make it shorter by using getContents:

import Data.Char

main = do

contents <- getContents

putStr $ map toUpper contents

We run the getContents I/O action and name the string it produces contents. Then we map toUpper over that string and print that result to the terminal. Keep in mind that because strings are basically lists, which are lazy, and getContents is I/O lazy; it won’t try to read all of the content at once and store that into memory before printing out the caps-locked version. Rather, it will print out the caps-locked version as it reads, because it will read a line from the input only when it must.

Let’s test it:

$ ./capslocker < haiku.txt

I'M A LIL' TEAPOT

WHAT'S WITH THAT AIRPLANE FOOD, HUH?

IT'S SO SMALL, TASTELESS

So, it works. What if we just run capslocker and try to type in the lines ourselves? (To exit the program, just press ctrl-D.)

$ ./capslocker

hey ho

HEY HO

lets go

LETS GO

Pretty nice! As you can see, it prints our caps-locked input line by line.

When the result of getContents is bound to contents, it’s not represented in memory as a real string, but more like a promise that the string will be produced eventually. When we map toUpper over contents, that’s also a promise to map that function over the eventual contents. Finally, when putStr happens, it says to the previous promise, “Hey, I need a caps-locked line!” It doesn’t have any lines yet, so it says to contents, “How about getting a line from the terminal?” And that’s when getContents actually reads from the terminal and gives a line to the code that asked it to produce something tangible. That code then maps toUpper over that line and gives it to putStr, which prints the line. And then putStr says, “Hey, I need the next line—come on!” This repeats until there’s no more input, which is signified by an end-of-file character.

Now let’s make a program that takes some input and prints out only those lines that are shorter than 10 characters:

main = do

contents <- getContents

putStr (shortLinesOnly contents)

shortLinesOnly :: String -> String

shortLinesOnly = unlines . filter (\line

Return Main Page Previous Page Next Page

®Online Book Reader