We can write a simple program that allows a user to read and write files. The interface is admittedly poor, and it does not catch all errors (try reading a non-existant file). Nevertheless, it should give a fairly complete example of how to use IO. Enter the following code into “FileRead.hs,” and compile/run:
module Main where import IO main = do
hSetBuffering stdin LineBuffering doLoop
doLoop = do
putStrLn "Enter a command rFN wFN or q to quit:" command <- getLine
case command of ’q’:_ -> return ()
’r’:filename -> do putStrLn ("Reading " ++ filename) doRead filename
doLoop
’w’:filename -> do putStrLn ("Writing " ++ filename) doWrite filename
doLoop _ -> doLoop
5.4. A FILE READING PROGRAM 65
doRead filename =
bracket (openFile filename ReadMode) hClose (\h -> do contents <- hGetContents h
putStrLn "The first 100 chars:" putStrLn (take 100 contents)) doWrite filename = do
putStrLn "Enter text to go into the file:" contents <- getLine
bracket (openFile filename WriteMode) hClose (\h -> hPutStrLn h contents)
What does this program do? First, it issues a short string of instructions and reads a command. It then performs a case switch on the command and checks first to see if the first character is a ‘q.’ If it is, it returns a value of unit type.
NOTE Thereturnfunction is a function that takes a value of type aand returns an action of typeIO a. Thus, the type ofreturn ()is IO().
If the first character of the command wasn’t a ‘q,’ the program checks to see if it was an ’r’ followed by some string that is bound to the variablefilename. It then tells you that it’s reading the file, does the read and runsdoLoopagain. The check for ‘w’ is nearly identical. Otherwise, it matches , the wildcard character, and loops todoLoop.
ThedoReadfunction uses thebracketfunction to make sure there are no prob- lems reading the file. It opens a file inReadMode, reads its contents and prints the first 100 characters (thetakefunction takes an integernand a list and returns the firstn elements of the list).
ThedoWritefunction asks for some text, reads it from the keyboard, and then writes it to the file specified.
NOTE BothdoReadanddoWritecould have been made simpler by usingreadFileandwriteFile, but they were written in the ex- tended fashion to show how the more complex functions are used.
The only major problem with this program is that it will die if you try to read a file that doesn’t already exists or if you specify some bad filename like*\ˆ# @. You may think that the calls tobracketindoReadanddoWriteshould take care of this, but they don’t. They only catch exceptions within the main body, not within the startup or shutdown functions (openFileandhClose, in these cases). We would need to catch exceptions raised byopenFile, in order to make this complete. We will do this when we talk about exceptions in more detail in Section 10.1.
Exercises
Exercise 5.2 Write a program that first asks whether the user wants to read from a file, write to a file or quit. If the user responds quit, the program should exit. If he responds read, the program should ask him for a file name and print that file to the screen (if the file doesn’t exist, the program may crash). If he responds write, it should ask him for a file name and then ask him for text to write to the file, with “.” signaling completion. All but the “.” should be written to the file.
For example, running this program might produce:
Do you want to [read] a file, [write] a file or [quit]? read
Enter a file name to read: foo
...contents of foo...
Do you want to [read] a file, [write] a file or [quit]? write
Enter a file name to write: foo
Enter text (dot on a line by itself to end): this is some
text for foo .
Do you want to [read] a file, [write] a file or [quit]? read
Enter a file name to read: foo
this is some text for foo
Do you want to [read] a file, [write] a file or [quit]? read
Enter a file name to read: foof
Sorry, that file does not exist.
Do you want to [read] a file, [write] a file or [quit]? blech
I don’t understand the command blech.
Do you want to [read] a file, [write] a file or [quit]? quit
Chapter 6
Modules
In Haskell, program subcomponents are divided into modules. Each module sits in its own file and the name of the module should match the name of the file (without the “.hs” extension, of course), if you wish to ever use that module in a larger program.
For instance, suppose I am writing a game of poker. I may wish to have a separate module called “Cards” to handle the generation of cards, the shuffling and the dealing functions, and then use this “Cards” module in my “Poker” modules. That way, if I ever go back and want to write a blackjack program, I don’t have to rewrite all the code for the cards; I can simply import the old “Cards” module.
6.1
Exports
Suppose as suggested we are writing a cards module. I have left out the implementation details, but suppose the skeleton of our module looks something like this:
module Cards where data Card = ... data Deck = ... newDeck :: ... -> Deck newDeck = ...
shuffle :: ... -> Deck -> Deck shuffle = ...
-- ’deal deck n’ deals ’n’ cards from ’deck’ deal :: Deck -> Int -> [Card]
deal deck n = dealHelper deck n []
dealHelper = ...
In this code, the function dealcalls a helper functiondealHelper. The im- plementation of this helper function is very dependent on the exact data structures you used forCardandDeckso we don’t want other people to be able to call this function. In order to do this, we create an export list, which we insert just after the module name declaration:
module Cards ( Card(), Deck(), newDeck, shuffle, deal ) where ...
Here, we have specified exactly what functions the module exports, so people who use this module won’t be able to access our dealHelperfunction. The() after
CardandDeckspecify that we are exporting the type but none of the constructors. For instance if our definition ofCardwere:
data Card = Card Suit Face data Suit = Hearts
| Spades | Diamonds | Clubs data Face = Jack
| Queen | King | Ace
| Number Int
Then users of our module would be able to use things of typeCard, but wouldn’t be able to construct their ownCards and wouldn’t be able to extract any of the suit/face information stored in them.
If we wanted users of our module to be able to access all of this information, we would have to specify it in the export list:
module Cards ( Card(Card),
Suit(Hearts,Spades,Diamonds,Clubs), Face(Jack,Queen,King,Ace,Number), ...