是否可以创建一个函数,以便可以从里到外构建pipes来自question about P.zipWith
?从内到外,我的意思是从连接上游和下游连接的函数创建代理。最理想(但不可能)的签名是
Proxy
我们遇到的第一个问题是构造makeProxy :: (Monad m) =>
(Server a' a m r -> Client b' b m r -> Effect m r) ->
Proxy a' a b' b m r
的机械问题。我们无法知道该函数是在查看Proxy
还是Server
,除非每个函数都是Client
,在这种情况下我们只会知道它看到的是哪一个,而不是它试图向上游或下游发送的价值。如果我们专注于上游端,我们唯一知道的是有些东西试图找出上游代理是什么,所以我们需要决定总是导致M
更远的上游或Request
ING。无论我们回答哪种方式,我们唯一可以提供的价值是Respond
。这意味着我们可以立即向上游生产者或()
建立Request ()
。如果我们考虑为两端做出这种选择,那么只有四种可能的功能。以下函数以其上游和下游连接是向下游(Respond ()
)还是向上游(D
)发送有趣数据来命名。
U
betweenDD :: (Monad m) =>
(Server () a m r -> Client () b m r -> Effect m r) ->
Proxy () a () b m r
betweenDD = undefined
betweenDU :: (Monad m) =>
(Server () a m r -> Client b' () m r -> Effect m r) ->
Proxy () a b' () m r
betweenDU = undefined
betweenUU :: (Monad m) =>
(Server a' () m r -> Client b' () m r -> Effect m r) ->
Proxy a' () b' () m r
betweenUU f = reflect (betweenDD g)
where g source sink = f (reflect sink) (reflect source)
betweenUD :: (Monad m) =>
(Server a' () m r -> Client () b m r -> Effect m r) ->
Proxy a' () () b m r
betweenUD = undefined
是最有趣的,它会在betweenDD
和Producer
之间建立一个管道;对于上游运行的管道,Consumer
也会这样做。 betweenUU
将使用从两个来源之一请求数据的数据。 betweenDU
将生成数据,并将其发送到两个目的地之一。
我们可以为betweenUD
提供定义吗?如果没有,我们可以为以下更简单的函数提供定义吗?
betweenDD
这个问题的动机是试图写belowD :: (Monad m) =>
(Producer a m r -> Producer b m r) ->
Proxy () a () b m r
aboveD :: (Monad m) =>
(Consumer b m r -> Consumer a m r) ->
Proxy () a () b m r
来回答the question that inspired this question.。
这个例子恰好是{{3}}。
假设我们要创建belowD
,Pipe
将number
通过它的值。 Pipe
的值为a
,其上方为(n, a)
,值为Pipe a (n, a)
,位于下方;换句话说,它将是zip
。
我们将通过zip
ping来解决这个问题。 (->)
数字Producer a
的结果是从Producer (n, a)
到import qualified Pipes.Prelude as P
number' :: (Monad m, Num n, Enum n) => Producer a m () -> Producer (n, a) m ()
number' = P.zip (fromList [1..])
的函数Pipe
。
a
即使Producer
将从上游消耗a
,但从函数的角度来看,它需要belowD
number :: (Monad m, Num n, Enum n) => Pipe a (n, a) m ()
number = belowD (P.zip (fromList [1..]))
来提供这些值。如果我们有fromList
的定义,我们可以写
fromList :: (Monad m) => [a] -> Producer a m ()
fromList [] = return ()
fromList (x:xs) = do
yield x
fromList xs
给出了{{1}}
的合适定义{{1}}
答案 0 :(得分:2)
(对不起,我在一个昏昏欲睡的头上错过了几个括号,所以第一个答案是另一个问题)
Producer' a m r -> Producer' b m r
是Pipe a b m r
的定义 - 它可以使用a
并生成b
。
belowD ::Monad m => (Producer' a m () -> Producer' b m r) -> Pipe a b m ()
belowD g = sequence_ $ repeat $ do
x <- await -- wait for `a` as a Pipe
g $ yield x -- pass a trivial Producer to g, and forward output
每个b
预计会有一个或多个a
。如果g
需要多个a
来生成一个b
,则不会生成任何内容。
但是,由于Proxy a b c d m
是Monad
,我们可以解除await
:
belowD :: Monad m => (forall m . Monad m => Producer a m () -> Producer b m r) ->
Pipe a b m r
belowD g = h . g $ sequence_ $ repeat ((lift $ await) >>= yield) where
h :: Monad m => Producer b (Pipe a b m) r -> Pipe a b m r
h p = do
x <- next p
case x of
Left r -> return r
Right (x,p) -> do
yield x
h p
h :: Monad m => Producer a m () -> Producer a m ()
h :: Monad m => Producer a m () -> Producer a m ()
h p = p >-> (sequence_ $ repeat $ await >>= yield >> await) -- skips even
main = runEffect $ (mapM_ yield [1..10]) >-> (for (belowD h) $ lift . print)
> 1
> 3
> 5
> 7
> 9
答案 1 :(得分:2)
实际上,如果您略微更改类型,我认为makeProxy
是可行的。我在手机上,所以我还不能打字检查,但我相信这有效:
{-# LANGUAGE RankNTypes #-}
import Control.Monad.Trans.Class (lift)
import Pipes.Core
makeProxy
:: Monad m
=> ( forall n. Monad n
=> (a' -> Server a' a n r)
-> (b -> Client b' b n r)
-> Effect n r
)
-> Proxy a' a b' b m r
makeProxy k = runEffect (k up dn)
where
up = lift . request \>\ pull
dn = push />/ lift . respond
这假设k
被定义为:
k up dn = up ->> k >>~ dn
编辑:是的,如果您为lift
我将了解其工作原理。
首先,让我列出一些pipes
定义和法律:
-- Definition of `push` and `pull`
(1) pull = request >=> push
(2) push = respond >=> pull
-- Read this as: f * (g + h) = (f * g) + (f * h)
(3) f \>\ (g >=> h) = (f \>\ g) >=> (f \>\ h)
-- Read this as: (g + h) * f = (g * f) + (h * f)
(4) (g >=> h) />/ f = (g />/ f) >=> (h />/ f)
-- Right identity law for the request category
(5) f \>\ request = f
-- Left identity law for the respond category
(6) respond />/ f = f
-- Free theorems (equations you can prove from the types alone!)
(7) f \>\ respond = respond
(8) request />/ f = request
现在让我们使用这些公式展开up
和dn
:
up = (lift . request) \>\ pull
= (lift . request) \>\ (request >=> push) -- Equation (1)
= (lift . request \>\ request) >=> (lift . request \>\ push) -- Equation (3)
= lift . request >=> (lift . request \>\ push) -- Equation (5)
= lift . request >=> (lift . request \>\ (respond >=> pull)) -- Equation (2)
= lift . request >=> (lift . request \>\ respond) >=> (lift . request \>\ pull) -- Equation (3)
= lift . request >=> respond >=> (lift . request \>\ pull) -- Equation (7)
up = lift . request >=> respond >=> up
-- Same steps, except symmetric
dn = lift . respond >=> request >=> dn
换句话说,up
将request
上游界面中的所有k
转换为lift . request
,dn
转换所有respond
{1}}从k
的下游接口进入lift . respond
。事实上,我们可以证明:
(9) (f \>\ pull) ->> p = f \>\ p
(10) p >>~ (push />/ f) = p />/ f
...如果我们将这些方程应用于k
,我们得到:
(lift . request \>\ pull) ->> k >>~ (push />/ lift . respond)
= lift . request \>\ k />/ lift . respond
除了更直接地说明了同样的事情:我们用request
替换k
中的每个lift . request
并替换respond
中的每个k
与lift . respond
。
一旦我们将所有request
和respond
降低到基本monad,我们就会得到以下类型:
lift . request \>\ k />/ lift . respond :: Effect' (Proxy a' a b' b m) r
现在我们可以使用Effect
删除外部runEffect
。这留下了&#34;由内到外&#34; Proxy
。
这也是Pipes.Lift.distribute
用来交换Proxy
monad与其下方monad的顺序相同的技巧:
http://hackage.haskell.org/package/pipes-4.1.4/docs/src/Pipes-Lift.html#distribute