Copyright | (c) Justin Le 2019 |
---|---|
License | BSD3 |
Maintainer | justin@jle.im |
Stability | experimental |
Portability | non-portable |
Safe Haskell | None |
Language | Haskell2010 |
Internal module exposing the internals of Pipe
, including its
underlying representation and base functor.
Synopsis
- newtype Pipe i o u m a = Pipe {}
- data PipeF i o u a
- awaitEither :: Pipe i o u m (Either u i)
- yield :: o -> Pipe i o u m ()
- trimapPipe :: (i -> j) -> (p -> o) -> (u -> v) -> Pipe j p v m a -> Pipe i o u m a
- mapInput :: (i -> j) -> Pipe j o u m a -> Pipe i o u m a
- mapOutput :: (p -> o) -> Pipe i p u m a -> Pipe i o u m a
- mapUpRes :: (u -> v) -> Pipe i o v m a -> Pipe i o u m a
- hoistPipe :: (Monad m, Monad n) => (forall x. m x -> n x) -> Pipe i o u m a -> Pipe i o u n a
- type RecPipe i o u = FreeT (PipeF i o u)
- toRecPipe :: Monad m => Pipe i o u m a -> RecPipe i o u m a
- fromRecPipe :: Monad m => RecPipe i o u m a -> Pipe i o u m a
- withRecPipe :: (Monad m, Monad n) => (RecPipe i o u m a -> RecPipe j p v n b) -> Pipe i o u m a -> Pipe j p v n b
- runStateP :: Monad m => s -> Pipe i o u (StateT s m) a -> Pipe i o u m (a, s)
- pAwaitF :: forall m i o u. MonadFree (PipeF i o u) m => m (Either u i)
- pYieldF :: forall m i o u. MonadFree (PipeF i o u) m => o -> m ()
Documentation
newtype Pipe i o u m a Source #
Similar to a conduit from the conduit package.
For a
, you have:Pipe
i o u m a
i
: Type of input stream (the things you canawait
)o
: Type of output stream (the things youyield
)u
: Type of the result of the upstream pipe (Outputted when upstream pipe terminates)m
: Underlying monad (the things you canlift
)a
: Result type when pipe terminates (outputted when finished, withpure
orreturn
)
Some specializations:
- If
i
is()
, the pipe is a source --- it doesn't need anything to produce items. It will pump out items on its own, for pipes downstream to receive and process. - If
o
isVoid
, the pipe is a sink --- it will neveryield
anything downstream. It will consume items from things upstream, and produce a result (a
) if and when it terminates. - If
u
isVoid
, then the pipe's upstream is limitless, and never terminates. This means that you can useawaitSurely
instead ofawait
, to get await a value that is guaranteed to come. You'll get ani
instead of a
.Maybe
i - If
a
isVoid
, then the pipe never terminates --- it will keep on consuming and/or producing values forever. If this is a sink, it means that the sink will never terminate, and sorunPipe
will also never terminate. If it is a source, it means that if you chain something downstream with.|
, that downstream pipe can useawaitSurely
to guarantee something being passed down.
Applicative and Monadic sequencing of pipes chains by exhaustion.
do pipeX pipeY pipeZ
is a pipe itself, that behaves like pipeX
until it terminates, then
pipeY
until it terminates, then pipeZ
until it terminates. The
Monad
instance allows you to choose "which pipe to behave like next"
based on the terminating result of a previous pipe.
do x <- pipeX pipeBasedOn x
Usually you would use it by chaining together pipes with
.|
and then running the result with
runPipe
.
runPipe
$ someSource.|
somePipe .| someOtherPipe .| someSink
See .|
and runPipe
for more information
on usage.
For a "prelude" of commonly used Pipe
s, see
Data.Conduino.Combinators.
Instances
Base functor of Pipe
.
A pipe fundamentally has the ability to await and the ability to yield. The other functionality are implemented.
- Lifting effects is implemented by the
MonadTrans
andMonadIO
instances thatFT
gives. - Ending with a result is implemented by the
Applicative
instance'spure
thatFT
gives. - Applicative and monadic sequenceing "after a pipe is done" is
implemented by the
Applicative
andMonad
instances thatFT
gives.
On top of these we implement .|
and other combinators
based on the structure that FT
gives. For some functions, it can be
easier to use an alternative encoding, RecPipe
, which is the same
thing but explicitly recursive.
awaitEither :: Pipe i o u m (Either u i) Source #
Await on upstream output. Will block until it receives an i
(expected input type) or a u
if the upstream pipe terminates.
trimapPipe :: (i -> j) -> (p -> o) -> (u -> v) -> Pipe j p v m a -> Pipe i o u m a Source #
Map over the input type, output type, and upstream result type.
If you want to map over the result type, use fmap
.
mapInput :: (i -> j) -> Pipe j o u m a -> Pipe i o u m a Source #
(Contravariantly) map over the expected input type.
mapOutput :: (p -> o) -> Pipe i p u m a -> Pipe i o u m a Source #
Map over the downstream output type.
If you want to map over the result type, use fmap
.
mapUpRes :: (u -> v) -> Pipe i o v m a -> Pipe i o u m a Source #
(Contravariantly) map over the upstream result type.
hoistPipe :: (Monad m, Monad n) => (forall x. m x -> n x) -> Pipe i o u m a -> Pipe i o u n a Source #
Transform the underlying monad of a pipe.
Note that if you are trying to work with monad transformers, this is probably not what you want. See Data.Conduino.Lift for tools for working with underlying monad transformers.
withRecPipe :: (Monad m, Monad n) => (RecPipe i o u m a -> RecPipe j p v n b) -> Pipe i o u m a -> Pipe j p v n b Source #
Convenint wrapper over toRecPipe
and fromRecPipe
.
Since: 0.2.1.0
runStateP :: Monad m => s -> Pipe i o u (StateT s m) a -> Pipe i o u m (a, s) Source #
Turn a Pipe
that runs over StateT
into a "state-modifying Pipe
",
that returns the final state when it terminates.
The main usage of this is to "isolate" the state from other pipes in the
same chain. For example, of p
, q
, and r
are all pipes under
StateT
, then:
p .| q .| r
will all share underlying state, and each can modify the state that they all three share. We essentially have global state.
However, if you use runStateP
, you can all have them use different
encapsulated states.
void (runStateP s0 p) .| void (runStateP s1 q) .| runStateP s2 r
In this case, each of those three chained pipes will use their own internal states, without sharing.
This is also useful if you want to chain a pipe over StateT
with
pipes that don't use state at all: for example if a
and b
are
"non-stateful" pipes (not over StateT
), you can do:
a .| void (runStateP s1 q) .| b
And a
and b
will be none the wiser to the fact that q
uses
StateT
internally.
Note to avoid the usage of void
, evalStateP
might
be more useful.
Since: 0.2.1.0