Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
COSC 4P41 – Functional Programming Programming with actions Why is I/O an issue? • I/O is a kind of side-effect. Example: Suppose there is an operation inputInt :: Int -- reads an integer inputDiff = inputInt – inputInt inputInt should return two different values (depending on the users input). Hence, its evaluation depends on the context where (or when) it is executed side-effect!!! • The meaning of an expression relying on side-effects is no longer determined by looking only at the meanings of its parts. © M. Winter 9.1 COSC 4P41 – Functional Programming Monadic I/O • The Haskell type IO a is the type of I/O actions of type a, e.g., the elements of a are somehow wrapped into an I/O container; the monad IO. • An expression of type IO a is a program which will do some I/O and then return a value of type a. • One way of looking at the I/O a types is that they provide a simple imperative language for writing I/O programs on top of Haskell, without compromising the functional model of Haskell. • This approach is called the monadic approach. It is more general and can be used to incorporate side-effect into a pure functional programming language (without its problems). © M. Winter 9.2 COSC 4P41 – Functional Programming Reading input and writing output getLine :: IO String getChar :: IO Char putStr :: String -> IO () print :: Show a => a -> IO () where () is the type containing exactly one element; the element (). Remark: In WinGHCi just those I/O actions of type IO () are actually printed to the screen. © M. Winter 9.3 COSC 4P41 – Functional Programming The do notation The do notation is a flexible mechanism. With respect to the monad IO it supports two things: • it is used to sequence I/O programs, and • it is used to ‘capture’ the values returned by IO actions and so to pass these values to actions which follow them in the program. In general: • it is used to sequence programs wrapped in monad, and • it is used to unwrap the values returned by the monad and so to pass these values to actions which follow them in the program. © M. Winter 9.4 COSC 4P41 – Functional Programming Examples putStrLn :: String -> IO () putStrLn str = do putStr str putStr ”\n” read2lines :: IO () read2lines = do getLine getLine puStrLn ”Two lines read.” getNput :: IO () getNput = do line <- getLine putStrLn line where line names the result of getLine (local variable). © M. Winter 9.5 COSC 4P41 – Functional Programming Examples (cont’d) reverse2lines :: IO () reverse2lines = do line1 <line2 <let rev1 let rev2 putStrLn putStrLn getLine getLine = reverse line1 = reverse line2 rev1 rev2 getInt :: IO Int getInt = do line <- getLine return (read line :: Int) where return :: a -> IO a packs an a value in the IO monad. © M. Winter 9.6 COSC 4P41 – Functional Programming Examples (cont’d) Notice, that the following program is not type correct: getInt = do line <- getLine (read line :: Int) Each component of a do construction has to be an element of IO a for a type a. while :: IO Bool -> IO () -> IO () while test action = do res <- test if res then do action while test action else return () © M. Winter 9.7 COSC 4P41 – Functional Programming Examples (cont’d) isEOF :: IO Bool copyInputToOutput :: IO () copyInputToOutput = while (do res <- isEOF return (not res)) (do line <- getLine putStrLn line) © M. Winter 9.8 COSC 4P41 – Functional Programming Examples (cont’d) goUntilEmpty :: IO () goUntilEmpty = do line <- getLine if line == [] then return () else (do putStrLn line goUntilEmpty) does not work since variables goUntilEmpty’ :: IO () cannot be updated. goUntilEmpty’ = do line <- getLine while (return (line /= [])) (do putStrLn line here we create a new variable line <- getLine return ()) © M. Winter 9.9 COSC 4P41 – Functional Programming Calculator example data Expr = Lit Int | Var Var | Op Ops Expr Expr data Ops = Add | Sub | Mul | Div | Mod type Var = Char initial :: Store value :: Store -> Var -> Int update :: Store -> Var -> Int -> Store data Command = Eval Expr | Assign Var Expr | Null commLine :: String -> Command eval :: Expr -> Store -> Int © M. Winter 9.10 COSC 4P41 – Functional Programming Calculator example (cont’d) command :: Command -> Store -> (Int,Store) command Null st = (0 , st) command (Eval e) st = (eval e st , st) command (Assign v e) st = (val , newSt) where val = eval e st newSt = update st v val calcStep :: Store -> IO Store calcStep st = do line <- getLine comm <- return (commLine line) (val , newSt) <- return (command comm st) print val return newSt © M. Winter 9.11 COSC 4P41 – Functional Programming Calculator example (cont’d) calcSteps :: Store -> IO () calcSteps st = while notEOF (do newSt <- calcStep st calcSteps newSt) notEOF :: IO Bool notEOF = do res <- isEOF return (not res) mainCalc :: IO () mainCalc = calcSteps initial © M. Winter 9.12 COSC 4P41 – Functional Programming Further I/O • File I/O – readFile :: FilePath -> IO String – writeFile :: FilePath -> String -> IO () – appendFile :: FilePath -> String -> IO () • Errors – ioError :: IOError -> IO a – catch :: IO a -> (IOError -> IO a) -> IO a where IOError is the system-dependent data type of I/O errors. More on error handling (especially the type IOError) can be found in the documentation for the I/O library IO.hs. © M. Winter 9.13 COSC 4P41 – Functional Programming Monads IO is a type constructor, i.e., an operation on types. It maps a type a to the type IO a of I/O action on a. A characteristic of the I/O monad is that it allows to sequence operations. do line <- getLine putStrLn line What is the type of such a combinator which sequences the operations: (>>=) :: IO a -> (a -> IO b) -> IO b or, more general, for an arbitrary type constructor m, a combinator (>>=) :: m a -> (a -> m b) -> m b © M. Winter 9.14 COSC 4P41 – Functional Programming Monads (cont’d) class Monad m where (>>=) :: m a -> (a -> m b) -> m b return :: a -> m a (>>) :: m a -> m b -> m b fail :: String -> m a m >> k = m >>= \_ -> k fail s = error s Requirements (informally): • The operation return x should simply return the value x, without any additional computational effect. • The sequencing given by >>= should be irrelevant of the way that expressions are bracketed. • >> acts like >>=, except that the value returned by the first expression is discarded rather than being passed to the second argument. • The value fail s corresponds to a computation that fails, giving the error message s. © M. Winter 9.15 COSC 4P41 – Functional Programming Rules for monads (>@>) :: Monad m => (a -> m b) -> (b -> m c) -> (a -> mc) f >@> g = \x -> (f x) >>= g Rules that should be satisfied: (M1) return >@> f = f identity law (left) (M2) f >@> return = f identity law (right) (M3) (f >@> g) >@> h = f >@> (g >@> h) associativity In terms of (>>=): (M1) (return x) >>= f (M2) m >>= return (M3) (m >>= f) >>= g = f x = m = m >>= (\x -> (f x) >>= g) In category theory (>@>) is called the Kleisli composition. © M. Winter 9.16 COSC 4P41 – Functional Programming The do notation revisited The do notation can be used for arbitrary monads (not just for the I/O monad). It just a shorthand for an expression build up from (>>=) and (>>). Example: do line <- getLine putStrLn line putStrLn ”That’s it.” return 5 is translated to getLine >>= putStrLn >> putStrLn ”That’s it.” >> return 5 © M. Winter 9.17 COSC 4P41 – Functional Programming Examples of monads • Identity monad – m a = a – m >>= f = f m – return = id • I/O monad • List monad instance Monad [] where xs >>= f = concat (map f xs) return x = [x] fail s = [] • Maybe monad (Error monad) instance Monad Maybe where (Just x) >>= k = k x Nothing >>= k = Nothing return = Just fail s = Nothing © M. Winter 9.18 COSC 4P41 – Functional Programming Examples of monads (cont’d) • Parsing monad (Parsec – a useful parser combinator library) – Import statement: import Text.ParserCombinators.Parsec The type GenParser a state b • is the type of parsers taking lists of a elements as input producing an element of b. • is implemented similar to the type Parser from Week 7. • has a parameter state. This parameter can be used as an internal state storing information during the parsing process. • has an internal and hidden error state. This state is used to keep track of errors that occurred during the parsing process. GenParser combines 3 monadic structures (parser monad, state monad, error monad) into one data structure. © M. Winter 9.19 COSC 4P41 – Functional Programming Examples of monads (cont’d) • State monad – a state monad is a type of the form State a b = a -> (a,b) where the type constructor is State a. – An operation of this type can change the state (of type a) before returning a value of type b. – Example: data Tree a = Nil | Node a (Tree a) (Tree a) Moon Ahmet 0 Dweezil Ahmet © M. Winter Moon 1 2 1 0 9.20 COSC 4P41 – Functional Programming Examples of monads (cont’d) type Table a = [a] data State a b = State (Table a -> (Table a , b)) instance Monad (State a) where return x = State (\tab -> (tab,x)) (State st) >>= f = State (\tab -> let (newTab,y) = st tab (State trans) = f y in trans newTab) © M. Winter 9.21 COSC 4P41 – Functional Programming Examples of monads (cont’d) numberTree :: Eq a => Tree a -> State numberTree Nil = return Nil numberTree (Node x t1 t2) = do num <nt1 <nt2 <return a (Tree Int) numberNode x numberTree t1 numberTree t2 (Node num nt1 nt2) numberNode :: Eq a => a -> State a Int numberNode x = State (nNode x) nNode :: Eq a => a -> (Table a -> (Table a , Int)) nNode x table | elem x table = (table , lookup x table) | otherwise = (table++[x] , length table) © M. Winter 9.22 COSC 4P41 – Functional Programming Examples of monads (cont’d) extract :: State a b -> b extract (State st) = snd (st []) numTree :: Eq a => Tree a => Tree Int numTree = extract . numberTree © M. Winter 9.23 COSC 4P41 – Functional Programming Some Monad Functions Module Prelude • • • • • readFile :: FilePath -> IO String sequence :: Monad m => [m a] -> m [a] sequence_ :: Monad m => [m a] -> m () mapM :: Monad m => (a -> m b) -> [a] -> m [b] mapM_ :: Monad m => (a -> m b) -> [a] -> m () Module Data.IORef (updatable varibles, i.e., a state monad) • newIORef :: a -> IO (IORef a) • readIORef :: IORef a -> IO a • writeIORef :: IORef a -> a -> IO () © M. Winter 9.24