Exercise set 4.
It is the fourth week of the course, and we will covers a side effects in pure functional programming. We will be reading chapters 10 and 12 in Programming in Haskell by Graham Hutton, focusing on the Monad type class.
Credits
The following exercises is based on a warmup exercise formulated by Andrzej Filinski for the course Advanced Programming at the University of Copenhagen.
Reader Writer State (Plain).
Read All about monads, and pay
special attention to the Reader, Writer and State monads. Then include the
following lines in an empty .hs
file:
import Control.Monad
type ReadData = Int
type WriteData = String -- pick any monoid here.
type StateData = Double
type Result a = (a, WriteData, StateData)
-- A plain version of the RWS monad
newtype RWSP a = RWSP { runRWSP :: ReadData -> StateData -> Result a }
The purpose of RWSP
is to represent a computation that mutates a state and
depends on some read-only data. Further more, the computation has the
side-effect of writing some write data.
Complete the declaration:
instance Monad RWSP where
-- ..
Using RWSP.
In the following, replace undefined
with a suitable implementation, such that
the value of testP
becomes True
.
-- returns current read data
askP :: RWSP ReadData
askP = RWSP (\r s -> (r, mempty, s)) -- freebie
-- runs computation with new read data
withP :: ReadData -> RWSP a -> RWSP a
withP r' m = undefined
-- adds some write data to accumulator
tellP :: WriteData -> RWSP ()
tellP w = undefined
-- returns current state data
getP :: RWSP StateData
getP = undefined
-- overwrites the state data
putP :: StateData -> RWSP ()
putP s' = undefined
-- sample computation using all features
sampleP :: RWSP String
sampleP =
do r1 <- askP
r2 <- withP 5 askP
tellP "Hello, "
s1 <- getP
putP (s1 + 1.0)
tellP "world!"
return $ "r1 = " ++ show r1 ++ ", r2 = " ++ show r2 ++ ", s1 = " ++ show s1
expected :: Result String
expected = ("r1 = 4, r2 = 5, s1 = 3.5", "Hello, world!", 4.5)
testP = runRWSP sampleP 4 3.5 == expected
An RWS monad that throws errors.
Finish the implementation:
-- Version of RWS monad with errors
type Error = String
newtype RWSE a = RWSE {runRWSE :: ReadData -> StateData -> Either Error (Result a)}
-- Hint: here you may want to exploit that "Either ErrorData" is itself a monad
instance Monad RWSE where
return a = undefined
m >>= f = undefined
instance Functor RWSE where
fmap = liftM
instance Applicative RWSE where
pure = return; (<*>) = ap
askE :: RWSE ReadData
askE = undefined
withE :: ReadData -> RWSE a -> RWSE a
withE r' m = undefined
tellE :: WriteData -> RWSE ()
tellE w = undefined
getE :: RWSE StateData
getE = undefined
putE :: StateData -> RWSE ()
putE s' = undefined
throwE :: ErrorData -> RWSE a
throwE e = undefined
sampleE :: RWSE String
sampleE =
do r1 <- askE
r2 <- withE 5 askE
tellE "Hello, "
s1 <- getE
putE (s1 + 1.0)
tellE "world!"
return $ "r1 = " ++ show r1 ++ ", r2 = " ++ show r2 ++ ", s1 = " ++ show s1
sampleE2 :: RWSE String
sampleE2 =
do r1 <- askE
x <- if r1 > 3 then throwE "oops" else return 6
tellE "Blah"
return $ "r1 = " ++ show r1 ++ ", x = " ++ show x
testE = runRWSE sampleE 4 3.5 == Right expected
testE2 = runRWSE sampleE2 4 3.5 == Left "oops"
Final abstraction
Consider sharing functionality between RWSP
and RWSE
by abstracting
their common functionality into a class, and finish the instance:
-- The class of monads that support the core RWS operations
class Monad rws => RWSMonad rws where
ask :: rws ReadData
with :: ReadData -> rws a -> rws a
tell :: WriteData -> rws ()
get :: rws StateData
put :: StateData -> rws ()
-- And those that additionally support throwing errors
class RWSMonad rwse => RWSEMonad rwse where
throw :: Error -> rwse a
-- RWSP is an RWS monad
instance RWSMonad RWSP where
-- ...
-- So is RWSE
instance RWSMonad RWSE where
-- ...
-- But RWSE also supports errors
instance RWSEMonad RWSE where
-- ...