次のようなタイプのオートマトンタイプを作成したい:
newtype Auto i o = Auto {runAuto :: i -> (o, Auto i o)}
これがAutomata arrowのタイプであることは知っていますが、矢印を探しているわけではありません。これをモナドにしたいので、おそらく次のような型になります
newtype Auto i o a = ???? What goes here?
次のような関数を使用します。
yield :: o -> Auto i o i
したがって、Auto モナド内から「yield」を呼び出すと、「runAuto」関数は「yield」への引数と継続関数からなるペアを返します。アプリケーションプログラムが継続関数を呼び出すと、引数は「yield」の結果としてモナド内に返されます。
これには継続モナドのフレーバーが必要になることはわかっていますが、過去に継続と格闘したにもかかわらず、これをコーディングする方法がわかりません。
また、これは Michael Snoyman のConduit モナドにかなり似ていることも知っていますが、彼は "yield" と "await" を分けています。このモナドは、すべての入力に対して正確に 1 つの出力を持たなければなりません。
背景: GUI イベントに複雑な方法で応答するコードを書いています。これを手書きのステート マシンに変えるのではなく、ユーザー インタラクションの進行に合わせて画面を更新する代わりに、一連の入力を受け入れるコードを記述できるようにしたいと考えています。
編集
これはすべて微妙に間違っていることが判明しました。Petr Pudlák の返信で提案されたコードを書きましたが、動作しているように見えましたが、「yield」操作は常に前のyield からの出力を生成しました。それは奇妙でした。
画面をじっと見つめた後、ここにコードを貼り付ける必要があることがわかりました。決定的な違いは AutoF タイプにあります。以下のものをPetrによって提案されたものと比較してください。
import Control.Applicative
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.State.Class
import Control.Monad.Trans.Class
import Control.Monad.Trans.Free
import Data.Void
class (Monad m) => AutoClass i o m | m -> i, m -> o where
yield :: o -> m i
data AutoF i o a = AutoF o (i -> a)
instance Functor (AutoF i o) where
fmap f (AutoF o nxt) = AutoF o $ \i -> f $ nxt i
newtype AutoT i o m a = AutoT (FreeT (AutoF i o) m a)
deriving (Functor, Applicative, Monad, MonadIO, MonadTrans, MonadState s)
instance (Monad m) => AutoClass i o (AutoT i o m) where
yield v = AutoT $ liftF $ AutoF v id
runAutoT :: (Monad m) => AutoT i o m Void -> m (o, i -> AutoT i o m Void)
runAutoT (AutoT step) = do
f <- runFreeT step
case f of
Pure v -> absurd v
Free (AutoF o nxt) -> return (o, AutoT . nxt)
-- Quick test
--
-- > runTest testStart
testStart :: Int -> AutoT Int Int IO Void
testStart x = do
liftIO $ putStrLn $ "My state is " ++ show x
y <- liftIO $ do
putStrLn "Give me a number: "
read <$> getLine
v1 <- yield $ x + y
liftIO $ putStrLn $ "I say " ++ show v1
v2 <- yield $ 2 * v1
testStart v2
runTest auto = do
putStrLn "Next input:"
v1 <- read <$> getLine
(v2, nxt) <- runAutoT $ auto v1
putStrLn $ "Output = " ++ show v2
runTest nxt