Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell Pipes - get return value of last Proxy in pipeline

Let's say I have two Proxy in Haskell Pipes. They represent external system processes.

produce :: MonadIO m => Producer ByteString m ExitCode
consume :: MonadIO m => Consumer ByteString m ExitCode

So I hook them into an Effect, like this:

effect :: Effect m ExitCode
effect = produce >-> consume

This Effect is going to give me the ExitCode from the first Proxy that terminates. Ordinarily this will be the produce, not the consume. What's the idiomatic Pipes way to get the return value of the consume even if it does not terminate first?

So far I am thinking this is not possible without doing some sort of icky in-band signaling so the consume knows the stream is done. The only way the last Proxy knows to shut down is by getting something from an await, so I could send it an empty ByteString to signal that the stream is done. That just doesn't feel right though. What I've got now is a separate MVar that can provide the exit value but I'm thinking there has to be a more idiomatic way to do this.

like image 727
Omari Norman Avatar asked Mar 27 '15 21:03

Omari Norman


2 Answers

Without in-band signaling it will never be possible for the Consumer to have a "return value" if the Producer returns first. If the producer is returning that means the Consumer must be blocked waiting for a requested value. The Consumer will never run again, and thus never have an opportunity to return, until the Consumer gets an in-band signal with the requested value.

Just because signaling is in-band doesn't mean it needs to be "icky". We can convert a Producer that might return into a Producer that we know doesn't return (it's return type is forall r' . r') by capturing the return and forwarding it downstream. We do this forever in case another request comes back upstream.

returnDownstream :: Monad m => Proxy a' a b' b m r -> Proxy a' a b' (Either r b) m r'
returnDownstream = (forever . respond . Left =<<) . (respond . Right <\\)

At the Consumer end you need to explicitly handle what to do when a value is requested but instead of getting the response (in a Right) you get the return value of the upstream producer (in a Left).

like image 124
Cirdec Avatar answered Sep 22 '22 11:09

Cirdec


Thanks. What I have come up with is something like

produce :: MonadIO m => Producer (Either ExitCode ByteString) m r
consume :: MonadIO m => Consumer (Either ExitCode ByteString) m (Maybe ExitCode, ExitCode)

so that when the Effect runs, I get a (Nothing, code) if the downstream process terminated, or (Just code1, code2) if the upstream process terminated first. (If the downstream terminates first, there's nothing left to do with the upstream process but terminate it, so providing an exit code doesn't make any sense.)

like image 22
Omari Norman Avatar answered Sep 20 '22 11:09

Omari Norman