Is it possible to make a function so that a Proxy
from pipes can be constructed inside-out? By inside-out, I mean create a proxy from a function that connects the upstream and downstream connections. The most desirable (but impossible) signature would be
makeProxy :: (Monad m) =>
(Server a' a m r -> Client b' b m r -> Effect m r) ->
Proxy a' a b' b m r
The first problem we encounter is the mechanical problem of constructing the Proxy
. There's no way for us to know if the function looks at the Server
or Client
except by having each of them be M
, in which case we'll only know which one it looked at, not what value it tried to send upstream or downstream. If we focus on the upstream end, the only thing we know is that something tried to figure out what the upstream proxy is, so we need to decide on either always resulting in a Request
farther upstream or Respond
ing. Either way we answer, the only value we can provide is ()
. This means we can make a Request ()
to an upstream producer or Respond ()
immediately. If we consider making this choice for both ends, there are only four possible functions. The following functions are named after whether their upstream and downstream connections send interesting data downstream (D
) or upstream (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
is the most interesting, it would build a pipe between a Producer
and a Consumer
; betweenUU
would do the same for a pipe running upstream. betweenDU
would consume data requesting it from one of two sources. betweenUD
would produce data, sending it to one of two destinations.
Can we provide a definition for betweenDD
? If not, can we instead provide definitions for the following simpler functions?
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
This question was motivated by trying to write belowD
to use in answering a question about P.zipWith
.
Example
This example happens to be essentially the question that inspired this question..
Let's say we want to create a Pipe
that will number
the values passing through it. The Pipe
will have values a
coming downstream from above and values (n, a)
leaving downstream below; in other words it will be a Pipe a (n, a)
.
We'll solve this problem by zip
ping with the numbers. The result of zip
ing with the numbers is a function (->)
from a Producer a
to 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..])
Even though the Pipe
will consume a
s from upstream, from the point of view of the function it needs a Producer
of a
s to provide those values. If we had a definition for belowD
we could write
number :: (Monad m, Num n, Enum n) => Pipe a (n, a) m ()
number = belowD (P.zip (fromList [1..]))
given a suitable definition for fromList
fromList :: (Monad m) => [a] -> Producer a m ()
fromList [] = return ()
fromList (x:xs) = do
yield x
fromList xs
Actually, I think
makeProxy
is possible if you slightly change the type. I am on my phone so I cannot type check this just yet, but I believe this works:This assumes that
k
is defined as:Edit: Yeah, it works if you add an import for
lift
I'll walk through why this works.
First, let me set out some of the
pipes
definitions and laws:Now let's use those equations to expand out
up
anddn
:In other words,
up
converts allrequest
s going out ofk
's upstream interface intolift . request
anddn
converts allrespond
s going out ofk
's downstream interface intolift . respond
. In fact, we can prove that:... and if we apply those equations to
k
, we get:This says the same thing except more directly: we're replacing every
request
ink
withlift . request
and replacing everyrespond
ink
withlift . respond
.Once we lower all
request
s andrespond
s to the base monad, we end up with this type:Now we can delete the outer
Effect
usingrunEffect
. This leaves behind the "inside-out"Proxy
.This is also the same trick that
Pipes.Lift.distribute
uses to swap the order of theProxy
monad with the monad underneath it:http://hackage.haskell.org/package/pipes-4.1.4/docs/src/Pipes-Lift.html#distribute