我想用这样的类型创建一个自动机类型:
newtype Auto i o = Auto {runAuto :: i -> (o, Auto i o)}
我知道这是Automata 箭头的类型,但我不是在寻找箭头。我想让它成为一个单子,所以大概它会有一个像
newtype Auto i o a = ???? What goes here?
具有这样的功能:
yield :: o -> Auto i o i
因此,当我从 Auto monad 中调用“yield”时,“runAuto”函数会返回一个由“yield”参数和延续函数组成的对。当应用程序调用延续函数时,参数会在 monad 中作为“yield”的结果返回。
我知道这将需要一些延续单子的味道,但尽管过去曾与延续争论过,但我看不出如何编写这个代码。
我也知道这很像 Michael Snoyman 的Conduit monad,除了他将“yield”和“await”分开。对于每个输入,这个 monad 必须只有一个输出。
背景:我正在编写一些以复杂方式响应 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