8

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}
4

6 回答 6

11

考虑使用ExceptTfrom Control.Monad.Trans.Except(而不是使用 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

如果您对 Monads 和 Monad 转换器不满意,那么我会这样做!它们是一个巨大的帮助和程序员生产力性能的胜利。

于 2010-10-31T22:09:17.127 回答
7

有两种可能的解决方案。最接近您上面提供的代码的是:

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
    }

另一种形式在状态处理中移动错误处理:

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
    }
于 2010-10-31T15:24:39.987 回答
4

我没有看到这里有人提到Martin Grabmüller 的论文 Monad Transformers Step by Step

我发现它对学习组合 monad 非常有帮助。

于 2010-11-02T00:38:35.197 回答
4

你需要一个单子变压器。诸如mtl之类的 Monad 转换器库允许您组合不同的 monad 来制作新版本。使用 mtl,您可以定义

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

这将允许您访问StateMonad.

于 2010-10-31T15:29:18.390 回答
2

您始终可以使用带有 State monad 的 ErrorT monad 转换器(反之亦然)。看看所有关于 monads的变形金刚部分。

高温下,

于 2010-10-31T15:32:06.037 回答
1

刚刚看到像这样的例子

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

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

但据我了解,如果出现错误,您将丢失状态,因为在这里您在 Either/Except 中添加状态,因此状态只能在Right中访问。如果您需要处理错误并获取在发生错误时计算的状态,您可以使用exceptT e (State s)堆栈:

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")
于 2019-03-09T15:50:36.740 回答