2015-07-11 110 views
3

屈服/等待功能延续单子我想创建一个类型像这样的自动机类型:在哈斯克尔

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 

所以,当我打电话从内自动单子的“产量” “runAuto”函数返回一个由“yield”参数和continuation函数组成的对。当应用程序调用continuation函数时,参数将作为“yield”的结果在monad中返回。

我知道这将需要一些延续单子的味道,但尽管过去我已经与延续争执,我看不出如何编码这一个。

我也知道这与Michael Snoyman的Conduit monad非常相似,只不过他分裂了“收益”和“等待”。这个monad每个输入都必须有一个输出。

背景:我正在编写一些以复杂的方式响应GUI事件的代码。我希望能够编写接受一系列输入的代码,以便随着用户交互的进行产生更新屏幕,而不是将其转换为手动编码的状态机。

编辑

这一切都被证明是错误的巧妙。我写了PetrPudlá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 

回答

2

你可以扩展你的自动机在Conduit的精神,也就是让它退出和有限多输入返回值:

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

然后你可以定义使用连接两个自动单子实例>>=:第一个完成时,第二个继续。

好消息是你不需要自己实现它。使用函数返回值或嵌套正是free monad所做的(请参见its 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 @[email protected] 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相同的函子,您将得到相应的monad变换器:

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

... 
+0

几乎正是我想要的,除了“yield”还需要在下一个状态转换的新输入,所以它应该是“yield :: o - > Auto i o i”。所以推测是“yield x = liftF(AutoF $ \ v - >(x,v))”。不过,感谢大家深入了解免费monads是我所需要的。过去我已经阅读过关于它们的内容,但没有将它们与这个特定的问题联系起来。当然,现在它非常明显。 –

+0

确实,这将是你想要的语义的“yield”的正确定义。 –

+1

你也可以考虑使用['Codensity'](https://hackage.haskell.org/package/kan-extensions-4.2.2/docs/Control-Monad-Codensity.html)来获得更好的渐进性能。 –