pipes-2.1.0: Compositional pipelines

Control.Pipe

Description

Pipe is a monad transformer that enriches the base monad with the ability to await or yield data to and from other Pipes.

Synopsis

# Introduction

I completely expose the Pipe data type and internals in order to encourage people to write their own Pipe functions. This does not compromise the correctness or safety of the library at all and you can feel free to use the constructors directly without violating any laws or invariants.

I promote using the Monad and Category instances to build and compose pipes, but this does not mean that they are the only option. In fact, any combinator provided by other iteratee libraries can be recreated for pipes, too. However, this core library does not provide many of the functions found in other libraries in order to encourage people to find principled and theoretically grounded solutions rather than devise ad-hoc solutions characteristic of other iteratee implementations.

# Types

The Pipe type is strongly inspired by Mario Blazevic's Coroutine type in his concurrency article from Issue 19 of The Monad Reader and is formulated in the exact same way.

His Coroutine type is actually a free monad transformer (i.e. FreeT) and his InOrOut functor corresponds to PipeF.

data PipeF a b x Source #

The base functor for the Pipe type

Constructors

 Await (a -> x) Yield (b, x)

Instances

 Functor (PipeF a b) Source # Methodsfmap :: (a -> b) -> PipeF a b a -> PipeF a b b #(<$) :: a -> PipeF a b b -> PipeF a b a # type Pipe a b = FreeT (PipeF a b) Source # The base type for pipes • a - The type of input received from upstream pipes • b - The type of output delivered to downstream pipes • m - The base monad • r - The type of the return value type Producer b = Pipe () b Source # A pipe that produces values type Consumer b = Pipe b Void Source # A pipe that consumes values type Pipeline = Pipe () Void Source # A self-contained pipeline that is ready to be run # Create Pipes yield and await are the only two primitives you need to create pipes. Since Pipe a b m is a monad, you can assemble yield and await statements using ordinary do notation. Since Pipe a b is also a monad transformer, you can use lift to invoke the base monad. For example, you could write a pipe stage that requests permission before forwarding any output: check :: (Show a) => Pipe a a IO r check = forever$ do
x <- await
lift $putStrLn$ "Can '" ++ (show x) ++ "' pass?"
ok <- read <$> lift getLine when ok (yield x) await :: Monad m => Pipe a b m a Source # Wait for input from upstream. await blocks until input is available from upstream. yield :: Monad m => b -> Pipe a b m () Source # Deliver output downstream. yield restores control back upstream and binds the result to await. pipe :: Monad m => (a -> b) -> Pipe a b m r Source # Convert a pure function into a pipe pipe = forever$ do
x <- await
yield (f x)

# Compose Pipes

Pipes form a Category, meaning that you can compose Pipes and also define an identity Pipe.

Pipe composition binds the output of the upstream Pipe to the input of the downstream Pipe. Like Haskell functions, Pipes are lazy, meaning that upstream Pipes are only evaluated as far as necessary to generate enough input for downstream Pipes. If any Pipe terminates, it also terminates every Pipe composed with it.

If you want to define a proper Category instance you have to wrap the Pipe type using the newtype PipeC in order to rearrange the type variables.

This means that if you want to compose pipes using (.) from the Category type class, you end up with a newtype mess:

unPipeC (PipeC p1 . PipeC p2)

You can avoid this by using convenient operators that do this newtype wrapping and unwrapping for you: