views:

134

answers:

5

Hi,

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}
A: 

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
    }
Michael Snoyman
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?
seh
@seh, good catch, it's updated
Michael Snoyman
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.
Stephen Tetley
+2  A: 

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.

John
+1  A: 

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,

Ozgur
+2  A: 

Consider using ErrorT from Control.Monad.Error (instead of using Either).

import Control.Monad.State
import Control.Monad.Error
import Control.Monad.Identity

type MyMonadT e m = StateT State (ErrorT e m)

runMyMonadT :: (Monad m, Error e) => MyMonad e m a -> State -> Either e a
runMyMonadT m = runErrorT . runStateT m

type MyMonad e = MyMonadT e Identity
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.

  • Code is typed, not tested
TomMD