あなたが求めているモナドを作ることはできないと思います。jozefg との議論で述べたように、次の 2 つのモナド則があります。
f >=> return = f
return >=> f = f
これは、バインディングの場所で「興味深い」ことは何も起こらないことを意味します。特に、各バインディングで状態遷移関数を実行することはできません。なぜなら、f >=> return
その遷移関数を実行しても実行せf
ず、これらの法則が破られるからです。
しかし、だからといって、状態遷移を実行するモナド アクションを作成することを止めるわけではありません。そこで、このような遷移を追跡してオンデマンドで実行するモナドを設計する方法のアイデアをスケッチします。API を使いたい場合は、API を肉付けする必要があります。基本的な考え方は、s
状態としてだけではなく、遷移テーブルと遷移テーブルの両方を格納するというものs
です。まず、いくつかのボイラープレート。
{-# 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
そして、それはほとんどすべてです!