事件处理程序栈

时间:2014-04-20 17:45:22

标签: events haskell

我想实现一堆可以在事件中交换的对象。在接收到上方或下方的事件表单时,对象可以向Ether侧发出其他事件或更改状态(但保留其在堆栈中的位置)。

目前我有这个工作。我有一个Animation a b类型,它是事件处理程序的容器,它接收(从上面)类型为a的事件并发出类型为b的事件,并且我有一个函数{{ 1}}堆叠它们。

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]是作为独立对象或使用相同处理程序的结果。我并不开心。

有更优雅的方法吗?

1 个答案:

答案 0 :(得分:6)

这正是来自Proxy的{​​{1}}类型。从图形上看,它看起来有点像这样:

pipes

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

要与上游接口通信,请使用 Upstream | Downstream +---------+ | | a' <== <== b' | | a ==> ==> b | | | +----|----+ v r ,其类型为:

request

换句话说,request :: Monad m => a' -> Proxy a' a b' b m a 在上游发送request类型的值,并等待a'类型的响应。

a的对偶是request,它在下游接口上进行通信:

respond

respond :: Monad m => b -> Proxy a' a b' b m b' 向下游发送respond类型的值,并等待b类型的响应。

b'可以处于三种状态之一。它可以是:

  • 等待上游的回复

它的类型表示它正在等待Proxy

a
  • 等待下游的回复:

它的类型表明它正在等待waitingUp :: a -> Proxy a' a b' b m r

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 连接到等待上游的Proxy,这会生成新的有效Proxy

这是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

这是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

这是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 up开始手动跟踪执行路径。每次Proxy up带有一个值时,就会将控制权交给respond,每次middle middle都有一个值将控制权移交给respond down。反之亦然,每次down request sa值,将控制权交给middle,每次middle request值将控制权交给{ {1}}。如果链中的任何管道终止,则整个链终止。

编辑:要回答您的问题,是的,您可以根据结果更改行为。只需像这样写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 foo都是bar,其输入和输出与Proxy相同:

middle

当您对两个foo :: Proxy String Int Double Char IO () bar :: Proxy String Int Double Char IO () 进行排序时,第二个Proxy从第一个Proxy开始的位置开始。您不仅限于对基本命令进行排序,例如Proxyrequest。只要它共享相同的上游和下游接口,您就可以调用任意数量的任何respond步骤作为较大Proxy内的子例程。