从内部构建一个管道代理

3

是否可能创建一个函数,以便可以从pipes内部向外构建Proxy?所谓内部向外,是指通过连接上游和下游连接的函数创建代理。最理想的(但不可能的)签名是:

makeProxy :: (Monad m) =>
             (Server a' a m r -> Client b' b m r -> Effect m r) ->
              Proxy  a' a               b' b               m r

我们遇到的第一个问题是构建代理的机械问题。除非我们将ServerClient都设置为M,否则我们无法知道函数是否查看了哪个。即使这样做,我们也只能知道它查看了哪一个,而不知道它试图向上游或下游发送什么值。如果我们关注上游端,唯一知道的是某些东西试图找出上游代理是什么,因此我们需要决定始终导致更远的上游RequestRespond。无论我们如何回答,我们只能提供()。这意味着我们可以向上游生产者发出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

这个问题的动机是想要编写belowD,以用于回答关于P.zipWith的问题

示例

这个示例实际上基本上是启发这个问题的问题

假设我们想要创建一个Pipe,用于给通过它的值进行编号。 Pipe将具有从上游传下来的值a和从下游传出的值(n,a);换句话说,它将是一个Pipe a (n, a)

我们将通过与数字进行zip解决此问题。 与数字进行zip的结果是一个从Producer aProducer (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,但从函数的角度来看,它需要一个 aProducer 来提供这些值。如果我们有 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
2个回答

2

抱歉,我睡眼惺忪错过了一些括号,所以第一个答案是针对不同问题的。

Producer' a m r -> Producer' b m rPipe 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

这个代码期望每个a都有一个或多个b。如果g需要多个a才能生成一个b,它将不会产生任何输出。
但是由于Proxy a b c d m是一个Monad,我们可以使用lift函数来操作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

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

现在让我们使用这些方程来扩展updn:
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将从k的上游接口发出的所有request转换为lift . request,而dn将从k的下游接口发出的所有respond转换为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

这句话直接明了:我们将k中的每个request替换为lift . request,并将k中的每个respond替换为lift . respond

一旦我们将所有的requestrespond降到基本单子上,我们最终得到的类型是:

lift . request \>\ k />/ lift . respond :: Effect' (Proxy a' a b' b m) r

现在我们可以使用runEffect删除外部的Effect。这样就留下了“内向外”的Proxy
这也是Pipes.Lift.distribute使用的相同技巧,用于交换Proxy monad与其下面的monad的顺序: http://hackage.haskell.org/package/pipes-4.1.4/docs/src/Pipes-Lift.html#distribute

这涵盖了我所谓的 betweenDU 情况,其中所有有趣的数据都在流动。 betweenDU f = makeProxy g where g fa' fb = f (fa' ()) (fb ()) - Cirdec
@Cirdec,你无法满足makeProxy的原始签名,因为ServerClient都缺少一个参数。在组合的Proxy管道中,除了(最多)一个之外的每个Proxy都必须带有一个参数(类型强制执行此操作)。你的ServerClient都没有参数,这意味着它们通常是不可能连接的。 - Gabriella Gonzalez

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接