© M. Winter
COSC 4P41 – Functional Programming
9.1
Programming with actions
Why is I/O an issue?
• I/O is a kind of side-effect. Example:Suppose there is an operationinputInt :: 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
COSC 4P41 – Functional Programming
9.2
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
COSC 4P41 – Functional Programming
9.3
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 tothe screen.
© M. Winter
COSC 4P41 – Functional Programming
9.4
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
COSC 4P41 – Functional Programming
9.5
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
COSC 4P41 – Functional Programming
9.6
Examples (cont’d)
reverse2lines :: IO ()
reverse2lines = do line1 <- getLine
line2 <- getLine
let rev1 = reverse line1
let rev2 = reverse line2
putStrLn rev1
putStrLn 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
COSC 4P41 – Functional Programming
9.7
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
COSC 4P41 – Functional Programming
9.8
Examples (cont’d)
isEOF :: IO Bool
copyInputToOutput :: IO ()
copyInputToOutput
= while (do res <- isEOF
return (not res))
(do line <- getLine
putStrLn line)
© M. Winter
COSC 4P41 – Functional Programming
9.9
Examples (cont’d)
goUntilEmpty :: IO ()goUntilEmpty = do line <- getLine if line == [] then return () else (do putStrLn line goUntilEmpty)
goUntilEmpty’ :: IO ()goUntilEmpty’ = do line <- getLine while (return (line /= [])) (do putStrLn line line <- getLine return ())
does not work since variables cannot be updated.
here we create a new variable
© M. Winter
COSC 4P41 – Functional Programming
9.10
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
COSC 4P41 – Functional Programming
9.11
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
COSC 4P41 – Functional Programming
9.12
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
COSC 4P41 – Functional Programming
9.13
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
COSC 4P41 – Functional Programming
9.14
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
COSC 4P41 – Functional Programming
9.15
Monads (cont’d)class Monad m where
(>>=) :: m a -> (a -> m b) -> m breturn :: a -> m a(>>) :: m a -> m b -> m b
fail :: String -> m am >> k = m >>= \_ -> kfail 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
COSC 4P41 – Functional Programming
9.16
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 = f x
(M2) m >>= return = m
(M3) (m >>= f) >>= g = m >>= (\x -> (f x) >>= g)
In category theory (>@>) is called the Kleisli composition.
© M. Winter
COSC 4P41 – Functional Programming
9.17
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
COSC 4P41 – Functional Programming
9.18
Examples of monads
• Identity monad– m a = a– m >>= f = f m– return = id
• I/O monad• List monad
instance Monad [] wherexs >>= f = concat (map f xs)return x = [x]fail s = []
• Maybe monad (Error monad)instance Monad Maybe where
(Just x) >>= k = k xNothing >>= k = Nothingreturn = Justfail s = Nothing
© M. Winter
COSC 4P41 – Functional Programming
9.19
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
COSC 4P41 – Functional Programming
9.20
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
Dweezil
Ahmet
Ahmet
Moon
0
2
1
1
0
© M. Winter
COSC 4P41 – Functional Programming
9.21
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
COSC 4P41 – Functional Programming
9.22
Examples of monads (cont’d)
numberTree :: Eq a => Tree a -> State a (Tree Int)
numberTree Nil = return Nil
numberTree (Node x t1 t2) = do num <- numberNode x
nt1 <- numberTree t1
nt2 <- numberTree t2
return (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
COSC 4P41 – Functional Programming
9.23
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
COSC 4P41 – Functional Programming
9.24
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 ()