How can I write a state monad that does error handling as well?
Asked Answered
L

6

11

I need to write a state monad that can also support error handling. I was thinking of using the Either monad for this purpose because it can also provide details about what caused the error. I found a definition for a state monad using the Maybe monad however I am unable to modify it to use Either, instead of Maybe. Here's the code:

newtype StateMonad a = StateMonad (State -> Maybe (a, State))

instance Monad StateMonad where
(StateMonad p) >>= k = StateMonad (\s0 -> case p s0 of 
                                 Just (val, s1) -> let (StateMonad q) = k val in q s1
                                 Nothing -> Nothing)
return a = StateMonad (\s -> Just (a,s))

data State = State
{ log  :: String
, a    :: Int}
Leede answered 31/10, 2010 at 14:58 Comment(0)
A
12

Consider using ExceptT from Control.Monad.Trans.Except (instead of using Either).

import Control.Monad.State
import Control.Monad.Trans.Except
import Control.Monad.Identity

data MyState = S

type MyMonadT e m a = StateT MyState (ExceptT e m) a

runMyMonadT :: (Monad m) => MyMonadT e m a -> MyState -> m (Either e a)
runMyMonadT m = runExceptT . evalStateT m

type MyMonad e a = MyMonadT e Identity a
runMyMonad m = runIdentity . runMyMonadT m

If you aren't comfortable with Monads and Monad transformers then I'd do that first! They are a huge help and programmer productivity performance win.

Astri answered 31/10, 2010 at 22:9 Comment(0)
P
8

There are two possible solutions. The one that is closest to the code you provided above is:

newtype StateMonad e a = StateMonad (State -> Either e (a, State))

instance Monad (StateMonad e) where
    (StateMonad p) >>= k =
        StateMonad $ \s0 ->
            case p s0 of
                Right (val, s1) ->
                    let (StateMonad q) = k val
                     in q s1
                Left e -> Left e
    return a = StateMonad $ \s -> Right (a, s)

data State = State
    { log  :: String
    , a    :: Int
    }

The other form moves the error handling within the state handling:

newtype StateMonad e a = StateMonad (State -> (Either e a, State))

instance Monad (StateMonad e) where
    (StateMonad p) >>= k =
        StateMonad $ \s0 ->
            case p s0 of
                (Right val, s1) ->
                    let (StateMonad q) = k val
                     in q s1
                (Left e, s1) -> (Left e, s1)
    return a = StateMonad $ \s -> (Right a, s)

data State = State
    { log  :: String
    , a    :: Int
    }
Pout answered 31/10, 2010 at 15:24 Comment(2)
I don't see the difference between the first code block and the second. Did you mistakenly include the same code twice, or, if not, can you clarify the difference?Trammell
Also note that these two are operationally a little bit different. The second version allows resumable errors, whereas the first version terminates on the first error. If you are modelling logging, be aware the first version also 'loses' the log on error.Mausoleum
Z
4

You need a monad transformer. Monad transformer libraries such as mtl allow you to compose different monads to make a new version. Using mtl, you could define

type StateMonad e a = StateT State (Either e) a

which will allow you to access both state and error handling within your StateMonad.

Zygoma answered 31/10, 2010 at 15:29 Comment(0)
P
4

I didn't see anyone here mention the paper Monad Transformers Step by Step by Martin Grabmüller

I found it to be very helpful in learning about combining monads.

Peking answered 2/11, 2010 at 0:38 Comment(1)
The paper has moved to page.mi.fu-berlin.de/scravy/realworldhaskell/materialien/…Shalloon
N
3

Just saw examples like

type StateMonad e a = StateT State (Either e) a

and

type MyMonadT e m a = StateT MyState (ExceptT e m) a

but as far as I understand, you will lose your state in case of error, because here you add state inside Either/Except, so state will be only accessible in Right. If you need handle error and get state, which was computed up to moment where error occurred, you can use ExceptT e (State s) a stack:

type StateExcept e s a = ExceptT e (State s) a

test :: Int -> StateExcept String String ()
test limit =  do
    modify (succ . head >>= (:)) -- takes first char from state and adds next one in alphabet to state
    s <- get
    when (length s == limit) (throwError $ "State reached limit of " ++ show limit)

runTest :: ExceptT String (State String) () -> (Either String (), [Char])
runTest se = runState (runExceptT se) "a"


λ: runTest (forever $ test 4)
(Left "State reached limit of 4","dcba")
λ: runTest (replicateM_ 2 $ test 4)
(Right (),"cba")
Notepaper answered 9/3, 2019 at 15:50 Comment(0)
S
2

You can always use a ErrorT monad transformer with a State monad inside (or vice versa). Have a look at the transformers section of all about monads.

HTH,

Specious answered 31/10, 2010 at 15:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.