How can I write a state monad that does error handling as well?
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 = Stat开发者_如何学编程eMonad (\s -> Just (a,s))
data State = State
{ log :: String
, a :: Int}
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.
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
}
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
.
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.
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,
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")
精彩评论