Cen*_*ril 4 monads haskell state-monad monad-transformers
假设我们有一堆具有状态monad变换器的monad作为最外层的变换器,如下所示:
-- | 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
Run Code Online (Sandbox Code Playgroud)
然后我们想以某种形式做:SEWT s e w m a -> s -> SEWT t e w m a.这当然不可能使用(>>=)或do阻止,因为具有sas状态的状态monad与具有as的状态monad不同t.
然后我可以想出这样的事情:
-- | '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
Run Code Online (Sandbox Code Playgroud)
但是,是否有更优雅和一般的方式从一种状态转变为另一种状态?
我对两个解决方案感兴趣,其中第二个计算不依赖于第一个计算的最终状态(仅结果),以及它的一个位置.
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
Run Code Online (Sandbox Code Playgroud)
您的想法可以使用索引状态monad实现.
newtype IState i o a = IState { runIState :: i -> (o, a) }
Run Code Online (Sandbox Code Playgroud)
类型的值IState i o a是有状态的计算,它返回类型的值a,从转化隐式状态的种类i以o在过程中.将此与常规Statemonad进行对比,后者不允许您更改其状态类型:
type State s = IState s s
Run Code Online (Sandbox Code Playgroud)
排序索引状态monad应确保输入和输出对齐.一次计算的输出类型是下一次计算的输入.输入Atkey的参数化monad(现在通常称为索引monad),类似monad的类,描述通过有向图的路径.
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
Run Code Online (Sandbox Code Playgroud)
绑定的索引单子就像玩多米诺骨牌:如果你有办法从获得i到j和方式,从获得j到k,>>>=将你粘合在一起的多米诺骨牌成一个更大的计算,从去i到k.麦克布赖德描述了这个索引monad在Kleisli Arrows of Outrageous Fortune中更强大的版本,但是这个版本足以满足我们的目的.
如上所述,多米诺骨牌式排序正是索引状态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
Run Code Online (Sandbox Code Playgroud)
从索引状态monad中检索值不会更改状态的类型.
get :: IState s s s
get = IState $ \s -> (s, s)
Run Code Online (Sandbox Code Playgroud)
将值放入索引状态monad会丢弃旧状态.这意味着输入状态的类型可以是您喜欢的任何类型.
put :: s -> IState i s ()
put x = IState $ \_ -> (x, ())
Run Code Online (Sandbox Code Playgroud)
请注意,所有与之配合使用的代码IState完全相同State!它只是变得更聪明的类型.
这是一个简单的IState计算,它需要一个类型Int的状态,将状态更改为a String,并返回一个布尔答案.所有这些都是静态检查.
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)
Run Code Online (Sandbox Code Playgroud)