home *** CD-ROM | disk | FTP | other *** search
Text File | 1994-09-28 | 4.7 KB | 128 lines | [TEXT/CCL2] |
-
- Page: 22 Section 8
-
- > module Test where
-
- > import Prelude hiding (putStr, getLine)
-
- Section: 8.1 The I/O Monad
-
- The I/O monad divides the Haskell world into values and actions. So far,
- we have only needed to look at values. To deal with actions, we need
- to introduce a new editor command: C-c r. We use the term `dialogue'
- to denote an action returning no value, as indicated by the type `IO ()'.
- Instead of printing a value, C-c r runs a dialogue. (Actually C-c e creates
- an action on the fly and runs it in the same manner as C-c r).
- As with C-c e you are prompted for an expression; here the type of the
- expression must be IO (). We use d1,d2,... for dialogues to be
- executed by C-c r.
-
- In an interactive environment such as this, there is no real need to
- use `main' in module `Main' to designate the dialogue associated with a
- program since C-c r queries for the dialogue to be executed.
- However, many commands such as C-c m make use of this convention.
-
- The first example is putStr:
-
- > putStr :: String -> IO ()
- > putStr s = foldr (>>) (return ()) (map putChar s)
-
- > d1 = putStr "Hello World"
-
- Both putStr and getLine are actually in the prelude.
-
- > getLine :: IO String
- > getLine = getChar >>= (\c ->
- > if c == '\n' then return ""
- > else getLine >>= (\l -> return (c:l)))
-
- > d2 = putStr "Type Something: " >> getLine >>= (\str ->
- > putStr "You typed: " >> putStr str >> putStr "\n")
-
- To experiment with I/O errors, we need to get a bit creative. Generating
- an error is generally OS specific so instead we use a new
- version of getLine that raises an error when a blank line is entered:
-
- > getLineErr = getLine >>= (\l ->
- > if l == "" then failwith EOF
- > else return l)
-
- First, enter a blank line with no active error handler:
-
- > d3 = getLineErr >>= putStr
-
- Now with an error handler:
-
- > d4 = try getLineErr (\ e -> return (show e)) >>= putStr
-
- This is the file copier:
-
- > d5 = getAndOpenFile "Copy from: " ReadMode >>= (\fromHandle ->
- > getAndOpenFile "Copy to: " WriteMode >>= (\toHandle ->
- > getContents fromHandle >>= (\contents ->
- > hPutStr toHandle contents >> close toHandle >> putStr "Done.\n")))
-
- > getAndOpenFile :: String -> IOMode -> IO Handle
-
- > getAndOpenFile prompt mode =
- > putStr prompt >>
- > getLine >>= (\name ->
- > try (openFile mode name)
- > (\err -> putStr ("Cannot open "++ name ++ "\n") >>
- > getAndOpenFile prompt mode))
-
- Finally, an example not in the tutorial. Reading stdin using getContents
- makes the demands for evaluation of the input stream visible to the user
- since the program will stop for input whenever demand reaches a new line in
- the input stream.
-
- This reads the entire input stream, breaks it into lines, and hands a list
- of lines to munchInput:
-
- > d6 = getContents stdin >>= \i -> munchInput (lines i)
-
- The munchInput function looks at the next line of input. It prints a prompt
- and then looks at a line of input. The command `stop' halts execution,
- the command `skip' skips over the next input line, and anything else is
- echoed.
-
- > munchInput (l:ls) =
- > putStr "* " >>
- > case l of
- > "stop" -> return ()
- > "skip" -> munchInput (tail ls)
- > _ -> putStr (l ++ " not understood.\n") >> munchInput ls
-
- Run this program. There is a problem with the prompting: it reads the
- input line before the prompt is printed out. While the case statement would
- be expected to demand a line of input, this demand actually occurs earlier.
- The pattern (l:ls) can only be matched by stripping a line of the input
- stream. Since this pattern is matched upon entry to munchInput, the input
- demand occurs before the prompt can be printed.
-
- Change the first line of munchInput to:
-
- munchInput ~(l:ls) =
-
- This will allow the prompt to print out before the case statement looks at
- the value of l. Run the program again.
-
- Note the behavior when you type "skip". Again, you need to understand when
- the input will be demanded to figure out why the prompt prints before the
- ignored input line. Now change this by putting a something in the
- recursive call to munchInput which will look at the line being discarded
- before the recursive call:
-
- "skip" -> case ls of (_ : t) -> munchInput t
-
- This reads the skipped line before the prompt on the recursive call.
-
- If you find all of this confusing, then the lesson here is that it is probably
- best to avoid reading stdin with getContents. Using getLine to read a line
- at a time is much easier to understand. Since getLine is strict with respect
- to the input, there is no way that later demands will suddenly stop execution
- to wait for input.
-
- For more examples using the I/O system look in the demo programs
- that come with haskell (in $HASKELL/progs/demo) and the report.
-