事件处理程序栈

Kar*_*elė 2 events haskell

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

目前我有这个工作.我有一个类型Animation a b,它是事件处理程序的容器,它接收(从上面)类型的事件a并发出(向上)类型的事件,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))
Run Code Online (Sandbox Code Playgroud)

以下(Either e h)是来自上方或下方[Either g f]的事件,是向下或向上发出的事件,并且Either (Animation e f) (Animation g h)是作为独立对象或使用相同处理程序的结果.而且我不高兴.

有没有更优雅的方式来做到这一点?

Gab*_*lez 6

这正是Proxy类型的pipes作用.从图形上看,它看起来有点像这样:

 Upstream | Downstream
     +---------+
     |         |
 a' <==       <== b'
     |         |
 a  ==>       ==> b
     |    |    |
     +----|----+
          v
          r
Run Code Online (Sandbox Code Playgroud)

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

要与上游接口通信,请使用request具有以下类型的上游接口:

request :: Monad m => a' -> Proxy a' a b' b m a
Run Code Online (Sandbox Code Playgroud)

换句话说,request发送a'上游类型的值并等待类型的响应a.

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

respond :: Monad m => b -> Proxy a' a b' b m b'
Run Code Online (Sandbox Code Playgroud)

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

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

  • 等待来自上游的回应

它的类型表明它正在等待a:

waitingUp :: a -> Proxy a' a b' b m r
Run Code Online (Sandbox Code Playgroud)
  • 等待下游的回复:

它的类型表明它正在等待b':

waitingDn :: b' -> Proxy a' a b' b m r
Run Code Online (Sandbox Code Playgroud)
  • 目前活跃,而不是等待任何事情:

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

notWaiting :: Proxy a' a b' b m r
Run Code Online (Sandbox Code Playgroud)

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

  • 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
Run Code Online (Sandbox Code Playgroud)
  • 将活动连接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
Run Code Online (Sandbox Code Playgroud)
  • 连接两个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
Run Code Online (Sandbox Code Playgroud)
  • 连接两个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
Run Code Online (Sandbox Code Playgroud)

这是以这种方式实现和连接的三个堆栈帧的示例.我将使用堆栈从上游开始的约定,尽管实现是完全对称的,如果您愿意,可以使用相反的约定:

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 ()
Run Code Online (Sandbox Code Playgroud)

这会产生以下输出:

>>> main
4
'4'
1.0
1.0
5
'5'
2.0
2.0
Run Code Online (Sandbox Code Playgroud)

尝试从手工跟踪执行路径up Proxy.每次up respond带有一个值的s,都会将控制权移交给middle每一次,并且每次middle respond都有一个值可以控制的值down.反之亦然,每次down requestsa值,即手动控制middle,以及每次middle requestsa值控制权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
Run Code Online (Sandbox Code Playgroud)

...在哪里foobar哪些Proxys具有相同的输入和输出middle:

foo :: Proxy String Int Double Char IO ()

bar :: Proxy String Int Double Char IO ()
Run Code Online (Sandbox Code Playgroud)

当你对两个Proxys进行排序时,第二个Proxy从第一个Proxy结束的地方开始.您不仅限于对request和等原语命令进行排序respond.只要它共享相同的上游和下游接口,您就可以Proxy将任意数量的步骤调用为更大的子程序Proxy.