210 likes | 307 Views
Lecture #16, Nov. 29, 2004. Todays Topics Files, Channels, and Handles IO exception handling First Class Channels Concurrency and ForkIO Reading Read Chapter 16 – Communicating with the Outside World Read Chapter 17 – Rendering Reactive Animations Assignment
E N D
Lecture #16, Nov. 29, 2004 • Todays Topics • Files, Channels, and Handles • IO exception handling • First Class Channels • Concurrency and ForkIO • Reading • Read Chapter 16 – Communicating with the Outside World • Read Chapter 17 – Rendering Reactive Animations • Assignment • Last homework assigned Wednesday. See webpage. Due Wednesday Dec. 8, 2004. • Final Exam • scheduled for Wednesday Dec. 8, 2004
Files and Handles • The functions: writeFile :: FilePath -> String -> IO () appendFile :: FilePath -> String -> IO () are used to read and write to files, but they incur quite a bit of overhead if they are used many times in a row. Instead we wish to open a file once, then make many actions on the file before we close it for a final time. openFile :: FilePath -> IOMode -> IO Handle hClose :: Handle -> IO () data IOMode = ReadMode | WriteMode | AppendMode deriving (Eq, Ord, Ix, Bounded, Enum, Read, Show)
File Modes • A file mode tells how an open file will be used. Different modes support different operations. • When in WriteMode hPutChar :: Handle -> Char -> IO () hPutStr :: Handle -> String -> IO () hPutStrLn :: Handle -> String -> IO () hPrint :: Show a => Handle -> a -> IO () • When in ReadMode hGetChar :: Handle -> IO Char hGetLine :: Handle -> IO String
Standard Channels and Errors • Predefined standard Channels stdin, stdout, stderr :: Handle • Error Handling while doing IO isEOFError :: IOError -> Bool -- Test if the EOF error ioError :: IOError -> IO a -- Raise an IOError catch :: IO a -> (IOError -> IO a) -> IO a -- Handle an Error • Other IO types of errors and their predicates. • isAlreadyExistsError, isDoesNotExistError, • isAlreadyInUseError, isFullError, • isEOFError, isIllegalOperation, • isPermissionError, isUserError,
IOError • IOError is an abstract datatype • NOT and algebraic datatype, defined with data like [ ] or Tree • Thus it does not admit pattern matching. • Hence the use of all the IOError recognizing predicates. • isAlreadyExistsError, isDoesNotExistError, • isAlreadyInUseError, isFullError, • isEOFError, isIllegalOperation, • isPermissionError, isUserError • This was a concious decision, made to allow easy extension of the kinds of IOErrors, as the system grew.
Handling IO Errors • Any action of type IO a may potentially cause an IO Error. • The function • catch ::IO a -> (IOError -> IO a) -> IO a can be used to gracefully handle such an error by providing a “fix” getChar' :: IO Char getChar' = catch getChar (\ e -> return '\n') getChar2 :: IO Char getChar2 = catch getChar (\ e -> if isEOFError e then return '\n' else ioError e) –- pass non EOF errors on
An Example getLine' :: IO String getLine' = catch getLine'' (\ e -> return ("Error: " ++ show e)) where getLine'' = do { c <- getChar2 ; if c == '\n' then return "" else do { l <- getLine' ; return (c:l) } }
Catching errors when opening files getAndOpenFile :: String -> IOMode -> IO Handle getAndOpenFile prompt mode = do { putStr prompt ; name <- getLine ; catch (openFile name mode) (\e -> do { putStrLn ("Cannot open: "++name) ; print e ; getAndOpenFile prompt mode }) }
Copying Files main = do { fromHandle <- getAndOpenFile "Copy from: " ReadMode ; toHandle <- getAndOpenFile "Copy to: " WriteMode ; contents <- hGetContents fromHandle ; hPutStr toHandle contents ; hClose fromHandle ; hClose toHandle ; putStr "Done\n" }
First Class Channels • A Channel is a special kind of abstraction, in the multiprocessing (or concurrency) paradigm. • If you “pull” on the tail of a channel, and it is null, then you “wait” until something becomes available. • First Class Channel Operations 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 ex1 = do { c <- newChan ; writeChan c 'a' ; writeChan c 'b' ; a <- readChan c ; b <- readChan c ; print [a,b] ; return [a,b] } It is easy to cause deadlock Using Channels just flipping the order of the writeChan and the readChan actions will do it
Channels are used to communicate • Independent (concurrent) processes can communicate through channels. • An independent, concurrent process is started in Haskell with the forkIO primitive. forkIO :: IO () -> IO () ex2 :: IO() ex2 = do { c1 <- newChan :: IO(Chan Int) ; c2 <- newChan :: IO(Chan Int) ; forkIO (client c1 c2) ; forkIO (server c2 c1) }
Client Server Example from chapt 14 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 }
Rendering Reactive Animations • To render a reactive animation, we need to connect the stream of real events (mouse clicks, key presses, clock ticks, etc.) to our abstract representation of behaviors as stream transformers. • The Goal is to write a function: reactimate :: String -> Behavior a -> (a -> IO Graphic) -> IO () • Since a (Behavior a) has form (Beh f) where f is a function with type ([Maybe UserAction],[Time]) -> [a] • We need to manufacture a pair of streams with type ([Maybe UserAction],[Time])
windowUser • We do this with the function: windowUser :: Window -> IO ([Maybe UserAction],[Time],IO ()) Used in the following fashion ((uts,ts),addEvents) <- windowUser w
The Channel Abstraction (us,ts,addEvents) <- windowUser w • us, and ts are infinite streams made with channels. • addEvents :: IO () is a action which adds the latest user actions, thus extending the streams us and ts
Reactimate reactimate :: String -> Behavior a -> (a -> IO Graphic) -> IO () reactimate title franProg toGraphic = runGraphics $ do w <- openWindowEx title (Just (0,0)) (Just (xWin,yWin)) drawBufferedGraphic (Just 30) (us,ts,addEvents) <- windowUser w addEvents let drawPic (Just p) = do g <- toGraphic p setGraphic w g addEvents getWindowTick w drawPic Nothing = return () let Event fe = sample `snapshot_` franProg mapM_ drawPic (fe (us,ts))
Making a Stream from a Channel makeStream :: IO ([a], a -> IO ()) makeStream = do ch <- newChan contents <- getChanContents ch return (contents, writeChan ch)
A Reactive window windowUser :: Window -> IO ([Maybe UserAction], [Time], IO ()) windowUser w = do (evs, addEv) <- makeStream t0 <- timeGetTime let addEvents = let loop rt = do mev <- maybeGetWindowEvent w case mev of Nothing -> return () Just e -> addEv(rt, Just e) >> loop rt in do t <- timeGetTime let rt = w32ToTime (t-t0) loop rt addEv (rt, Nothing) return (map snd evs, map fst evs, addEvents)
The “Paddle Ball” Game paddleball vel = walls `over` paddle `over` ball vel walls = let upper = paint blue (translate ( 0,1.7) (rec 4.4 0.05)) left = paint blue (translate (-2.2,0) (rec 0.05 3.4)) right = paint blue (translate ( 2.2,0) (rec 0.05 3.4)) in upper `over` left `over` right paddle = paint red (translate (fst mouse, -1.7) (rec 0.5 0.05)) between x (a,b) = x >* a &&* x <* b
The “reactive” ball ball vel = let xvel = vel `stepAccum` xbounce ->> negate xpos = integral xvel xbounce = predicate (xpos >* 2 &&* xvel >* 0 ||* xpos <* -2 &&* xvel <* 0) yvel = vel `stepAccum` ybounce ->> negate ypos = integral yvel roofbounce = ypos >* 1.5 &&* yvel >* 0 paddlebounce = (ypos `between` (-2.0,-1.5)) &&* (fst mouse `between` (xpos-0.25,xpos+0.25)) &&* (yvel <* 0) ybounce = predicate (roofbounce ||* paddlebounce) in (paint yellow (translate (xpos, ypos) (ell 0.2 0.2)))