Question

I'm working with pipes-4.0.0. In that library, the number of yields to downstream a pipe makes is in general unrelated to the number of awaits from upstream.

But suppose I wanted to build a restricted pipe that enforced that one and only one yield is performed for each await, while still being able to sequence these kinds of pipes using monadic (>>=).

I have observed that, in the bidirectional case, each value requested from upstream by a Proxy is matched with a value sent back. So maybe what I'm searching for is a function of type Proxy a' a () b m r -> Pipe a (Either b a') m r that "reflects" the values going upstream, turning them into additional yields to downstream. Or, less generally, Client a' a -> Pipe a a'. Is such a function possible?

Was it helpful?

Solution

You definitely do not want to use pipes for this. But, what you can do is define a restricted type that does this, do all your connections and logic within that restricted type, then promote it to a Pipe when you are done.

The type in question that you want is this, which is similar to the netwire Wire:

{-# LANGUAGE DeriveFunctor #-}

import Control.Monad.Trans.Free  -- from the 'free' package

data WireF a b x = Pass (a -> (b, x)) deriving (Functor)

type Wire a b = FreeT (WireF a b)

That's automatically a monad and a monad transformer since it is implemented in terms of FreeT. Then you can implement this convenient operation:

pass :: (Monad m) => (a -> b) -> Wire a b m ()
pass f = liftF $ Pass (\a -> (f a, ()))

... and assemble custom wires using monadic syntax:

example :: Wire Int Int IO ()
example = do
    pass (+ 1)
    lift $ putStrLn "Hi!"
    pass (* 2)

Then when you're done connecting things with this restricted Wire type you can promote it to a Pipe:

promote :: (Monad m) => Wire a b m r -> Pipe a b m r
promote w = do
    x <- lift $ runFreeT w
    case x of
        Pure r -> return r
        Free (Pass f) -> do
            a <- await
            let (b, w') = f a
            yield b
            promote w'

Note that you can define an identity and wire and wire composition:

idWire :: (Monad m) => Wire a a m r
idWire = forever $ pass id

(>+>) :: (Monad m) => Wire a b m r -> Wire b c m r -> Wire a c m r
w1 >+> w2 = FreeT $ do
    x <- runFreeT w2
    case x of
        Pure       r   -> return (Pure r)
        Free (Pass f2) -> do
            y <- runFreeT w1
            case y of
                Pure       r   -> return (Pure r)
                Free (Pass f1) -> return $ Free $ Pass $ \a ->
                        let (b, w1') = f1 a
                            (c, w2') = f2 b
                        in  (c, w1' >+> w2')

I'm pretty sure those form a Category:

idWire >+> w = w

w >+> idWire = w

(w1 >+> w2) >+> w3 = w1 >+> (w2 >+> w3)

Also, I'm pretty sure that promote obeys the following functor laws:

promote idWire = cat

promote (w1 >+> w2) = promote w1 >-> promote w2

OTHER TIPS

My gut feeling is that this is going to be very hard to do, if not outright impossible. Not only can you write producers and consumers full of complex loops but the monadic interface mans that the control flow of the consumer can depend on the values it gets from the producer.

consumer = do
  n <- await
  for i in 1..n do
     m <- await
     print m

Its going to be very hard to encode on the types of the producer that "this produces N + 1 numbers where N is the value of the first number yielded".


Going back on topic, I think you might have a better chance if you use your own combinators instead of the base monadic interface for pipes. For example, the boomerang web routes library uses a set of combinators to simultaneously build the code that does the (Route -> URL) conversion and teh code that does the (URL -> Route) conversion, thus guaranteeing that they are compatible and inverses of each other.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top