Published: 2012-04-04T19:35Z

In the pipes library, the type of the composition operator is

(>+>) :: Pipe m a b r -> Pipe m b c r -> Pipe m a c r

If you look closely, then you will notice that all three pipes have result type `r`.
How does this work? Simple: whichever pipe stops first provides the final result.

In my opinion this is wrong. The upstream pipe produces values, and the downstream pipe does something with them. The downstream pipe is the one that leads the computation, by pulling results from the upstream pipe. It is therefore always the downstream pipe that should provide the result. So, in the pipification of conduit, the proposed type for composition is instead

(>+>) :: Pipe m a b () -> Pipe m b c r -> Pipe m a c r

This makes it clear that the result of the first pipe is not used, the result of the composition always has to come from downstream. But now the result of the first pipe would be discarded completely.

Another, more general, solution is to communicate the result of the first pipe to the second one.
That would give the `await` function in the downstream pipe the type

await :: Pipe m a b (Either r_{1}a)

where `r _{1}` is the result of the upstream pipe.
Of course that

data Pipe m stream_{in}stream_{out}final_{in}final_{out}

giving await the type

await :: Pipe m a b x (Either x a)

Composition becomes

(>+>) :: Pipe m a b x y -> Pipe m b c y z -> Pipe m a c x z

I think this makes `Pipe` into a category over *pairs* of Haskell types. I was tempted to call this a bicategory, in analogy with bifunctor, but that term apparently means something else.

Note that this article is just about a quick idea I had. I am not saying that this is the best way to do things. In fact, I am not even sure if propagating result values in this way actually helps solve any real world problems.

## Comments

One obvious category of pairs of Haskell types is the product category. Then the arrows are also pairs of arrows. Maybe a pipe can be a tuple of yield/await/effect on the left and early-close/done on the right?

Sjoerd: if you use a product category, then it becomes impossible to have pipes that close early based on awaited input. If you ignore effects, then pipes are functions between lists with a value at the end.

So

Pipe a b x y ≈ (Stream a x -> Stream b y).Ok. But, should pipes know how to convert

x's intoy's? Or do we havePipe a b x y ≈ (Stream a x -> Stream b (Either x y))?I think that the pipe itself should know how to convert

xintoy. That's also how it works right now in conduit, witha=(). You would have, for instanceThe user of

awaitwould have to know what to do in theRightcase.The last case also illustrates that this implementation is not good enough if you want to use

Pipeas a monad. Then you would also need to keep track of the left-over input somehow.In pipes-core, the await function is made more convenient by adding an error monad on top of pipes, so

awaitwould throw an exception when there is no more input. You could easily do that yourself withErrorT x (Pipe a b x) y.## Reply