In my experience, the real-world benefits of upstream terminators are very slim, which is why they're hidden from the public API at this point. I think I only used them in one piece of code ever (wai-extra's multipart parsing).
In its most general form, a Pipe allows you to produce both a stream of output values and a final result. When you fuse that Pipe with another downstream Pipe, then that stream of output values becomes downstream's input stream, and upstream's final result becomes downstream's "upstream terminator." So from that perspective, having arbitrary upstream terminators allows for a symmetric API.
However, in practice, it's very rare that such functionality is actually used, and since it just confuses the API, it was hidden in the .Internal module with the 1.0 release. One theoretical use case could be the following:
- You have a Source which produces a stream of bytes.
- A Conduit which consumes a stream of bytes, calculates a hash as a final result, and passes on all of the bytes downstream.
- A Sink which consumes the stream of bytes, e.g., to store them in a file.
With upstream terminators, you could connect these three up and have the result from the Conduit returned as the final result of the pipeline. However, in most cases there's an alternate, simpler means to achieve the same goals. In this case, you could:
- Use
conduitFile
to store the bytes in a file and turn the hash Conduit into a hash Sink and place it downstream - Use zipSinks to merge both a hash sink and a file writing sink into a single sink.