2014-04-20 57 views
2

我想实现一堆可以在事件中交换的对象。在接收到上面或下面的事件表单后,一个对象可以将其他事件发送到以太网端或更改状态(但将其位置保留在堆栈中)。事件处理程序堆栈

此刻我有这个工作。我有一个类型Animation a b这是一个事件处理程序的容器,它接收(从上面)类型为a的事件并发出(up)b类型的事件,并且我有一个函数handle :: Handler -> Animation g h -> Animation e f来堆叠它们。

Handler实际类型是

Animation g h -> (Either e h) -> 
    WriterT [Either g f] IO (Either (Animation e f) (Animation g h)) 

这里(Either e h)是一个事件从上方或下方来,[Either g f]是事件发出向下或向上和Either (Animation e f) (Animation g h)是结果无论是作为使用了一个独立的对象或一个同一处理程序我不高兴。

有没有更好的方法来做到这一点?

+0

你'Animation'类型有点像我'pipes'或'conduit'库提供 – cdk

+0

@cdk,我不是好熟悉​​的,但似乎两者都对数据转换,而对于我来说慢跑,最重要的要求是每个'动画'能够取代它自己。 –

回答

6

这正是pipesProxy型号所做的。概略地,它看起来有点像这样:

Upstream | Downstream 
    +---------+ 
    |   | 
a' <==  <== b' 
    |   | 
a ==>  ==> b 
    | | | 
    +----|----+ 
      v 
      r 

它有两个接口:上游接口和下游接口。它在两个接口上发送和接收信息。这类似于堆栈中的一个层,其中“上游”可能是其上面的堆栈框架,“下游”可能是其下面的堆栈框架。

传达给上游接口,可以使用request,它具有这种类型的:

request :: Monad m => a' -> Proxy a' a b' b m a 

换句话说,request发送a'类型上游和等待的一个值a类型的响应。

双重的requestrespond,其下游接口上进行通信:

respond :: Monad m => b -> Proxy a' a b' b m b' 

respond发送b型下游并等待的值b'类型的响应。

A Proxy可以处于三种状态之一。它可以是:

  • 等待来自上游

它的类型的响应将指示它的等待a

​​
  • 等待来自下游的反应:

它的类型w乌尔德表明,它等待b'

waitingDn :: b' -> Proxy a' a b' b m r 
  • 目前活跃的,而不是等待什么:

它的类型将表明,它不会等待任何值:

notWaiting :: Proxy a' a b' b m r 

有四种方式可以连接这三种状态:

  • 将等待下游的Proxy连接到活动的Proxy,该活动产生新的活动Proxy

这是操作者(+>>)做什么:

(+>>) 
    :: Monad m 
    => (b' -> Proxy a' a b' b m r) -- Waiting on downstream 
    ->  Proxy b' b c' c m r -- Active 
    ->  Proxy a' a c' c m r -- Active 
  • 连接有源ProxyProxy等待上游,其生成新的活性Proxy

这是(>>~)运营商做什么:

(>>~) 
    :: Monad m 
    =>  Proxy a' a b' b m r -- Active 
    -> (b -> Proxy b' b c' c m r) -- Waiting on upstream 
    ->  Proxy a' a c' c m r -- Active 
  • 将两个Proxy S中的上游都在等待,生成一个新的Proxy等待上游。

这是(>~>)运营商做什么:

(>~>) 
    :: Monad m 
    => (a -> Proxy a' a b' b m r) -- Waiting on upstream 
    -> (b -> Proxy b' b c' c m r) -- Waiting on upstream 
    -> (a -> Proxy a' a c' c m r) -- Waiting on upstream 
  • 将两个Proxy S中的对下游都在等待,生成一个新的Proxy等待下游。

这是(>+>)运营商做什么:

(>+>) 
    :: Monad m 
    => (b' -> Proxy a' a b' b m r) -- Waiting on downstream 
    -> (c' -> Proxy b' b c' c m r) -- Waiting on downstream 
    -> (c' -> Proxy a' a c' c m r) -- Waiting on downstream 

这里的实施和连接该声道三分栈帧的例子。我将使用堆栈从上游开始的惯例,虽然实现是完全对称的,如果你喜欢,你可以使用相反的约定:

import Pipes.Core 
import Pipes 

--    +-+-- Closed upstream interface 
--    | | 
--    v v 
up ::() -> Proxy X() String Int IO() 
up() = do 
    str1 <- respond 4 
    lift (putStrLn str1) 
    str2 <- respond 5 
    lift (putStrLn str2) 

middle :: Int -> Proxy String Int Double Char IO() 
middle int = do 
    lift (print int) 
    double <- respond (head (show int)) 
    lift (print double) 
    int' <- request (show double) 
    middle int' 

-- Closed downstream interface --+-+ 
--        | | 
--        v v 
down :: Char -> Proxy Double Char() X IO() 
down char1 = do 
    lift (print char1) 
    char2 <- request (1.0) 
    lift (print char2) 
    char3 <- request (2.0) 
    lift (print char3) 

--     +-+--+--+-- Everything closed 
--     | | | | 
--     v v v v 
total ::() -> Proxy X()() X IO() 
total = up >~> middle >~> down 

main :: IO() 
main = runEffect $ total() 

这将产生以下的输出:

>>> main 
4 
'4' 
1.0 
1.0 
5 
'5' 
2.0 
2.0 

尝试手动追踪执行路径,从upProxy开始。每次有值时,将控制权交给middle,并且每次用middlerespond都有一个将控制权交给down的值。反之,每次downrequest是个值,即手拿开控制middle,每次middlerequest s表示手断控制up的值。如果链中的任何管道终止,则整个链终止。

编辑:要回答你的问题,是的,你可以根据结果改变行为。只要写middle这样的:

middle :: Int -> Proxy String Int Double Char IO() 
middle int = do 
    lift (print int) 
    double <- respond (head (show int)) 
    case double of 
     0.0 -> foo 
     _ -> bar 

...其中foobarProxy s的相同的输入和输出middle

foo :: Proxy String Int Double Char IO() 

bar :: Proxy String Int Double Char IO() 

当你连续两次Proxy秒,第二Proxy从第一个Proxy结束处开始。您不限于对原始命令进行排序,如requestrespond。只要共享相同的上游和下游接口,您可以将任意数量的步骤的任何Proxy作为较大的Proxy中的子例程调用。

+0

是否有可能以这种方式编写'middle',每当它从上游得到一定的响应时,就会用另一个'Proxy','foo'替换它自己,这样整个效果就相当于' up>〜> foo>〜> down'?它可以变成只是'up>〜> foo'?换句话说,这个例子非常适合消息传递,但是状态呢? –

+0

是的。我更新了我的答案,详细说明了如何做到这一点。简短的回答是:直接调用'foo'就可以了。 –