120 likes | 138 Views
Explore advanced IO concepts in Haskell including file handles, channels, exceptions, and concurrency with practical examples. Learn to use first-class channels for asynchronous communication and implement concurrent processes.
E N D
Chapter 16 Communicating Withthe 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 CharhGetLine :: Handle -> IO StringhGetContents :: 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 -> Booland 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 <- getLine catch (do handle <- openFile name mode return handle ) (\error -> 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 pwill 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 ]
client :: Chan Int -> Chan Int -> IO () client cin cout =do writeChan cout 1 loopwhere loop = do c <- readChan cin print c writeChan cout c loop server :: Chan Int -> Chan Int -> IO () server cin cout =do loopwhere loop = do c <- readChan cin writeChan cout (c+1) loop Concurrent Client-Server main :: IO () main = do c1 <- newChan :: IO (Chan Int) c2 <- newChan :: IO (Chan Int) forkIO (client c1 c2) forkIO (server c2 c1)