> {-# LANGUAGE FlexibleInstances, FlexibleContexts #-} > module Lecture8summary where > import Lecture7summary hiding (get, put, stAct) > get' :: St State > get' = S (\ s -> (s, s)) > put' :: State -> St () > put' s = S (\ s' -> ((), s)) Preliminaries: Two functions from |Control.Monad|: > join :: Monad m => m (m a) -> m a > join mmx = do mx <- mmx > x <- mx > return x > liftM :: Monad m => (a -> b) -> m a -> m b > liftM f = (>>= (return . f)) |liftM| is just |fmap| in disguise. Revisiting the exception-raising evaluator from Lecture 7: < evalEx :: Term -> Exc Float < evalEx = foldTerm fC fA fD < where < fC = return < fA m1 m2 = do x1 <- m1 < x2 <- m2 < return (x1 + x2) < fD m1 m2 = do x1 <- m1 < x2 <- m2 < if x2 == 0 < then Raise "Division by 0!" < else return (x1 + x2) The use of the specifics of |Exc| to raise an exception is quite ham-fisted. Much better to make ``exception-like'' monads a type class: > class Monad m => ExcMonad m where > raise :: Exception -> m a |raise| must fulfill certain requirements, if |m| is to behave like |Exc|, for example < raise e >>= f = raise e Obviously |Exc| is an instance of |ExcMonad|: > instance ExcMonad Exc where > raise = Raise We can now write a more abstract version of the evaluator, which can be used with any ``exception-like'' monad: > evalEx :: ExcMonad m => Term -> m Float > evalEx = foldTerm fC fA fD > where > fC = return > fA m1 m2 = do x1 <- m1 > x2 <- m2 > return (x1 + x2) > fD m1 m2 = do x1 <- m1 > x2 <- m2 > if x2 == 0 > then raise "Division by 0!" > else return (x1 / x2) Similarly, the state-based evaluator depended on the concrete representation we chose for |St|: < evalSt :: Term -> St Float < evalSt = foldTerm fC fA fD < where < fC = return < fA m1 m2 = do x1 <- m1 < x2 <- m2 < stAct incA < return (x1 + x2) < fD m1 m2 = do x1 <- m1 < x2 <- m2 < stAct incD < return (x1 / x2) We have already made certain progress here with |stAct| which is written in terms of two functions which act on the state: |get| and |put|. These are the characteristic functions of a ``state-like'' monad: > class Monad m => StMonad m where > get :: m State > put :: State -> m () related by the laws: < do { put s; put s' } = put s' -- overwriting state < do { put s; get } = do { put s; return s } -- |get| after |put| < do { s <- get; put s } = return -- |put| after |get| < do { s <- get; s' <- get; k s s'} = do { s <- get; k s s } -- idempotence We have > instance StMonad St where > get = get' > put = put' > stAct :: StMonad m => (State -> State) -> m () > stAct f = do {s <- get; put (f s)} > evalSt :: StMonad m => Term -> m Float > evalSt = foldTerm fC fA fD > where > fC = return > fA m1 m2 = do x1 <- m1 > x2 <- m2 > stAct incA > return (x1 + x2) > fD m1 m2 = do x1 <- m1 > x2 <- m2 > stAct incD > return (x1 / x2) This gives us the possibility of \emph{combining monads}, for example: > evalExSt :: (ExcMonad m, StMonad m) => Term -> m Float > evalExSt = foldTerm fC fA fD > where > fC = return > fA m1 m2 = do x1 <- m1 > x2 <- m2 > stAct incA > return (x1 + x2) > fD m1 m2 = do x1 <- m1 > x2 <- m2 > if x2 == 0 > then raise "Division by 0!" > else do stAct incD > return (x1 / x2) But how do we get such a monad, that implements both the exception-raising and the state operations? It turns out we can systematically transform \emph{any} monad, hence also |St|, into an |ExcMonad|: > newtype EXC m a = E { unE :: m (Exc a) } (This naming of the constructed part is equivalent to unE :: EXC m a -> m (Exc a) unE (E mx) = mx ) The new type |EXC m a| inherits the monadic structure: > instance Monad m => Monad (EXC m) where > return = E . return . return > (E mex) >>= f = E (mex >>= g) > where > g (Raise e) = return (Raise e) > g (Return a) = unE (f a) and the resulting monad is ``exception-like'': > instance Monad m => ExcMonad (EXC m) where > raise = E . return . raise Remark ------ As an aside, we remark that many other natural ideas of extending a monad |m| with exception features don't work, for example, if we try > data EXC1 m a = E1 (Exc (m a)) we find that, when it comes to defining the monad structure > instance Monad m => Monad (EXC1 m) where > return = E1 . return . return > E1 (Raise e) >>= f = E1 (Raise e) |return| and half of |(>>=)| work out fine, but > E1 (Return ma) >>= f = E1 (Return (ma >>= g)) > where > g a = case f a of > E1 (Return mb) -> mb > E1 (Raise e) -> undefined we cannot complete the definition in a reasonable way, since we don't have an appropriate value |mb| in our hands. (End of Remark) --------------- Therefore, |EXC St| is ``exception-like'', and by recovering the underlying |St| it is also ``state-like''. < instance StMonad m => StMonad (EXC m) where < get = E (liftM return get) < put s = E (liftM return (put s)) This is rather unwieldy. Not only is there the code duplication of |E (liftM return ...)|, but we're also going to have an unnecessarily hard time proving that this is indeed a state monad. It turns out that this is a common enough problem to warrent the introduction of a new type class: > class MonadTrans t where > lift :: Monad m => m a -> t m a which collects together monad transformers for which |lift| is a monad morphism, that is < lift . return = return < lift (ma >>= f) = lift ma >>= (lift . f) This allows us to recover the operations of the transformed monad |m| in the resulting monad |t m| in a consistent way. In our case, we have > instance MonadTrans EXC where > lift = E . liftM return and > instance StMonad m => StMonad (EXC m) where > get = lift get > put s = lift (put s) We can now easily prove the state monad laws, for example do { s <- get; put s } = { desugaring the |do|-notation } get >>= put = { def. |get|, |put| for |EXC m| } lift get >>= lift . put = { |lift| monad morphism } lift (get >>= put) = { |StMonad m| } lift return = { |lift| monad morphism } return Similarly, we will be able to use |lift| to prove properties of other monads which we want to extend with exceptions. We can now use |evalExSt| with |Exc St a|: > instance Show a => Show (EXC St a) where > show (E ste) = show (liftM show ste) > testAnswer1 :: EXC St Float > testAnswer1 = evalExSt answer > testWrong1 :: EXC St Float > testWrong1 = evalExSt wrong We could have also done it differently, namely by making a given monad ``state-like'' (see Exercises 9).