我想实现一堆可以在事件中交换的对象。在接收到上方或下方的事件表单时,对象可以向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]
是作为独立对象或使用相同处理程序的结果。我并不开心。
有更优雅的方法吗?
答案 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
开始的位置开始。您不仅限于对基本命令进行排序,例如Proxy
和request
。只要它共享相同的上游和下游接口,您就可以调用任意数量的任何respond
步骤作为较大Proxy
内的子例程。