モナドは多くの驚くべき、クレイジーなことをすることができます。それらは、値の重ね合わせを保持する変数を作成できます。計算する前に、将来のデータにアクセスできるようになります。彼らはあなたが破壊的な更新を書くことを可能にすることができますが、実際にはそうではありません。そして、継続モナドを使用すると、人々の心を壊す!通常は自分のものにすることができます。 ;-)
しかし、ここに課題があります。一時停止のモナドを作成できますか?
[。 ) step :: s-> Pause s()->(s、Maybe(Pause s()))
Pause
モナドは一種の状態モナドです(したがって、mutate
、明白なセマンティクスを備えています)。通常、このようなモナドには、計算を実行して最終状態を返す、ある種の「実行」関数があります。ただし、Pause
は異なります。step
関数を提供します。この関数は、魔法のyield
関数を呼び出すまで計算を実行します。ここで計算は一時停止され、後で計算を再開するのに十分な情報が呼び出し元に返されます。
さらに素晴らしい場合:呼び出し元がstep
呼び出し間の状態を変更できるようにします。 (たとえば、上記の型シグネチャはこれを可能にするはずです。)
ユースケース:複雑なことを行うコードを書くのは簡単なことがよくありますが、それを変換するためのトータルPITA output操作の中間状態。ユーザーが実行の途中でchange何かを実行できるようにしたい場合、物事は非常に速く複雑になります。
実装のアイデア:
明らかにスレッド、ロック、およびIO
を使用して実行できます。しかし、もっとうまくやれるでしょうか? ;-)
継続モナドで何か気が狂った?
おそらく、yield
が現在の状態をログに記録するだけの、ある種のライターモナドであり、ログ内の状態を反復処理することで、step
のふりをすることができます。 (明らかに、現在は実際には何も「一時停止」していないため、ステップ間で状態を変更することはできません。)
承知しました;計算を結果で終了させるか、それ自体を一時停止して、再開時に使用するアクションとその時点の状態を指定するだけです。
data Pause s a = Pause { runPause :: s -> (PauseResult s a, s) }
data PauseResult s a
= Done a
| Suspend (Pause s a)
instance Monad (Pause s) where
return a = Pause (\s -> (Done a, s))
m >>= k = Pause $ \s ->
case runPause m s of
(Done a, s') -> runPause (k a) s'
(Suspend m', s') -> (Suspend (m' >>= k), s')
get :: Pause s s
get = Pause (\s -> (Done s, s))
put :: s -> Pause s ()
put s = Pause (\_ -> (Done (), s))
yield :: Pause s ()
yield = Pause (\s -> (Suspend (return ()), s))
step :: Pause s () -> s -> (Maybe (Pause s ()), s)
step m s =
case runPause m s of
(Done _, s') -> (Nothing, s')
(Suspend m', s') -> (Just m', s')
Monad
インスタンスは、通常の方法でシーケンスを実行し、最終結果をk
継続に渡すか、中断時に実行される残りの計算を追加します。
注:このモナドの現在の状態s
に直接アクセスすることはできませんでした。
Pause s
は、mutate
およびyield
操作の単なる無料のモナドです。直接実装すると、次のようになります。
data Pause s a
= Return a
| Mutate (s -> s) (Pause s a)
| Yield (Pause s a)
instance Monad (Pause s) where
return = Return
Return a >>= k = k a
Mutate f p >>= k = Mutate f (p >>= k)
Yield p >>= k = Yield (p >>= k)
目的のAPIを提供するためのいくつかのスマートコンストラクターを使用します。
mutate :: (s -> s) -> Pause s ()
mutate f = Mutate f (return ())
yield :: Pause s ()
yield = Yield (return ())
そしてそれを駆動するためのステップ関数
step :: s -> Pause s () -> (s, Maybe (Pause s ()))
step s (Mutate f k) = step (f s) k
step s (Return ()) = (s, Nothing)
step s (Yield k) = (s, Just k)
これを直接使用して定義することもできます
data Free f a = Pure a | Free (f (Free f a))
(私の「無料」パッケージから)
data Op s a = Mutate (s -> s) a | Yield a
一時停止のモナドがすでにあります
type Pause s = Free (Op s)
スマートコンストラクターとステッパーを定義する必要があります。
高速化
現在、これらの実装は簡単に推論できますが、最速の運用モデルはありません。特に、(>> =)の左関連の使用は、漸近的に遅いコードを生成します。
これを回避するには、 Codensity モナドを既存の無料モナドに適用するか、 'Church free' モナドを直接使用します。どちらも詳細に説明します。ブログ。
http://comonad.com/reader/2011/free-monads-for-less/
http://comonad.com/reader/2011/free-monads-for-less-2/
http://comonad.com/reader/2011/free-monads-for-less-3/
Freeモナドのチャーチエンコードバージョンを適用した結果、データ型のモデルについて簡単に推論でき、それでも高速な評価モデルが得られます。
freeモナドを使用して、これを実行する方法を説明します。えーと、ええと、彼らは何ですか?それらは、ノードにアクションがあり、リーフに値があるツリーであり、>>=
木の接ぎ木のように振る舞います。
data f :^* x
= Ret x
| Do (f (f :^* x))
Fを書くことは珍しいことではありません*数学のそのようなことのためのX、それ故に私の気難しい中置タイプ名。インスタンスを作成するには、f
をマップできるものにする必要があります。どのFunctor
でもかまいません。
instance Functor f => Monad ((:^*) f) where
return = Ret
Ret x >>= k = k x
Do ffx >>= k = Do (fmap (>>= k) ffx)
これは、「すべての葉にk
を適用し、結果のツリーに移植する」だけです。これらの缶ツリーは、インタラクティブな計算のためにstrategiesを表します。ツリー全体が環境とのすべての可能な相互作用をカバーし、環境はツリー内のどのパスをたどるかを選択します。なぜ彼らは無料?それらは単なる木であり、どの戦略が他のどの戦略と同等であるかを示す興味深い方程式理論はありません。
それでは、実行したい一連のコマンドに対応するファンクターを作成するためのキットを用意しましょう。この事
data (:>>:) s t x = s :? (t -> x)
instance Functor (s :>>: t) where
fmap k (s :? f) = s :? (k . f)
入力タイプx
および出力タイプs
の-oneコマンドの後にt
で値を取得するというアイデアをキャプチャします。これを行うには、s
で入力を選択し、x
でコマンドの出力を指定してt
で値を続行する方法を説明する必要があります。そのようなもの全体に関数をマッピングするには、それを継続に追加します。これまでのところ、標準装備。私たちの問題では、2つのファンクターを定義することができます。
type Modify s = (s -> s) :>>: ()
type Yield = () :>>: ()
実行したいコマンドの値型を書き留めたようなものです。
次に、これらのコマンドの間にchoiceを提供できることを確認しましょう。ファンクター間の選択がファンクターを生み出すことを示すことができます。より標準的な装備。
data (:+:) f g x = L (f x) | R (g x)
instance (Functor f, Functor g) => Functor (f :+: g) where
fmap k (L fx) = L (fmap k fx)
fmap k (R gx) = R (fmap k gx)
そう、 Modify s :+: Yield
は、変更と降伏の間の選択を表します。 signatureの単純なコマンド(計算を操作するのではなく、値の観点から世界と通信する)は、この方法でファンクターに変換できます。手でやらなきゃいけないのが面倒!
それは私にあなたのモナドを与えます:modifyとyieldの署名上の無料のモナド。
type Pause s = (:^*) (Modify s :+: Yield)
Modifyコマンドとyieldコマンドをone-do-then-returnとして定義できます。 yield
のダミー入力をネゴシエートすることを除けば、それは単なる機械的なものです。
mutate :: (s -> s) -> Pause s ()
mutate f = Do (L (f :? Ret))
yield :: Pause s ()
yield = Do (R (() :? Ret))
step
関数は、戦略ツリーに意味を与えます。それは制御演算子であり、ある計算を(多分)別の計算から構築します。
step :: s -> Pause s () -> (s, Maybe (Pause s ()))
step s (Ret ()) = (s, Nothing)
step s (Do (L (f :? k))) = step (f s) (k ())
step s (Do (R (() :? k))) = (s, Just (k ()))
step
関数は、Ret
で終了するか、生成されるまで戦略を実行し、状態を変化させます。
一般的な方法は次のようになります。コマンド(値の観点から相互作用する)を制御演算子(計算を操作する)から分離します。コマンドの署名(ハンドルのクランク)の上に「戦略ツリー」の無料モナドを構築します。戦略ツリーを再帰的に実行して、制御演算子を実装します。
タイプの署名と正確には一致しませんが、確かに単純です。
{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, UndecidableInstances #-}
import Control.Monad.State
newtype ContinuableT m a = Continuable { runContinuable :: m (Either a (ContinuableT m a)) }
instance Monad m => Monad (ContinuableT m) where
return = Continuable . return . Left
Continuable m >>= f = Continuable $ do
v <- m
case v of
Left a -> runContinuable (f a)
Right b -> return (Right (b >>= f))
instance MonadTrans ContinuableT where
lift m = Continuable (liftM Left m)
instance MonadState s m => MonadState s (ContinuableT m) where
get = lift get
put = lift . put
yield :: Monad m => ContinuableT m a -> ContinuableT m a
yield = Continuable . return . Right
step :: ContinuableT (State s) a -> s -> (Either a (ContinuableT (State s) a), s)
step = runState . runContinuable
-- mutate unnecessary, just use modify
_{-# LANGUAGE TupleSections #-}
newtype Pause s x = Pause (s -> (s, Either x (Pause s x)))
instance Monad (Pause s) where
return x = Pause (, Left x)
Pause k >>= f = Pause $ \s -> let (s', v) = k s in
case v of
Left x -> step (f x) s'
Right x -> (s', Right (x >>= f))
mutate :: (s -> s) -> Pause s ()
mutate f = Pause (\s -> (f s, Left ()))
yield :: Pause s ()
yield = Pause (, Right (return ()))
step :: Pause s x -> s -> (s, Either x (Pause s x))
step (Pause x) = x
_
それが私がこれを書いた方法です。 step
にもう少し一般的な定義を付けましたが、runPause
という名前にすることもできます。実際、step
のタイプについて考えると、Pause
の定義につながります。
monad-coroutine パッケージには、一般的なモナド変換子が含まれています。 _Pause s
_モナドはCoroutine (State s) Id
と同じです。コルーチンを他のモナドと組み合わせることができます。
関連: http://themonadreader.files.wordpress.com/2010/01/issue15.pdf のプロンプトモナド
注:この答えは 利用可能 Gistの読み書きのできるHaskellファイルとしてです。
私はこの運動をとても楽しんだ。私は答えを見ずにそれをやろうとしました、そしてそれはそれだけの価値がありました。かなりの時間がかかりましたが、結果は驚くほど他の2つの答えと、 monad-coroutine ライブラリに近いものになっています。したがって、これはこの問題に対するやや自然な解決策だと思います。この演習がなければ、monad-coroutineが実際にどのように機能するのか理解できません。
いくつかの付加価値を追加するために、最終的にソリューションにたどり着いた手順を説明します。
状態モナドの認識
州を扱っているので、州のモナドで効果的に記述できるパターンを探します。特に、_s - s
_はs -> (s, ())
と同型であるため、State s ()
に置き換えることができます。また、タイプs -> x -> (s, y)
の関数をx -> (s -> (s, y))
に切り替えることができます。これは、実際には_x -> State s y
_です。これにより、署名が更新されます
_mutate :: State s () - Pause s ()
step :: Pause s () - State s (Maybe (Pause s ()))
_
一般化
私たちのPause
モナドは現在、州によってパラメータ化されています。ただし、現在、状態は実際には何も必要ないことがわかります。また、状態モナドの詳細も使用していません。したがって、任意のモナドによってパラメーター化される、より一般的なソリューションを作成することを試みることができます。
_mutate :: (Monad m) = m () -> Pause m ()
yield :: (Monad m) = Pause m ()
step :: (Monad m) = Pause m () -> m (Maybe (Pause m ()))
_
また、_()
_だけでなく、あらゆる種類の値を許可することで、mutate
とstep
をより一般的にしようとすることもできます。そして、_Maybe a
_がEither a ()
と同型であることを認識することにより、最終的に署名を次のように一般化できます。
_mutate :: (Monad m) = m a -> Pause m a
yield :: (Monad m) = Pause m ()
step :: (Monad m) = Pause m a -> m (Either (Pause m a) a)
_
step
が計算の中間値を返すようにします。
モナド変換子
これで、実際にモナドからモナドを作成しようとしていることがわかります。いくつかの機能を追加します。これは通常 モナド変換子 と呼ばれるものです。さらに、mutate
の署名は、MonadTrans
の- lift とまったく同じです。おそらく、私たちは正しい方向に進んでいます。
最後のモナド
step
関数は、モナドの最も重要な部分のようです。必要なものだけを定義します。おそらく、これは新しいデータ構造である可能性がありますか?やってみよう:
_import Control.Monad
import Control.Monad.Cont
import Control.Monad.State
import Control.Monad.Trans
data Pause m a
= Pause { step :: m (Either (Pause m a) a) }
_
Either
部分がRight
の場合、これは一時停止のない単なるモナド値です。これにより、最も簡単なもの、つまりlift
のMonadTrans
関数を実装する方法がわかります。
_instance MonadTrans Pause where
lift k = Pause (liftM Right k)
_
mutate
は単なる専門分野です。
_mutate :: (Monad m) => m () -> Pause m ()
mutate = lift
_
Either
部分がLeft
の場合、一時停止後の継続的な計算を表します。それでは、そのための関数を作成しましょう。
_suspend :: (Monad m) => Pause m a -> Pause m a
suspend = Pause . return . Left
_
これで、計算をyield
するのは簡単です。空の計算で中断するだけです:
_yield :: (Monad m) => Pause m ()
yield = suspend (return ())
_
それでも、最も重要な部分が欠けています。 Monad
インスタンス。修正しましょう。 return
の実装は簡単で、内側のモナドを持ち上げるだけです。 _>>=
_の実装は少し注意が必要です。元のPause
値が単純な値(_Right y
_)のみであった場合、結果として_f y
_をラップするだけです。継続できる一時停止された計算(_Left p
_)の場合、再帰的にその計算に降ります。
_instance (Monad m) => Monad (Pause m) where
return x = lift (return x) -- Pause (return (Right x))
(Pause s) >>= f
= Pause $ s >>= \x -> case x of
Right y -> step (f y)
Left p -> return (Left (p >>= f))
_
テスト
状態を使用および更新するモデル関数を作成して、計算中に生成してみましょう。
_test1 :: Int -> Pause (State Int) Int
test1 y = do
x <- lift get
lift $ put (x * 2)
yield
return (y + x)
_
そして、モナドをデバッグするヘルパー関数-その中間ステップをコンソールに出力します:
_debug :: Show s => s -> Pause (State s) a -> IO (s, a)
debug s p = case runState (step p) s of
(Left next, s') -> print s' >> debug s' next
(Right r, s') -> return (s', r)
main :: IO ()
main = do
debug 1000 (test1 1 >>= test1 >>= test1) >>= print
_
結果は
_2000
4000
8000
(8000,7001)
_
予想通り。
コルーチンとmonad-コルーチン
私たちが実装したのは、 コルーチン を実装する非常に一般的なモナドソリューションです。おそらく驚くことではないが、誰かが以前にアイデアを持っていて、 monad-coroutine パッケージを作成した。それほど驚くことではありませんが、これは私たちが作成したものと非常によく似ています。
パッケージはアイデアをさらに一般化します。継続的な計算は、任意のファンクター内に保存されます。これにより、 suspend 中断された計算を処理する方法の多くのバリエーションが可能になります。たとえば、to 値を渡す の呼び出し元に resume (step
と呼びます)、またはto 値を待つ =継続するために提供されるなど。