Presentation on theme: "Chapter 16 Communicating With the Outside World. Motivation In Chapter 3 we learned how to do basic IO, but it was fairly limited. In this chapter."— Presentation transcript:
Chapter 16 Communicating With the Outside World
Motivation In Chapter 3 we learned how to do basic IO, but it was fairly limited. In this chapter we investigate advanced IO ideas: file handles, channels, exceptions, and concurrency. The ideas developed here will be used in the next chapter to write a renderer for FAL. The ideas will continue to be cast in terms of “IO actions” of type “IO a”, and will be sequenced using Haskell’s “do” syntax. The “mysteries” underlying the type “IO” and Haskell’s “do” syntax will be revealed in Chapter 18. (Guess what’s underneath it all? Yes, functions! )
File Handles Recall from Chapter 3 the IO actions: writeFile:: FilePath -> String -> IO () appendFile:: FilePath -> String -> IO () type FilePath = String These functions open a file, write a string, and then close the file. But if many successive writes are needed, the repeated openings and closings can be very inefficient. Better to open a file, and leave it open until done: openFile:: FilePath -> IOMode -> IO Handle hClose:: Handle -> IO () data IOMode = ReadMode | WriteMode | AppendMode | ReadWriteMode
File Operations Once open, many file operations are possible: hPutChar :: Handle -> Char -> IO () hPutStr :: Handle -> String -> IO () hPutStrLn :: Handle -> String -> IO () hPrint :: Show a => Handle -> a -> IO () hGetChar :: Handle -> IO Char hGetLine :: Handle -> IO String hGetContents :: Handle -> String when in “WriteMode” when in “ReadMode”
Standard Channels As in many OS’s, certain kinds of IO are treated like files. In Haskell these are called channels, and include standard input (stdin), standard output (stdout), and standard error (stderr). Indeed, these two functions are pre-defined in the Prelude: getChar :: IO Char getChar = hGetChar stdin putChar :: IO () putChar = hPutChar stdout
Exceptions There are many ways that an IO action might fail, and we need a way to detect and recover from such failures. In Haskell, IO failures are called exceptions, and they can be “caught” by: catch :: IO a -> (IOError -> IO a) -> IO a For example: getChar' :: IO Char getChar' = catch getChar (\e -> return '\n')
Dispatching on IOError IoError is an abstract type with predicates such as: isEOFError :: IOError -> Bool isDoesNotExistError :: IOError -> Bool and a “throw” operation: ioError :: IOError -> IO a A “more refined” getChar can then be defined: getChar' :: IO Char getChar' = catch getChar (\e -> if isEOFError e then return '\n‘ else ioError e ) Note how ioError “throws” the error to an outer dynamic scope. (See text for example of “nested” exceptions.)
Putting It All Together (copying a file) getAndOpenFile :: String -> IOMode -> IO Handle getAndOpenFile prompt mode = do putStr prompt name do putStrLn ("Cannot open " ++ name) print error getAndOpenFile prompt mode ) Main :: IO () main = do fromHandle <- getAndOpenFile "Copy from: " ReadMode toHandle <- getAndOpenFile "Copy to: " WriteMode contents <- hGetContents fromHandle hPutStr toHandle contents hClose fromHandle hClose toHandle putStrLn "Done."
First-Class Channels “First-class channels” are different from the “standard channels” described earlier. They are a mechanism through which Haskell “processes” can communicate asynchronously. A first-class channel containing values of type “a” has type “Chan a”. The key operations are: newChan:: IO (Chan a) writeChan:: Chan a -> a -> IO () readChan:: Chan a -> IO a getChanContents:: Chan a -> IO [a] isEmptyChan:: Chan a -> IO Bool
Example A first-class channel implements an unbounded queue that can be written to and read from asynchronously. For example: do c <- newChan writeChan c `a` writeChan c `b`... do... a <- readChan c b <- readChan c return [a,b] returns the string "ab".
Concurrency Concurrent processes may be forked off from the main computation by forkIO :: IO () -> IO () The IO action: forkIO p will cause p to run concurrently with the main program. For example, we can create a concurrent version of the client-server program from Chapter 14. [ see next slide ]
Concurrent Client-Server client :: Chan Int -> Chan Int -> IO () client cin cout = do writeChan cout 1 loop where loop = do c <- readChan cin print c writeChan cout c loop server :: Chan Int -> Chan Int -> IO () server cin cout = do loop where loop = do c <- readChan cin writeChan cout (c+1) loop main :: IO () main = do c1 <- newChan :: IO (Chan Int) c2 <- newChan :: IO (Chan Int) forkIO (client c1 c2) forkIO (server c2 c1)