I'd like to hear from someone with a deeper understanding than myself what the fundamental differences are between Enumerators, Conduits, and Pipes as well as the key benefits and drawbacks. Some discussion's already ongoing but it'd be nice to have a high-level overview.
相关问题
- Understanding do notation for simple Reader monad:
- Making Custom Instances of PersistBackend
- Haskell: What is the differrence between `Num [a]
- applying a list to an entered function to check fo
- Haskell split a list into two by a pivot value
相关文章
- Is it possible to write pattern-matched functions
- Haskell underscore vs. explicit variable
- Top-level expression evaluation at compile time
- Stuck in the State Monad
- foldr vs foldr1 usage in Haskell
- List of checkboxes with digestive-functors
- How does this list comprehension over the inits of
- Replacing => in place of -> in function type signa
Enumerators/Iteratees as an abstraction were invented by Oleg Kiselyov. They provide a clean way of doing IO with predictable (low) resource requirements. The current Enumerators package is pretty close to Oleg's original work.
Conduits were created for the Yesod web framework. My understanding is that they were designed to be blazingly fast. Early versions of the library were highly stateful.
Pipes focus on elegance. They have just one type instead of several, form monad (transformer) and category instances, and are very "functional" in design.
If you like categorical explanations: the
Pipe
type is just the free monad over the following ungodly simple functorIn the actual pipe definition these are baked in, but the simplicity of this definition is amazing. Pipes form a category under the operation
(<+<) :: Monad m => Pipe c d m r -> Pipe a b m r -> Pipe a d m r
which takes whatever the first pipeyields
and feeds it to the awaiting second pipe.It looks like
Conduits
is moving to be morePipe
like (using CPS instead of state, and switching to a single type) while Pipes are gaining support for better error handling, and perhaps the return of separate types for generators and consumers.This area is moving quickly. I've been hacking on an experimental variant of the Pipe library with these features, and know other people are as well (see the Guarded Pipes package on Hackage), but suspect that Gabriel (the author of Pipes) will figure them out before I do.
My recommendations: if you are using Yesod, use Conduits. If you want a mature library use Enumerator. If you primarily care about elegance, use Pipe.
After writing applications with all three libraries, I think the biggest difference I've seen is in how resource finalization is handled. For example, Pipes breaks resource finalization out into separate types of Frames and Stacks.
There also still seems to be some debate about how to not only finalize the input resource, but also potentially the output resource. For example, if you're reading from a DB and writing to a file, the connection for the DB needs to be closed as well as the output file needing to be flushed and closed. Things get hairy when deciding how to handle exceptions and failure cases along the pipeline.
Another more subtle difference seems to be how the return value of the enumerator pipeline is handled and computed.
A lot of these differences and potential inconsistencies have been exposed by the use of the Monad and Category implementations for Pipes and now are making their way into Conduits.