home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Tools / Languages / MacHaskell 2.2 / progs / tutorial / temp.lhs < prev    next >
Encoding:
Text File  |  1994-09-28  |  4.7 KB  |  128 lines  |  [TEXT/CCL2]

  1.  
  2. Page: 22  Section 8
  3.  
  4. > module Test where
  5.  
  6. > import Prelude hiding (putStr, getLine)
  7.  
  8. Section: 8.1  The I/O Monad
  9.  
  10. The I/O monad divides the Haskell world into values and actions.  So far,
  11. we have only needed to look at values.  To deal with actions, we need
  12. to introduce a new editor command: C-c r.  We use the term `dialogue'
  13. to denote an action returning no value, as indicated by the type `IO ()'.
  14. Instead of printing a value, C-c r runs a dialogue.  (Actually C-c e creates
  15. an action on the fly and runs it in the same manner as C-c r).  
  16. As with C-c e you are prompted for an expression; here the type of the
  17. expression must be IO ().  We use d1,d2,... for dialogues to be
  18. executed by C-c r.
  19.  
  20. In an interactive environment such as this, there is no real need to
  21. use `main' in module `Main' to designate the dialogue associated with a
  22. program since C-c r queries for the dialogue to be executed.
  23. However, many commands such as C-c m make use of this convention.
  24.  
  25. The first example is putStr:
  26.  
  27. > putStr    :: String -> IO ()
  28. > putStr s = foldr (>>) (return ()) (map putChar s)
  29.  
  30. > d1 = putStr "Hello World"
  31.  
  32. Both putStr and getLine are actually in the prelude.
  33.  
  34. > getLine :: IO String
  35. > getLine = getChar >>= (\c ->
  36. >           if c == '\n' then return ""
  37. >                        else getLine >>= (\l -> return (c:l)))
  38.  
  39. > d2 = putStr "Type Something: " >> getLine >>= (\str ->
  40. >      putStr "You typed: " >> putStr str >> putStr "\n")
  41.  
  42. To experiment with I/O errors, we need to get a bit creative.  Generating
  43. an error is generally OS specific so instead we use a new
  44. version of getLine that raises an error when a blank line is entered:
  45.  
  46. > getLineErr = getLine >>= (\l ->
  47. >              if l == "" then failwith EOF
  48. >                         else return l)
  49.  
  50. First, enter a blank line with no active error handler: 
  51.  
  52. > d3 = getLineErr >>= putStr
  53.  
  54. Now with an error handler:
  55.  
  56. > d4 = try getLineErr (\ e -> return (show e)) >>= putStr
  57.  
  58. This is the file copier:
  59.  
  60. > d5 = getAndOpenFile "Copy from: " ReadMode >>= (\fromHandle ->
  61. >      getAndOpenFile "Copy to: " WriteMode >>= (\toHandle ->
  62. >      getContents fromHandle >>= (\contents ->
  63. >      hPutStr toHandle contents >> close toHandle >> putStr "Done.\n")))
  64.  
  65. > getAndOpenFile :: String -> IOMode -> IO Handle
  66.  
  67. > getAndOpenFile prompt mode =
  68. >     putStr prompt >>
  69. >     getLine >>= (\name ->
  70. >     try (openFile mode name)
  71. >         (\err -> putStr ("Cannot open "++ name ++ "\n") >>
  72. >                  getAndOpenFile prompt mode))
  73.  
  74. Finally, an example not in the tutorial.  Reading stdin using getContents
  75. makes the demands for evaluation of the input stream visible to the user
  76. since the program will stop for input whenever demand reaches a new line in
  77. the input stream.
  78.  
  79. This reads the entire input stream, breaks it into lines, and hands a list
  80. of lines to munchInput:
  81.  
  82. > d6 = getContents stdin >>= \i -> munchInput (lines i)
  83.  
  84. The munchInput function looks at the next line of input.  It prints a prompt
  85. and then looks at a line of input.  The command `stop' halts execution,
  86. the command `skip' skips over the next input line, and anything else is
  87. echoed.
  88.  
  89. > munchInput (l:ls) =
  90. >   putStr "* " >>
  91. >   case l of
  92. >    "stop" -> return ()
  93. >    "skip" -> munchInput (tail ls)
  94. >    _      -> putStr (l ++ " not understood.\n") >> munchInput ls
  95.  
  96. Run this program.  There is a problem with the prompting: it reads the
  97. input line before the prompt is printed out.  While the case statement would
  98. be expected to demand a line of input, this demand actually occurs earlier.
  99. The pattern (l:ls) can only be matched by stripping a line of the input
  100. stream.  Since this pattern is matched upon entry to munchInput, the input
  101. demand occurs before the prompt can be printed.
  102.  
  103. Change the first line of munchInput to:
  104.  
  105. munchInput ~(l:ls) =
  106.  
  107. This will allow the prompt to print out before the case statement looks at
  108. the value of l.  Run the program again.
  109.  
  110. Note the behavior when you type "skip".  Again, you need to understand when
  111. the input will be demanded to figure out why the prompt prints before the
  112. ignored input line.  Now change this by putting a something in the
  113. recursive call to munchInput which will look at the line being discarded
  114. before the recursive call:
  115.  
  116.   "skip" -> case ls of (_ : t) -> munchInput t
  117.  
  118. This reads the skipped line before the prompt on the recursive call.
  119.  
  120. If you find all of this confusing, then the lesson here is that it is probably
  121. best to avoid reading stdin with getContents.  Using getLine to read a line
  122. at a time is much easier to understand.  Since getLine is strict with respect
  123. to the input, there is no way that later demands will suddenly stop execution
  124. to wait for input.
  125.  
  126. For more examples using the I/O system look in the demo programs
  127. that come with haskell (in $HASKELL/progs/demo) and the report.
  128.