5

次のようなタイプのオートマトンタイプを作成したい:

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
4

2 に答える 2

3

の精神でオートマトンを拡張できますConduit。つまり、有限数の入力に対して終了して値を返すことができます。

data Auto i o a
    = Step (i -> (o, Auto i o a))
    | End a

次に、 を使用して 2 つのオートマトンを連結するモナド インスタンスを定義できます>>=

幸いなことに、自分で実装する必要はありません。ファンクターを使用して値を返すか入れ子にすることは、まさにfree モナドが行うことです (その haddock docsを参照してください)。それでは、定義しましょう

{-# LANGUAGE DeriveFunctor #-}
import Control.Monad.Free
import Data.Void

-- | A functor describing one step of the automaton
newtype AutoF i o t = AutoF (i -> (o, t))
  deriving (Functor)

次に、元のAuto型をエイリアスとして定義できます。

type Auto i o = Free (AutoF i o)

これにより、 のすべてのインスタンスが自動的に提供されFree、元の関数を定義することもできます。

-- | If @a@ is 'Void', the machine runs forever:
runAuto :: Auto i o Void -> i -> (o, Auto i o Void)
runAuto (Pure v)  _         = absurd v
runAuto (Free (AutoF f)) i  = f i

yield :: o -> Auto i o ()
yield x = liftF (AutoF $ \_ -> (x, ()))

同じファンクターを使用するとFreeT、対応するモナド変換子が得られることに注意してください。

import Control.Monad.Trans.Free

type AutoT i o = FreeT (AutoF i o)

yieldT :: (Monad m) => o -> AutoT i o m ()
yieldT x = liftF (AutoF $ \_ -> (x, ()))

...
于 2015-07-12T16:19:12.163 に答える