我不认为你可以制作你想要的单子。正如我在与 jozefg 的讨论中提到的,我们有两个单子定律说
f >=> return = f
return >=> f = f
这意味着在绑定位置不会发生任何“有趣”的事情。特别是,我们不能在每次绑定时运行状态转换函数,因为 thenf >=> return
将运行该转换函数而f
不会运行,并且这些定律将被打破。
但是,这并不能阻止我们执行代表我们运行状态转换的单子操作。因此,我将勾勒出如何设计一个跟踪此类转换并按需运行它们的 monad 的想法。如果您希望 API 有用,您肯定需要充实一些 API。s
基本思想是,我们将存储一个s
和一个转换表,而不仅仅是一个as 状态。首先,一些样板。
{-# LANGUAGE FlexibleInstances, GeneralizedNewtypeDeriving, MultiParamTypeClasses #-}
import Control.Arrow
import Control.Applicative
import Control.Monad.State
现在,让我们只处理s -> s
过渡。您可以随心所欲地实现它们——包括查看谓词和转换列表并挑选出您想要运行的那些,如果那是您的一杯茶。但这与正确完成其余的想法是正交的。我们将定义我们的新类型,并给它一个Monad
实例,该实例只是分派到底层类型。
newtype TStateT s m a = TStateT { unTStateT :: StateT (s, s -> s) m a }
deriving (Functor, Applicative, Monad)
该MonadState
实例比仅使用 有点棘手deriving
,但仍然非常简单。大概在公开场合我们想假装它只是s
状态的一部分,所以我们需要集中注意力。我们还将给出runStateT
模拟,并选择一个合理的初始转换函数。(稍后我们将提供修改此选择的方法。)
instance Monad m => MonadState s (TStateT s m) where
state f = TStateT (state (\(s, t) -> let (v, s') = f s in (v, (s', t))))
runTStateT :: Functor m => TStateT s m a -> s -> m (a, s)
runTStateT m s = second fst <$> runStateT (unTStateT m) (s, id)
现在是有趣的一点。的超能力TStateT
是它有一些可以随时运行的转换。因此,让我们提供一种运行它们的方法和一种修改转换表的方法。
step :: Monad m => TStateT s m ()
step = TStateT (gets snd) >>= modify
modifyTransitions :: Monad m => ((s -> s) -> (s -> s)) -> TStateT s m ()
modifyTransitions = TStateT . modify . second
这几乎就是一切!