状态单子:从一种状态类型转换到另一种状态类型

State monads: Transitioning from one state type to another

假设我们有一堆单子,其中一个状态单子转换器作为最外层的转换器,如下所示:

-- | SEWT: Composition of State . Except . Writer monad transformers in that
-- order where Writer is the innermost transformer.
-- the form of the computation is: s -> (Either e (a, s), w)
newtype SEWT s e w m a = SEWT {
    _runSEWT :: StateT s (ExceptT e (WriterT w m)) a }
    deriving (Functor, Applicative, Monad,
              MonadState s, MonadError e, MonadWriter w)

-- | 'runSEWT': runs a 'SEWT' computation given an initial state.
runSEWT :: SEWT s e w m a -> s -> m (Either e (a, s), w)
runSEWT ev e = runWriterT $ runExceptT $ runStateT (_runSEWT ev) e

然后我们想以某种形式做:SEWT s e w m a -> s -> SEWT t e w m a。 这当然不可能使用 (>>=)do 块,因为以 s 作为状态的状态 monad 与以 t.[=18= 的状态 monad 不同。 ]

然后我可以想出这样的事情:

-- | 'sewtTransition': transitions between one 'SEWT' computation with state s,
-- to another with state s. The current state and result of the given
-- computation is given to a mapping function that must produce the next
-- computation. The initial state must also be passed as the last parameter.
transitionState :: (Monad m, Monoid w) => ((a, s) -> SEWT t e w m a)
                -> m (SEWT s e w m a) -> s -> m (SEWT t e w m a)
transitionState _trans _comp _init = do
    (res, logs) <- _comp >>= flip runSEWT _init
    return $ do tell logs 
                case res of Left  fail -> throwError fail
                            Right succ -> _trans succ

-- 'withState': behaves like 'transitionState' but ignores the state of
-- the first computation.
withState :: (Monad m, Monoid w)
          => m (SEWT s e w m a) -> s -> m (SEWT t e w m a)
withState = transitionState $ return . fst

但是是否有更优雅和通用的方式从一种状态类型转移到另一种状态类型?

我对第二次计算不依赖于第一次计算的最终状态(仅结果)的解决方案感兴趣,并且对它是这样的解决方案感兴趣。

Edit1:改进的过渡函数:

transSEWT :: Functor m => (((a, y), x) -> (a, y)) -> SEWT x e w m a -> x -> SEWT y e w m a
transSEWT f x_c x_i = SEWT $ StateT $ \y_i -> ExceptT . WriterT $
    first ((\(a, x_f) -> f ((a, y_i), x_f)) <$>) <$> runSEWT x_c x_i

changeSEWT :: Functor m => SEWT x e w m a -> x -> SEWT y e w m a
changeSEWT = transSEWT fst

transS :: Monad m => (((a, y), x) -> (a, y)) -> StateT x m a -> x -> StateT y m a
transS f x_c x_i = StateT $ \y_i -> do (a, x_f) <- runStateT x_c x_i
                                       return $ f ((a, y_i), x_f)

changeS :: Monad m => StateT x m a -> x -> StateT y m a
changeS = transS fst

你的想法可以用 索引状态 monad 来实现。

newtype IState i o a = IState { runIState :: i -> (o, a) }

IState i o a 类型的值是一种状态计算,其中 returns 类型 a 的值,将隐式状态的类型从 i 转换为 o 过程中。将此与常规 State monad 进行对比,后者不允许您更改其状态的类型:

type State s = IState s s

排序索引状态单子应确保输入和输出对齐。一个计算的输出类型是下一个计算的输入。输入 Atkey 的 parameterised monad(现在通常称为 索引 monad),描述通过有向图的路径的类 monad 事物的 class。

class IMonad m where
    ireturn :: a -> m i i a
    (>>>=) :: m i j a -> (a -> m j k b) -> m i k b

(>>>) :: IMonad m => m i j a -> m j k b -> m i k b
mx >>> my = mx >>>= const my

绑定索引 monad 就像玩多米诺骨牌:如果你有办法从 ij 和从 jk , >>>= 会将您的多米诺骨牌粘合在一起,形成一个更大的计算,从 ik。 McBride 在 Kleisli Arrows of Outrageous Fortune 中描述了这个索引 monad 的一个更强大的版本,但这个版本已经足够我们的目的了。

如上所述,多米诺骨牌式排序正是索引状态 monad 所需要的,它需要输入和输出对齐。

instance IMonad IState where
    ireturn x = IState $ \s -> (s, x)
    IState f >>>= g = IState $ \i -> let (o, x) = f i
                                     in runIState (g x) o

从索引状态 monad 中检索值不会更改状态的类型。

get :: IState s s s
get = IState $ \s -> (s, s)

将值放入索引状态 monad 会丢弃旧状态。这意味着输入状态的类型可以是任何你喜欢的。

put :: s -> IState i s ()
put x = IState $ \_ -> (x, ())

请注意,使用 IState 的所有代码与 State 完全相同!只是变得更聪明的类型。

这是一个简单的 IState 计算,它需要一个 Int 类型的状态,将状态更改为 String,并且 returns 一个布尔答案。所有这些都经过静态检查。

myStateComputation :: IState Int String Bool
myStateComputation =
    -- poor man's do notation. You could use RebindableSyntax
    get              >>>= \s ->
    put (show s)     >>>
    ireturn (s > 5)

main = print $ runIState myStateComputation 3
-- ("3", False)