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
  -- ...