3

是否可以创建一个函数,以便可以从内到外构造一个Proxyfrom管道?由内而外,我的意思是从连接上游和下游连接的函数创建代理。最理想(但不可能)的签名是

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上游更远,要么Responding。无论我们如何回答,我们唯一能提供的价值是(). 这意味着我们可以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最有趣的是,它会在 aProducer和 a之间建立一个管道ConsumerbetweenUU将对上游运行的管道执行相同的操作。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.

例子

这个例子恰好是激发这个问题的问题。.

假设我们要创建一个Pipenumber通过它的值。将Pipe具有a从上方传到下游的值和从(n, a)下方离开下游的值;换句话说,它将是一个Pipe a (n, a).

我们将通过zipping 数字来解决这个问题。zip用数字 ing的结果是一个(->)从 aProducer a到 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会从上游消耗as,但从函数的角度来看,它需要 a Producerof as 来提供这些值。如果我们有一个定义,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
4

2 回答 2

2

(抱歉,我在昏昏欲睡的时候错过了几个括号,所以第一个答案是另一个问题)

Producer' a m r -> Producer' b m r是a的定义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是 a 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
于 2014-12-09T00:40:17.127 回答
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把所有从上游接口request出来的s转换成,把从下游接口出来的所有s转换成。事实上,我们可以证明:klift . requestdnrespondklift . 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

除了更直接之外,这说明了同样的事情:我们将 every requestink替换为并将 every inlift . request替换为。respondklift . respond

一旦我们将所有requests 和responds 降低到基本 monad,我们最终会得到这种类型:

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

现在我们可以Effect使用runEffect. 这留下了“由内而外” Proxy

这也是用于将monad 与其下面的 monadPipes.Lift.distribute交换顺序的技巧:Proxy

http://hackage.haskell.org/package/pipes-4.1.4/docs/src/Pipes-Lift.html#distribute

于 2014-12-09T01:52:54.460 回答