如何编写一个可以进行错误处理的状态单子?

发布于 2024-09-30 04:04:13 字数 576 浏览 5 评论 0原文

我需要编写一个也可以支持错误处理的状态单子。我正在考虑使用 Either monad 来实现此目的,因为它还可以提供有关导致错误的原因的详细信息。我使用 Maybe monad 找到了状态 monad 的定义,但是我无法修改它以使用 Either,而不是 Maybe。这是代码:

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}

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}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

十秒萌定你 2024-10-07 04:04:13

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

如果您对 Monad 和 Monad 转换器感到不舒服,那么我会先这样做!它们对程序员的生产力和性能提升有巨大的帮助。

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.

陌伤ぢ 2024-10-07 04:04:13

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

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
    }

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
    }
鹿! 2024-10-07 04:04:13

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

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

允许您访问 StateMonad 中的状态和错误处理。

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.

念三年u 2024-10-07 04:04:13

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

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

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.

三月梨花 2024-10-07 04:04:13

的例子

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

刚刚看到了像and 之类

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")

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")
停顿的约定 2024-10-07 04:04:13

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

哈特哈,

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,

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文