Haskell中一个yield/await函数的延拓单子

Haskell中一个yield/await函数的延拓单子,haskell,monads,continuation,Haskell,Monads,Continuation,我想创建一个自动机类型,类型如下: newtype Auto i o = Auto {runAuto :: i -> (o, Auto i o)} yield :: o -> Auto i o i 我知道这是一种箭,但我不是在找箭。我想把它做成单子,所以它大概会有一个 newtype Auto i o a = ???? What goes here? 具有如下功能: newtype Auto i o = Auto {runAuto :: i -> (o, Auto i o

我想创建一个自动机类型,类型如下:

newtype Auto i o = Auto {runAuto :: i -> (o, Auto i o)}
yield :: o -> Auto i o i
我知道这是一种箭,但我不是在找箭。我想把它做成单子,所以它大概会有一个

newtype Auto i o a = ???? What goes here?
具有如下功能:

newtype Auto i o = Auto {runAuto :: i -> (o, Auto i o)}
yield :: o -> Auto i o i
因此,当我从Auto monad中调用“yield”时,“runAuto”函数返回一对,由“yield”的参数和continuation函数组成。当应用程序调用continuation函数时,参数将作为“yield”的结果在monad中返回

我知道这将需要一些延续单子的味道,但尽管过去曾与延续单子争论过,我不知道如何编写这一个

我也知道,这与迈克尔·斯诺曼(MichaelSnoyman)的很相似,只是他将“收益”和“等待”分开。对于每个输入,这个单子必须只有一个输出

背景:我正在编写一些以复杂方式响应GUI事件的代码。我不希望把它变成一个手工编码的状态机,而是希望能够编写接受一系列输入的代码,作为用户交互过程中屏幕更新的回报

编辑

所有这些都被证明是微妙的错误。我编写了Petr Pudlák在其回复中建议的代码,它似乎有效,但“产量”操作总是从上一个产量中产生输出。这是威德

在盯着屏幕看了很久之后,我终于明白我需要把代码粘贴在这里。关键的区别在于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
导入控件。应用程序
进口管制
导入控制.Monad.IO.Class
导入控制.Monad.State.Class
导入控制.Monad.Trans.Class
进口管制。单体。无反式
导入数据。无效
类(单子m)=>自动类IOM | m->i,m->o其中
收益率::o->mi
数据自动输入输出a=自动输入输出(i->a)
实例函子(AutoF io)其中
fmap f(AutoF o nxt)=AutoF o$\i->f$nxt i
新类型自动输入法=自动输入法(自由输入法)
派生(函子、应用程序、单子、单子、单子Trans、单子状态s)
实例(Monad m)=>自动类IO(自动类IO m),其中
收益率v=自动$liftF$AutoF v id
runAutoT::(Monad m)=>AutoT i o m Void->m(o,i->AutoT i o m Void)
运行自动测试(自动测试步骤)=执行
f荒谬的v
免费(AutoF o nxt)->返回(o,AutoT.nxt)
--快速测试
--
-->运行测试启动
testStart::Int->AutoT Int IO Void
testStart x=do
liftIO$putStrLn$“我的状态为”++显示x

那是一台粉状机器。请参阅一组实例,但请注意,
Monad
实例总是很慢,因为Monad定律要求它对角化


听起来您真正想要的似乎是“无论如何”。

您可以按照管道的精神扩展您的自动机,也就是说,允许它退出并在有限多个输入上返回一个值:

data Auto i o a
    = Step (i -> (o, Auto i o a))
    | End a
然后,您可以使用
>=
定义一个连接两个自动机的monad实例:当第一个自动机完成时,第二个自动机继续

好消息是,您不需要自己实现它。使用函子返回值或嵌套正是所做的(请参阅)。那么让我们来定义

{-# 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)
这将自动为您提供
免费的所有实例
,您还可以定义原始功能:

-- | 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, ()))
请注意,使用相同的函子可以得到相应的monad transformer:

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, ()))

...

几乎正是我想要的,除了“yield”还为下一个状态转换接收新的输入,所以它应该是“yield::o->autoi”。所以大概是“收益率x=liftF(AutoF$\v->(x,v))”。不过,感谢你的深刻见解,免费单子是我所需要的。我以前读过关于它们的文章,但没有把它们与这个问题联系起来。当然,它显然是显而易见的。事实上,这将是正确的定义<代码>屈服>代码>你想要的语义。你也可以考虑使用更好的渐近性能。