I'm announcing pipes-4.0
which greatly simplifies the types and API of the pipes
ecosystem. For people new to pipes
, pipes
is a compositional streaming library that decouples streaming programs into simpler, reusable components.
The purpose behind pipes
is to simplify effectful stream programming for both library authors and application writers. Library authors can package streaming components into a highly reusable interface, and application writers can easily connect together streaming components to build correct, efficient, and low-memory streaming programs. For example, the following program connects three reusable components to stream lines from standard input to standard output until the user types "quit"
:
import Pipes import qualified Pipes.Prelude as P main = runEffect $ P.stdinLn >-> P.takeWhile (/= "quit") >-> P.stdoutLn
pipes
distinguishes itself from other stream programming libraries in three main ways:
- Insistence on elegance, symmetry, and theoretical purity
- Careful attention to correctness, documentation, and detail
- Emphasis on a high power-to-weight ratio
Important links
- The Hackage page
- The official tutorial
- The Github repository
- The Haskell wiki page
- The haskell-pipes mailing list
Credits
This release was made possible due to the suggestions and contributions of many people and I want to give special mention to several people:
Renzo Carbonara, who is the largest contributor of downstream libraries, building and maintaining
pipes-network
,pipes-network-tls
,pipes-zlib
,pipes-binary
,pipes-attoparsec
andpipes-aeson
. He also provided lots of useful feedback on design proposals because of his experience maintaining these libraries.Ben Gamari, who contributed
pipes-vector
andpipes-interleave
.Oliver Charles who contributed to the design of the new
pipes-parse
library in the process of developing thepipes-tar
library.Csernik Flaviu Andrei, who contributed the complete benchmark suite for
pipes
.Merijn Verstraaten, who contribute the new
mtl
instances forpipes
.Gergely Risko, who fixed a concurrency bug in
pipes-concurrency
.Mihaly Barasz, who contributed the complete test suite for
pipes-concurrency
.Tony Day who helped automate the
pipes
ecosystem and contributed lots of useful feedback on documentation.Aleksey Khudyakov first proposed the idea to remove the old proxy transformer system and outsource the same functionality to monad transformers in the base monad. This change alone accounted for an almost 60% reduction in the library size and the greatest simplification of the types.
Johan Tibell proposed the initial idea to provide a simpler unidirectional subset of the API by default. This removed the warts that bidirectionality introduced.
Florian Hofmann whose work on
pipes-eep
led to the discovery of anArrow
instance for push-based pipes.Aristid Breitkreuz and Pierre Radermecker, who both caught important bugs in
pipes-parse
andpipes-safe
.Oliver Batchelor, whose work on integrating
pipes
with Cloud Haskell improved the design ofpipes-safe
.
Also, I would like to also thank everybody who provided feedback on the library and its documentation and also contributed code.
Change Log
People familiar with pipes
will notice that the biggest change to the library is the elimination of the proxy transformer system. This was made possible by an insight of Aleksey Khudyakov that the proxy transformers were isomorphism to monad transformers in the base monad if you ignored their ability to be unwrapped before the Proxy
layer. I later discovered how to unwrap these base monad transformers while preserving the Proxy
layer, which made possible the complete elimination of the proxy transformer system.
This had the largest impact on simplifying the API:
The number of exported functions dropped to approximately 1/3 of the original size (from about 300+ to 100+)
The number of modules dropped to 1/3 of the original size (from 18 to 6)
The
p
type parameter in type signatures disappeared, along with theProxy
type class, which became the concreteProxy
type (which was the oldProxyFast
implementation).No need for
runIdentityP
any longer
The next most important change was a simplification of the API to a unidirectional subset which is the new default. This fixed several warts of the previous API:
No more gratuitous
()
parametersThe pipe monad and category now overlap
Polymorphic type synonyms can now be used to simplify the types
The original bidirectional functionality still remains intact within the Pipes.Core
module. The only difference is that it is not exported by default.
The next important change was the realization that bind in the respond
Category (i.e. (//>)
) was exactly equivalent to a for
loop, so the unidirectional API now uses for
as a synonym for (//>)
and produces really beautiful for
loops.
Other important syntactic changes:
The unidirectional API uses
yield
instead ofrespond
like it was back inpipes-1.0
The unidirectional API uses
await
instead ofrequest
like it was back inpipes-1.0
runProxy
is nowrunEffect
(>->)
is the unidirectional pull-based composition, instead of bidirectional composition
Pipes.Prelude
has also changed to remove the suffix from all utilities, but is no longer re-exported from the main Pipes
module.
The downstream libraries have been updated as well to use the pipes-4.0
API and several of these now have much simpler APIs, too, particularly pipes-safe
. I will discuss these libraries in separate library-specific posts later on.
Future Goals
This release is intended to be the last major version bump. The next development priorities are:
Stabilize the core
pipes
libraryImprove distribution by packaging up
pipes
for several package managersContinue to build out the
pipes
ecosystem, particularly dedicatedText
andByteString
libraries
The long-term goal is to get pipes
into the Haskell platform once the API has proven itself stable and the ecosystem matures.
People interested in learning more about pipes
or contributing to development can join the official mailing list.
Hello, Gabriel.
ReplyDeleteThank you for your work. Now I can see that the API is pretty short and concise.
Yesterday I was able to write my first small utility to solve some minor business problem - split input stream by lines and put each to separate file. Today I polished it a bit and made one-liner:
import Control.Monad (forever)
import System.IO(withFile, hPutStrLn, IOMode(..))
import Pipes
import qualified Pipes.Prelude as P
main :: IO ()
main = do runEffect $ each ([1..]::[Int]) `P.zip` P.stdinLn >-> (forever $ do { (i,s) <- await; liftIO $ withFile (show i ++ ".out") WriteMode (\h -> hPutStrLn h s)})
You're welcome!
DeleteYou can actually polish it a bit more! One trick is that any time you have a `forever` in your pipeline, that's an opportunity to simplify your code using either `for` or `(>~)`.
In your case, you can simplify it to:
numbered :: Producer (Int, String) IO ()
numbered = each [1..] `P.zip` P.stdinLn
main = runEffect $ for numbered $ \(i, s) -> liftIO $ withFile (show i ++ ".out") WriteMode (\h -> hPutStrLn h s)
That uses a `for` loop to simplify the code.
This comment has been removed by the author.
Delete... Sorry for some delay (probably there are problems with Google notifications)...
DeleteThank you for this, Gabriel. I like your variant better than mine since it does not use do-block directly (with ugly sequence of round and curly braces). I'll try to rewrite my code in the future.
By the way, what about your PHD ? Have you completed it ?
I will be completing my PhD in November.
DeleteSounds good. Best wishes!
ReplyDeleteI hope, some day in the future, as you told before, you will try to write a book about Haskell (of cause, maybe not about Haskell itself, but about some libraries)...
Thanks! Yeah, I will definitely begin writing a book soon.
DeleteI was also excited to hear that you will be writing a book.I am looking forward to it.
Delete