从内到外构造管道代理

时间:2014-12-08 21:21:29

标签: haskell haskell-pipes

是否可以创建一个函数,以便可以从里到外构建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 是最有趣的,它会在betweenDDProducer之间建立一个管道;对于上游运行的管道,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}}。

假设我们要创建belowDPipenumber通过它的值。 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}}

2 个答案:

答案 0 :(得分: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

每个b预计会有一个或多个a。如果g需要多个a来生成一个b,则不会生成任何内容。


但是,由于Proxy a b c d mMonad,我们可以解除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

现在让我们使用这些公式展开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

换句话说,uprequest上游界面中的所有k转换为lift . requestdn转换所有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中的每个klift . respond

一旦我们将所有requestrespond降低到基本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