Как я могу написать монаду состояния, которая также обрабатывает ошибки? - PullRequest
8 голосов
/ 31 октября 2010

Мне нужно написать монаду о состоянии, которая также может поддерживать обработку ошибок. Я думал об использовании монады Either для этой цели, потому что она также может предоставить подробную информацию о том, что вызвало ошибку. Я нашел определение для монады состояния, использующей монаду Maybe, однако я не могу изменить ее, чтобы использовать 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}

Ответы [ 6 ]

11 голосов
/ 01 ноября 2010

Подумайте об использовании ExceptT из Control.Monad.Trans.Except (вместо использования любого из них).

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

Если вам не нравятся монады и трансформаторы монад, тогда я бы сделал это сначала !Это огромный выигрыш в производительности и производительности труда программистов.

7 голосов
/ 31 октября 2010

Есть два возможных решения.Наиболее близкий к приведенному выше коду:

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
    }
4 голосов
/ 02 ноября 2010

Я не видел здесь никого, кто бы упоминал статью Монада Трансформеры Шаг за Шагом Мартина Грабмюллера

Я считаю, что это очень полезно при изучении комбинирования монад.

4 голосов
/ 31 октября 2010

Вам нужен монадный трансформатор.Библиотеки преобразователей монад, такие как mtl , позволяют вам составлять разные монады для создания новой версии.Используя mtl, вы можете определить

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

, что позволит вам получить доступ как к состоянию, так и к обработке ошибок в вашем StateMonad.

2 голосов
/ 31 октября 2010

Вы всегда можете использовать монадный преобразователь ErrorT с монадой состояний внутри (или наоборот). Взгляните на раздел трансформаторов все о монадах .

НТН,

0 голосов
/ 09 марта 2019

Только что видел примеры типа

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

и

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

но, насколько я понимаю, вы потеряете свое состояние в случае ошибки, потому что здесь вы добавляете состояние внутри Either / Except, поэтому состояние будет доступно только в Right . Если вам требуется обработать ошибку и получить состояние, которое было вычислено до момента возникновения ошибки, вы можете использовать Кроме T e (State s) a стек:

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