Search code examples
haskellhaskell-pipes

Is a one-yield-per-await restricted pipe possible?


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?


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