这正是pipes
的Proxy
型号所做的。概略地,它看起来有点像这样:
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'
类型的响应。
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
- 连接有源
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
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
尝试手动追踪执行路径,从up
Proxy
开始。每次有值时,将控制权交给middle
,并且每次用middle
respond
都有一个将控制权交给down
的值。反之,每次down
request
是个值,即手拿开控制middle
,每次middle
request
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
...其中foo
和bar
都Proxy
s的相同的输入和输出middle
:
foo :: Proxy String Int Double Char IO()
bar :: Proxy String Int Double Char IO()
当你连续两次Proxy
秒,第二Proxy
从第一个Proxy
结束处开始。您不限于对原始命令进行排序,如request
和respond
。只要共享相同的上游和下游接口,您可以将任意数量的步骤的任何Proxy
作为较大的Proxy
中的子例程调用。
你'Animation'类型有点像我'pipes'或'conduit'库提供 – cdk
@cdk,我不是好熟悉的,但似乎两者都对数据转换,而对于我来说慢跑,最重要的要求是每个'动画'能够取代它自己。 –