packages feed

conduino 0.2.0.0 → 0.2.1.0

raw patch · 6 files changed

+844/−52 lines, 6 filesdep +exceptionsPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: exceptions

API changes (from Hackage documentation)

- Data.Conduino: pattern PipeList :: Monad m => ListT m (Maybe a) -> Pipe () a u m ()
+ Data.Conduino: (&|) :: Monad m => Pipe a b u m v -> Pipe b c v m r -> Pipe a c u m (v, r)
+ Data.Conduino: (|.) :: Monad m => Pipe a b u m v -> Pipe b c v m r -> Pipe a c u m v
+ Data.Conduino: feedPipe :: Monad m => [i] -> Pipe i o u m a -> m ([o], Either (i -> Pipe i o u m a) ([i], a))
+ Data.Conduino: feedPipeEither :: Monad m => [i] -> Pipe i o u m a -> m ([o], Either (Either u i -> Pipe i o u m a) ([i], a))
+ Data.Conduino: feedbackPipe :: Monad m => Pipe x x u m a -> Pipe x x u m a
+ Data.Conduino: fuseBoth :: Monad m => Pipe a b u m v -> Pipe b c v m r -> Pipe a c u m (v, r)
+ Data.Conduino: fuseBothMaybe :: Monad m => Pipe a b u m v -> Pipe b c v m r -> Pipe a c u m (Maybe v, r)
+ Data.Conduino: fuseUpstream :: Monad m => Pipe a b u m v -> Pipe b c v m r -> Pipe a c u m v
+ Data.Conduino: hoistPipe :: (Monad m, Monad n) => (forall x. m x -> n x) -> Pipe i o u m a -> Pipe i o u n a
+ Data.Conduino: squeezePipe :: Monad m => Pipe i o u m a -> m ([o], Either (i -> Pipe i o u m a) a)
+ Data.Conduino: squeezePipeEither :: Monad m => Pipe i o u m a -> m ([o], Either (Either u i -> Pipe i o u m a) a)
+ Data.Conduino: zipSource :: Monad m => Pipe () (a -> b) u m () -> Pipe () a v m () -> Pipe () b w m ()
+ Data.Conduino.Combinators: iterM :: Monad m => (i -> m ()) -> Pipe i i u m u
+ Data.Conduino.Internal: instance Control.Monad.Catch.MonadCatch m => Control.Monad.Catch.MonadCatch (Data.Conduino.Internal.Pipe i o u m)
+ Data.Conduino.Internal: instance Control.Monad.Catch.MonadThrow m => Control.Monad.Catch.MonadThrow (Data.Conduino.Internal.Pipe i o u m)
+ Data.Conduino.Internal: instance GHC.Base.Alternative m => GHC.Base.Alternative (Data.Conduino.Internal.Pipe i o u m)
+ Data.Conduino.Internal: instance GHC.Base.MonadPlus m => GHC.Base.MonadPlus (Data.Conduino.Internal.Pipe i o u m)
+ Data.Conduino.Internal: pAwaitF :: forall m_a9B6 i_a6LI o_a6LJ u_a6LK. MonadFree (PipeF i_a6LI o_a6LJ u_a6LK) m_a9B6 => m_a9B6 (Either u_a6LK i_a6LI)
+ Data.Conduino.Internal: pYieldF :: forall m_a9Dt i_a6LI o_a6LJ u_a6LK. MonadFree (PipeF i_a6LI o_a6LJ u_a6LK) m_a9Dt => o_a6LJ -> m_a9Dt ()
+ Data.Conduino.Internal: runStateP :: Monad m => s -> Pipe i o u (StateT s m) a -> Pipe i o u m (a, s)
+ Data.Conduino.Internal: 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
+ Data.Conduino.Lift: catchP :: Monad m => Pipe i o u m (Either SomeException a) -> Pipe i o u (CatchT m) a
+ Data.Conduino.Lift: evalRWSP :: (Monad m, Monoid w) => r -> s -> Pipe i o u (RWST r w s m) a -> Pipe i o u m (a, w)
+ Data.Conduino.Lift: evalRWSPS :: (Monad m, Monoid w) => r -> s -> Pipe i o u (RWST r w s m) a -> Pipe i o u m (a, w)
+ Data.Conduino.Lift: evalStateP :: Monad m => s -> Pipe i o u (StateT s m) a -> Pipe i o u m a
+ Data.Conduino.Lift: evalStatePS :: Monad m => s -> Pipe i o u (StateT s m) a -> Pipe i o u m a
+ Data.Conduino.Lift: exceptP :: Monad m => Pipe i o u m (Either e a) -> Pipe i o u (ExceptT e m) a
+ Data.Conduino.Lift: execRWSP :: (Monad m, Monoid w) => r -> s -> Pipe i o u (RWST r w s m) a -> Pipe i o u m (s, w)
+ Data.Conduino.Lift: execRWSPS :: (Monad m, Monoid w) => r -> s -> Pipe i o u (RWST r w s m) a -> Pipe i o u m (s, w)
+ Data.Conduino.Lift: execStateP :: Monad m => s -> Pipe i o u (StateT s m) a -> Pipe i o u m s
+ Data.Conduino.Lift: execStatePS :: Monad m => s -> Pipe i o u (StateT s m) a -> Pipe i o u m s
+ Data.Conduino.Lift: execWriterP :: (Monad m, Monoid w) => Pipe i o u (WriterT w m) a -> Pipe i o u m w
+ Data.Conduino.Lift: execWriterPS :: (Monad m, Monoid w) => Pipe i o u (WriterT w m) a -> Pipe i o u m w
+ Data.Conduino.Lift: readerP :: Monad m => (r -> Pipe i o u m a) -> Pipe i o u (ReaderT r m) a
+ Data.Conduino.Lift: runCatchP :: Monad m => Pipe i o u (CatchT m) a -> Pipe i o u m (Either SomeException a)
+ Data.Conduino.Lift: runExceptP :: Monad m => Pipe i o u (ExceptT e m) a -> Pipe i o u m (Either e a)
+ Data.Conduino.Lift: runExceptP_ :: Monad m => Pipe i o u (ExceptT e m) a -> Pipe i o u m ()
+ Data.Conduino.Lift: runRWSP :: (Monad m, Monoid w) => r -> s -> Pipe i o u (RWST r w s m) a -> Pipe i o u m (a, s, w)
+ Data.Conduino.Lift: runRWSPS :: (Monad m, Monoid w) => r -> s -> Pipe i o u (RWST r w s m) a -> Pipe i o u m (a, s, w)
+ Data.Conduino.Lift: runReaderP :: Monad m => r -> Pipe i o u (ReaderT r m) a -> Pipe i o u m a
+ Data.Conduino.Lift: runStateP :: Monad m => s -> Pipe i o u (StateT s m) a -> Pipe i o u m (a, s)
+ Data.Conduino.Lift: runStatePS :: Monad m => s -> Pipe i o u (StateT s m) a -> Pipe i o u m (a, s)
+ Data.Conduino.Lift: runWriterP :: (Monad m, Monoid w) => Pipe i o u (WriterT w m) a -> Pipe i o u m (a, w)
+ Data.Conduino.Lift: runWriterPS :: (Monad m, Monoid w) => Pipe i o u (WriterT w m) a -> Pipe i o u m (a, w)
+ Data.Conduino.Lift: rwsP :: (Monad m, Monoid w) => (r -> s -> Pipe i o u m (a, s, w)) -> Pipe i o u (RWST r w s m) a
+ Data.Conduino.Lift: rwsPS :: (Monad m, Monoid w) => (r -> s -> Pipe i o u m (a, s, w)) -> Pipe i o u (RWST r w s m) a
+ Data.Conduino.Lift: stateP :: Monad m => (s -> Pipe i o u m (a, s)) -> Pipe i o u (StateT s m) a
+ Data.Conduino.Lift: statePS :: Monad m => (s -> Pipe i o u m (a, s)) -> Pipe i o u (StateT s m) a
+ Data.Conduino.Lift: writerP :: (Monad m, Monoid w) => Pipe i o u m (a, w) -> Pipe i o u (WriterT w m) a
+ Data.Conduino.Lift: writerPS :: (Monad m, Monoid w) => Pipe i o u m (a, w) -> Pipe i o u (WriterT w m) a
- Data.Conduino: infixr 2 .|
+ Data.Conduino: infixr 2 |.

Files

CHANGELOG.md view
@@ -1,6 +1,26 @@ Changelog ========= +Version 0.2.1.0+---------------++*October 30, 2019*++<https://github.com/mstksg/conduino/releases/tag/v0.2.1.0>++*   `hoistPipe` exported from *Data.Conduit*+*   A handful of pipe primitive combinators added to *Data.Conduit*, including:+    *   `feedbackPipe`+    *   `zipPipe`+    *   `&|` / `fuseBoth`+    *   `|.` / `fuseUpstream`+    *   `fuseBothMaybe`+*   Some pipe runners added to *Data.Conduit*, including:+    *   `squeezePipe` / `squeezePipeEither`+    *   `feedPipe` / `feedPipeEither`+*   *Data.Conduit.Lift* module, for working with monad transformers+*   `iterM` added to *Data.Conduit.Combinators*+ Version 0.2.0.0 --------------- 
conduino.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: e4f50c4a474c128ace934290082d6a4eda455370d44bb3efb0192215a4fbcd1b+-- hash: 52a1f0db85b8a281a18d41d489266853a6510e8e6ea76c959b68bf3cbb5ac0ce  name:           conduino-version:        0.2.0.0+version:        0.2.1.0 synopsis:       Lightweight composable continuation-based stream processors description:    A lightweight continuation-based stream processing library.                 .@@ -38,6 +38,7 @@       Data.Conduino       Data.Conduino.Combinators       Data.Conduino.Internal+      Data.Conduino.Lift   other-modules:       Paths_conduino   hs-source-dirs:@@ -47,6 +48,7 @@       base >=4.11 && <5     , bytestring     , containers+    , exceptions     , free     , list-transformer     , mtl
src/Data/Conduino.hs view
@@ -5,6 +5,7 @@ {-# LANGUAGE PatternSynonyms            #-} {-# LANGUAGE RankNTypes                 #-} {-# LANGUAGE ScopedTypeVariables        #-}+{-# LANGUAGE TupleSections              #-} {-# LANGUAGE TypeInType                 #-} {-# LANGUAGE ViewPatterns               #-} @@ -23,36 +24,36 @@ -- A "prelude" of useful pipes can be found in "Data.Conduino.Combinators". -- -- == Why a stream processing library?--- +-- -- A stream processing library is a way to stream processors in a /composable/ way: -- instead of defining your entire stream processing function as a single -- recursive loop with some global state, instead think about each "stage" of the process, -- and isolate each state to its own segment.  Each component can contain its own -- isolated state:--- +-- -- >>> runPipePure $ sourceList [1..10] --       .| scan (+) 0 --       .| sinkList -- [1,3,6,10,15,21,28,36,45,55]--- +-- -- All of these components have internal "state":--- +-- -- *   @sourceList@ keeps track of "which" item in the list to yield next -- *   @scan@ keeps track of the current running sum -- *   @sinkList@ keeps track of all items that have been seen so far, as a list--- +-- -- They all work together without knowing any other component's internal state, so -- you can write your total streaming function without concerning yourself, at -- each stage, with the entire part.--- +-- -- In addition, there are useful functions to "combine" stream processors:--- +-- -- *   'zipSink' combines sinks in an "and" sort of way: combine two sinks in --     parallel and finish when all finish. -- *   'altSink' combines sinks in an "or" sort of way: combine two sinks in --     parallel and finish when any of them finish -- *   'zipSource' combines sources in parallel and collate their outputs.--- +-- -- Stream processing libraries are also useful for streaming composition of -- monadic effects (like IO or State), as well. --@@ -60,11 +61,22 @@     Pipe   , (.|)   , runPipe, runPipePure+  -- * Primitives   , awaitEither, await, awaitWith, awaitSurely, awaitForever, yield+  -- * Special chaining+  , (&|), (|.)+  , fuseBoth, fuseUpstream, fuseBothMaybe+  -- * Incremental running+  , squeezePipe, squeezePipeEither+  , feedPipe, feedPipeEither+  -- * Pipe transformers   , mapInput, mapOutput, mapUpRes, trimapPipe+  , hoistPipe+  , feedbackPipe   -- * Wrappers   , ZipSource(..)   , unconsZipSource+  , zipSource   , ZipSink(..)   , zipSink, altSink   -- * Generators@@ -78,11 +90,15 @@ import           Control.Monad.Trans.Class import           Control.Monad.Trans.Free        (FreeT(..), FreeF(..)) import           Control.Monad.Trans.Free.Church+import           Control.Monad.Trans.State+import           Data.Bifunctor import           Data.Conduino.Internal import           Data.Functor import           Data.Functor.Identity+import           Data.Sequence                   (Seq(..)) import           Data.Void import           List.Transformer                (ListT(..), Step(..))+import qualified Data.Sequence                   as Seq import qualified List.Transformer                as LT  -- | Await input from upstream.  Will block until upstream 'yield's.@@ -195,6 +211,77 @@ runPipePure :: Pipe () Void Void Identity a -> a runPipePure = runIdentity . runPipe +-- | Repeatedly run 'squeezePipe' by giving it items from an input list.+-- Returns the outputs observed, and 'Left' if the input list was exhausted+-- with more input expected, or 'Right' if the pipe terminated, with the+-- leftover inputs and output result.+--+-- @since 0.2.1.0+feedPipe+    :: Monad m+    => [i]+    -> Pipe i o u m a+    -> m ([o], Either (i -> Pipe i o u m a) ([i], a))+feedPipe xs = (fmap . second . first) (. Right)+            . feedPipeEither xs++-- | Repeatedly run 'squeezePipeEither' by giving it items from an input+-- list.  Returns the outputs observed, and 'Left' if the input list was+-- exhausted with more input expected (or a @u@ terminating upstream+-- value), or 'Right' if the pipe terminated, with the leftover inputs and+-- output result.+--+-- @since 0.2.1.0+feedPipeEither+    :: Monad m+    => [i]+    -> Pipe i o u m a+    -> m ([o], Either (Either u i -> Pipe i o u m a) ([i], a))+feedPipeEither xs p = do+    (zs, r) <- squeezePipeEither p+    case r of+      Left n -> case xs of+        []   -> pure (zs, Left n)+        y:ys -> first (zs ++) <$> feedPipeEither ys (n (Right y))+      Right z -> pure (zs, Right (xs, z))++-- | "Squeeze" a pipe by extracting all output that can be extracted+-- before any input is requested.  Returns a 'Left' if the pipe eventually+-- does request input (as a continuation on the new input), or a 'Right' if+-- the pipe terminates with a value before ever asking for input.+--+-- @since 0.2.1.0+squeezePipe+    :: Monad m+    => Pipe i o u m a+    -> m ([o], Either (i -> Pipe i o u m a) a)+squeezePipe = (fmap . second . first) (. Right)+            . squeezePipeEither++-- | "Squeeze" a pipe by extracting all output that can be extracted before+-- any input is requested.  Returns a 'Left' if the pipe eventually does+-- request input (as a continuation on the new input, or a terminating @u@+-- value), or a 'Right' if the pipe terminates with a value before ever+-- asking for input.+--+-- @since 0.2.1.0+squeezePipeEither+    :: Monad m+    => Pipe i o u m a+    -> m ([o], Either (Either u i -> Pipe i o u m a) a)+squeezePipeEither p = runFT (pipeFree p)+    (pure . ([],) . Right)+    (\pNext -> \case+        PAwaitF f g -> pure . ([],) . Left $ (unSqueeze =<<) . lift . pNext . either f g+        PYieldF o x -> first (o:) <$> pNext x+    )+  where+    unSqueeze (os, nxt) = do+      mapM_ yield os+      case nxt of+        Left f  -> f =<< awaitEither+        Right a -> pure a+ -- | The main operator for chaining pipes together.  @pipe1 .| pipe2@ will -- connect the output of @pipe1@ to the input of @pipe2@. --@@ -235,6 +322,90 @@       Free (PYieldF x' y') -> runFreeT $ compPipe_ y' (g x')     Free (PYieldF x y) -> pure . Free $ PYieldF x (compPipe_ p y) +-- | Useful prefix version of '&|'.+--+-- @since 0.2.1.0+fuseBoth+    :: Monad m+    => Pipe a b u m v+    -> Pipe b c v m r+    -> Pipe a c u m (v, r)+fuseBoth p q = p+            .| (q >>= exhaust)+  where+    exhaust x = go+      where+        go = awaitEither >>= \case+          Left  y -> pure (y, x)+          Right _ -> go++-- | Like 'fuseBoth' and '&|', except does not wait for the upstream pipe+-- to terminate.  Return 'Nothing' in the first field if the upstream pipe hasn't terminated,+-- and 'Just' if it has, with the terminating value.+--+-- @since 0.2.1.0+fuseBothMaybe :: Monad m => Pipe a b u m v -> Pipe b c v m r -> Pipe a c u m (Maybe v, r)+fuseBothMaybe p q = p+                 .| (q >>= check)+  where+    check x = (,x) . either Just (const Nothing) <$> awaitEither++-- | Useful prefix version of '|.'.+--+-- @since 0.2.1.0+fuseUpstream+    :: Monad m+    => Pipe a b u m v+    -> Pipe b c v m r+    -> Pipe a c u m v+fuseUpstream p q = fst <$> fuseBoth p q++-- | Like @.|@, but get the result of /both/ pipes on termination, instead+-- of just the second.  This means that @p &| q@ will only terminate with a result when+-- /both/ @p@ and @q@ terminate.  (Typically, @p .| q@ would terminate as soon as+-- @q@ terminates.)+--+-- @since 0.2.1.0+(&|) :: Monad m => Pipe a b u m v -> Pipe b c v m r -> Pipe a c u m (v, r)+(&|) = fuseBoth++-- | Like @.|@, but keep the result of the /first/ pipe, instead of the+-- second.  This means that @p |. q@ will only terminate with a result when+-- /both/ @p@ and @q@ terminate.  (Typically, @p .| q@ would terminate as soon as+-- @q@ terminates.)+--+-- @since 0.2.1.0+(|.) :: Monad m => Pipe a b u m v -> Pipe b c v m r -> Pipe a c u m v+(|.) = fuseUpstream++infixr 2 &|+infixr 2 |.++-- | Loop a pipe into itself.+--+-- *  Will feed all output back to the input+-- *  Will only ask for input upstream if output is stalled.+-- *  Yields all outputted values downstream, effectively duplicating them.+--+-- @since 0.2.1.0+feedbackPipe+    :: Monad m+    => Pipe x x u m a+    -> Pipe x x u m a+feedbackPipe p = fmap fst . runStateP Seq.empty $+       popper+    .| hoistPipe lift p+    .| awaitForever (\x -> lift (modify (:|> x)) *> yield x)+  where+    popper = lift get >>= \case+      Empty -> awaitEither >>= \case+        Left r  -> pure r+        Right x -> yield x >> popper+      x :<| xs -> do+        lift $ put xs+        yield x+        popper+ -- | A newtype wrapper over a source (@'Pipe' () o 'Void'@) that gives it an -- alternative 'Applicative' and 'Alternative' instance, matching "ListT -- done right".@@ -278,10 +449,15 @@  instance Monad m => Applicative (ZipSource m) where     pure = ZipSource . yield-    ZipSource (PipeList fs) <*> ZipSource (PipeList xs) = ZipSource . PipeList . fmap Just $-            uncurry ($)-        <$> LT.zip (concatListT fs) (concatListT xs)+    ZipSource p <*> ZipSource q = ZipSource $ zipSource p q +-- | Takes two sources and runs them in parallel, collating their outputs.+--+-- @since 0.2.1.0+zipSource :: Monad m => Pipe () (a -> b) u m () -> Pipe () a v m () -> Pipe () b w m ()+zipSource (PipeList fs) (PipeList xs) = PipeList . fmap Just $+    uncurry ($) <$> LT.zip (concatListT fs) (concatListT xs)+ concatListT :: Monad m => ListT m (Maybe a) -> ListT m a concatListT xs = ListT $ next xs >>= \case     Nil              -> pure Nil@@ -320,27 +496,10 @@     :: Monad m     => ListT m (Maybe o)     -> Pipe i o u m ()-fromListT = fromRecPipe . go-  where-    go xs = FreeT $ next xs >>= \case-      Nil              -> pure . Pure $ ()-      Cons Nothing  ys -> pure . Free $ PAwaitF (\_ -> pure ()) $ \_ -> go ys-      Cons (Just y) ys -> pure . Free $ PYieldF y (go ys)------ | A source is essentially equiavlent to 'ListT'.  This converts----- a 'ListT' to the source it encodes.---------- See 'ZipSource' for a wrapper over 'Pipe' that gives the right 'Functor'----- and 'Alternative' instances.---fromListT---    :: Monad m---    => ListT m o---    -> Pipe i o u m ()---fromListT = fromRecPipe . go---  where---    go xs = FreeT $ next xs >>= \case---      Nil       -> pure . Pure $ ()---      Cons y ys -> pure . Free $ PYieldF y (go ys)+fromListT xs = lift (next xs) >>= \case+      Nil              -> pure ()+      Cons Nothing  ys -> fromListT ys+      Cons (Just y) ys -> yield y *> fromListT ys  -- | Given a "generator" of @o@ in @m@, return a /source/ that that -- generator encodes.  Is the inverse of 'withSource'.
src/Data/Conduino/Combinators.hs view
@@ -44,6 +44,7 @@   -- * Pipes   , map   , mapM+  , iterM   , scan   , mapAccum   , take@@ -282,6 +283,13 @@ -- | Map a monadic function to process every input, and yield its output. mapM :: Monad m => (i -> m o) -> Pipe i o u m u mapM f = awaitForever ((yield =<<) . lift . f)++-- | Execute a monadic function to process every input, passing through the+-- original value back downstream.+--+-- @since 0.2.1.0+iterM :: Monad m => (i -> m ()) -> Pipe i i u m u+iterM f = mapM (\x -> x <$ f x)  -- | Map a pure "stateful" function over each incoming item.  Give -- a function to update the state and return an output and an initial
src/Data/Conduino/Internal.hs view
@@ -31,14 +31,21 @@   , hoistPipe   , RecPipe   , toRecPipe, fromRecPipe+  , withRecPipe+  , runStateP+  , pAwaitF, pYieldF   ) where +import           Control.Applicative+import           Control.Monad.Catch import           Control.Monad.Except import           Control.Monad.Free.Class import           Control.Monad.Free.TH import           Control.Monad.RWS-import           Control.Monad.Trans.Free        (FreeT(..))+import           Control.Monad.Trans.Free        (FreeT(..), FreeF(..)) import           Control.Monad.Trans.Free.Church+import           Control.Monad.Trans.State+import           Data.Functor  #if !MIN_VERSION_base(4,13,0) import           Control.Monad.Fail@@ -86,22 +93,23 @@ --    to produce items.  It will pump out items on its own, for pipes --    downstream to receive and process. ----- *  If @o@ is 'Void', the pipe is a /sink/ --- it will never 'yield'---    anything downstream.  It will consume items from things upstream, and---    produce a result (@a@) if and when it terminates.+-- *  If @o@ is 'Data.Void.Void', the pipe is a /sink/ --- it will never+--    'yield' anything downstream.  It will consume items from things+--    upstream, and produce a result (@a@) if and when it terminates. ----- *  If @u@ is 'Void', then the pipe's upstream is limitless, and never---    terminates.  This means that you can use 'Data.Condunio.awaitSurely'---    instead of 'Data.Conduino.await', to get await a value that is---    guaranteed to come.  You'll get an @i@ instead of a @'Maybe' i@.+-- *  If @u@ is 'Data.Void.Void', then the pipe's upstream is limitless,+--    and never terminates.  This means that you can use+--    'Data.Conduino.awaitSurely' instead of 'Data.Conduino.await', to get+--    await a value that is guaranteed to come.  You'll get an @i@ instead+--    of a @'Maybe' i@. ----- *  If @a@ is 'Void', 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 so---    'Data.Condunio.runPipe' will also never terminate.  If it is+-- *  If @a@ is 'Data.Void.Void', 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 so+--    'Data.Conduino.runPipe' will also never terminate.  If it is --    a source, it means that if you chain something downstream with---    'Data.Condunio..|', that downstream pipe can use 'awaitSurely' to---    guarantee something being passed down.+--    'Data.Conduino..|', that downstream pipe can use+--    'Data.Conduino.awaitSurely' to guarantee something being passed down. -- -- Applicative and Monadic sequencing of pipes chains by exhaustion. --@@ -122,8 +130,8 @@ -- @ -- -- Usually you would use it by chaining together pipes with--- 'Data.Condunio..|' and then running the result with--- 'Data.Condunio.runPipe'.+-- 'Data.Conduino..|' and then running the result with+-- 'Data.Conduino.runPipe'. -- -- @ -- 'Data.Conduino.runPipe' $ someSource@@ -132,11 +140,11 @@ --        .| someSink -- @ ----- See 'Data.Condunio..|' and 'Data.Condunio.runPipe' for more information+-- See 'Data.Conduino..|' and 'Data.Conduino.runPipe' for more information -- on usage. -- -- For a "prelude" of commonly used 'Pipe's, see--- "Data.Condunio.Combinators".+-- "Data.Conduino.Combinators". -- newtype Pipe i o u m a = Pipe { pipeFree :: FT (PipeF i o u) m a }   deriving@@ -151,6 +159,10 @@     , MonadWriter w     , MonadError e     , MonadRWS r w s+    , Alternative+    , MonadPlus+    , MonadThrow+    , MonadCatch     )  instance MonadFail m => MonadFail (Pipe i o u m) where@@ -185,6 +197,10 @@       PYieldF a x -> PYieldF (g a) x  -- | 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. hoistPipe     :: (Monad m, Monad n)     => (forall x. m x -> n x)@@ -221,3 +237,69 @@ -- | Convert a 'RecPipe' back into a 'Pipe'. fromRecPipe :: Monad m => RecPipe i o u m a -> Pipe i o u m a fromRecPipe = Pipe . toFT++-- | Convenint wrapper over 'toRecPipe' and 'fromRecPipe'.+--+-- @since 0.2.1.0+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+withRecPipe f = fromRecPipe . f . toRecPipe++-- | 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', 'Data.Conduino.Lift.evalStateP' might+-- be more useful.+runStateP+    :: Monad m+    => s+    -> Pipe i o u (StateT s m) a+    -> Pipe i o u m (a, s)+runStateP = withRecPipe . go+  where+    go s (FreeT p) = FreeT $ runStateT p s <&> \(q, s') ->+      case q of+        Pure x -> Pure (x, s')+        Free l -> Free $ go s' <$> l+
+ src/Data/Conduino/Lift.hs view
@@ -0,0 +1,521 @@+{-# LANGUAGE LambdaCase   #-}+{-# LANGUAGE ViewPatterns #-}++-- |+-- Module      : Data.Conduino.Lift+-- Copyright   : (c) Justin Le 2019+-- License     : BSD3+--+-- Maintainer  : justin@jle.im+-- Stability   : experimental+-- Portability : non-portable+--+-- Working with underlying monad transformers and 'Pipe'.+--+-- There is no "general abstraction" for dealing with each monad+-- transformer, but we can translate the semantics that each monad+-- transformer provides into meaningful 'Pipe' operations.+--+-- For example, a @'Pipe' i o u ('State' s) a@ is a pipe working over+-- stateful effects --- it can pull information and modify an underlying+-- state to do its job.  It takes in @i@ and outputs @o@, using an+-- underlying state @s@.+--+-- However, such a pipe is similar to @s -> 'Pipe'+-- i o u 'Data.Functor.Identity.Identity' (a, s)@.  Giving some starting+-- state, it takes in @i@ and outputs @o@, and when it completes, it+-- returns an @a@ and an @s@, the final state after all its processing is+-- done.+--+-- The /general/ idea is that:+--+-- *  A pipe over a monad transformer /shares that monadic context/ over+--    /every pipe/ in a composition.+--+--    For example, if @p@, @q@, and @r@ are all pipes over 'StateT', the @p+--    .| q .| r@ will all share a common global state.+--+--    If @p@, @q@, and @r@ are all pipes over 'ExceptT', then @p .| q .| r@+--    will all short-circult fail each other: if @q@ fails, then they all+--    fail, etc.+--+--    If @p@, @q@, and @r@ are all pipes over 'WriterT' then @p .| q .| r@+--    will all accumulate to a shared global log.+--+--    If @p@, @q@, and @r@ are all pipes over 'ReaderT' then @p .| q .| r@+--    will use the same identical environment.+--+-- *  Using the @runX@ family of functions ('runStateP', 'runExceptP',+--    etc.) lets you /isolate/ out the common context within a composition+--    of pipes.+--+--    For example, if @p@ is a pipe over 'StateT', then @a .| 'void' ('runStateP'+--    s0 p) .| b@, @a@ and @b@ will not be able to use the state of @p@.+--+--    If @p@ is a pipe over 'ExceptT', then in @a .| void ('runExceptP' p) .|+--    b@, a failure in @p@ will not cause all the others to fail.+--+-- Both of these representations have different advantages and+-- disadvantages, that are separate and unique for each individual monad+-- transformer on a case-by-case basis.  This module provides functions on+-- such a case-by-case basis as you need them.+--+-- @since 0.2.1.0+module Data.Conduino.Lift (+  -- * State+  -- ** Lazy+    stateP, runStateP, evalStateP, execStateP+  -- ** Strict+  , statePS, runStatePS, evalStatePS, execStatePS+  -- * Except+  , exceptP, runExceptP, runExceptP_+  -- * Reader+  , readerP, runReaderP+  -- * Writer+  -- ** Lazy+  , writerP, runWriterP, execWriterP+  -- ** Strict+  , writerPS, runWriterPS, execWriterPS+  -- * RWS+  -- ** Lazy+  , rwsP, runRWSP, evalRWSP, execRWSP+  -- ** Strict+  , rwsPS, runRWSPS, evalRWSPS, execRWSPS+  -- * Catch+  , catchP, runCatchP+  ) where++import           Control.Monad.Catch.Pure+import           Control.Monad.Trans.Class+import           Control.Monad.Trans.Except+import           Control.Monad.Trans.Free+import           Control.Monad.Trans.RWS           (RWST(..))+import           Control.Monad.Trans.Reader+import           Control.Monad.Trans.State+import           Control.Monad.Trans.Writer+import           Data.Conduino+import           Data.Conduino.Internal+import           Data.Functor+import qualified Control.Monad.Trans.RWS           as RWS+import qualified Control.Monad.Trans.RWS.Strict    as RWSS+import qualified Control.Monad.Trans.State.Strict  as SS+import qualified Control.Monad.Trans.Writer.Strict as WS++-- | Turn a "state-modifying 'Pipe'" into a 'Pipe' that runs over 'StateT',+-- so you can chain it with other 'StateT' pipes.+--+-- Note that this will /overwrite/ whatever state exists with+-- the @s@ that it gets when it terminates.  If any other pipe in this+-- chain modifies or uses state, all modifications will be overwritten when+-- the @(a, s)@-producing pipe terminates.+stateP+    :: Monad m+    => (s -> Pipe i o u m (a, s))+    -> Pipe i o u (StateT s m) a+stateP f = do+    s       <- lift get+    (x, s') <- hoistPipe lift (f s)+    x <$ lift (put s')++-- | Like 'runStateP', but ignoring the final result.  It returns the final+-- state after the pipe succesfuly terminates.+execStateP+    :: Monad m+    => s+    -> Pipe i o u (StateT s m) a+    -> Pipe i o u m s+execStateP s = fmap snd . runStateP s++-- | Takes a 'Pipe' over 'StateT' and "hides" the state from the outside+-- world.  Give an initial state --- the pipe behaves the same way, but to+-- the external user it is abstracted away.  See 'runStateP' for more+-- information.+--+-- This can be cleaner than 'runStateP' because if @a@ is @()@, you+-- don't have to sprinkle in 'void' everywhere.  However, it's only really+-- useful if you don't need to get the final state upon termination.+evalStateP+    :: Monad m+    => s+    -> Pipe i o u (StateT s m) a+    -> Pipe i o u m a+evalStateP s = fmap fst . runStateP s++-- | 'stateP', but for "Control.Monad.Trans.State.Strict".+statePS+    :: Monad m+    => (s -> Pipe i o u m (a, s))+    -> Pipe i o u (SS.StateT s m) a+statePS f = do+    s       <- lift SS.get+    (x, s') <- hoistPipe lift (f s)+    x <$ lift (SS.put s')++-- | 'runStateP', but for "Control.Monad.Trans.State.Strict".+runStatePS+    :: Monad m+    => s+    -> Pipe i o u (SS.StateT s m) a+    -> Pipe i o u m (a, s)+runStatePS = withRecPipe . go+  where+    go s (FreeT p) = FreeT $ SS.runStateT p s <&> \(q, s') ->+      case q of+        Pure x -> Pure (x, s')+        Free l -> Free $ go s' <$> l++-- | 'execStateP', but for "Control.Monad.Trans.State.Strict".+execStatePS+    :: Monad m+    => s+    -> Pipe i o u (SS.StateT s m) a+    -> Pipe i o u m s+execStatePS s = fmap snd . runStatePS s++-- | 'evalStateP', but for "Control.Monad.Trans.State.Strict".+evalStatePS+    :: Monad m+    => s+    -> Pipe i o u (SS.StateT s m) a+    -> Pipe i o u m a+evalStatePS s = fmap fst . runStatePS s++-- | Turn a "failable-result" 'Pipe' into a pipe over 'ExceptT'.+--+-- Note that a 'throwE' failure will only ever happen when the input pipe+-- "succesfully" terminates with 'Left'.  It would never happen before the+-- pipe terminates, since you don't get the @'Either' e a@ until the pipe+-- succesfully terminates.+exceptP+    :: Monad m+    => Pipe i o u m (Either e a)+    -> Pipe i o u (ExceptT e m) a+exceptP p = hoistPipe lift p >>= \case+    Left  e -> lift $ throwE e+    Right x -> pure x++-- | Turn a 'Pipe' that runs over 'ExceptT' into an "early-terminating+-- 'Pipe'" that "succesfully" returns 'Left' or 'Right'.+--+-- The main usage of this is to "isolate" the short-circuiting failure of+-- 'ExceptT' to only happen within one component of a chain.  For example,+-- of @p@, @q@, and @r@ are all pipes under 'ExceptT', then:+--+-- @+--     p+--  .| q+--  .| r+-- @+--+-- will short-circuit fail if /any/ of @p@, @q@, or @r@ fail.  We have+-- global failure only.+--+-- However, if you use 'runExceptP', we isolate the short-circuiting+-- failure to only a single type.+--+-- @+--     void (runExceptP p)+--  .| void (runExceptP q)+--  .| runExceptP r+-- @+--+-- In this case, if (for example) @q@ fails, it won't cause the whole thing+-- to fail: it will just be the same as if @q@ succesfully terminates+-- normally.+--+-- This is also useful if you want to chain a pipe over 'ExceptT' with+-- pipes that don't have 'ExceptT' at all: for example if @a@ and @b@ are+-- "non-erroring" pipes (/not/ over 'ExceptT'), you can do:+--+-- @+--     a+--  .| void (runExceptP q)+--  .| b+-- @+--+-- And @a@ and @b@ will be none the wiser to the fact that @q@ uses+-- 'ExceptT' internally.+--+-- Note to avoid the usage of 'void', 'runExceptP_' might be more useful.+runExceptP+    :: Monad m+    => Pipe i o u (ExceptT e m) a+    -> Pipe i o u m (Either e a)+runExceptP = withRecPipe go+  where+    go (FreeT p) = FreeT $ runExceptT p <&> \case+      Left  e        -> Pure $ Left e+      Right (Pure x) -> Pure $ Right x+      Right (Free l) -> Free $ go <$> l++-- | A handy version of 'runExceptP' that discards its output, so it can be+-- easier to chain using '.|'.  It's useful if you are using 'runExceptP'+-- to "isolate" failures from the rest of a chain.+runExceptP_+    :: Monad m+    => Pipe i o u (ExceptT e m) a+    -> Pipe i o u m ()+runExceptP_ = void . runExceptP++-- | Like 'exceptP', but for 'CatchT'.  See 'exceptP' for usage details and+-- caveats.  In general, can be useful for chaining with other 'CatchT'+-- pipes.+--+-- Note that a 'throwM' failure will only ever happen when the input pipe+-- "succesfully" terminates with 'Left'.  It would never happen before the+-- pipe terminates, since you don't get the @'Either' 'SomeException' a@+-- until the pipe succesfully terminates.+catchP+    :: Monad m+    => Pipe i o u m (Either SomeException a)+    -> Pipe i o u (CatchT m) a+catchP p = hoistPipe lift p >>= \case+    Left  e -> lift $ throwM e+    Right x -> pure x++-- | Like 'runExceptP', but for 'CatchT'.  See 'runExceptP' for usage+-- details.  In general, can be useful for "isolating" a 'CatchT' pipe from+-- the rest of its chain.+runCatchP+    :: Monad m+    => Pipe i o u (CatchT m) a+    -> Pipe i o u m (Either SomeException a)+runCatchP = withRecPipe go+  where+    go (FreeT p) = FreeT $ runCatchT p <&> \case+      Left  e        -> Pure $ Left e+      Right (Pure x) -> Pure $ Right x+      Right (Free l) -> Free $ go <$> l++-- | Turn a "parameterized 'Pipe'" into a 'Pipe' that runs over 'ReaderT',+-- so you can chain it with other 'ReaderT' pipes.+--+-- Essentially, instead of directly providing the @r@ in an @r -> 'Pipe'+-- i o u m a@, the @r@ instead comes from the globally shared environment.+readerP+    :: Monad m+    => (r -> Pipe i o u m a)+    -> Pipe i o u (ReaderT r m) a+readerP f = hoistPipe lift . f =<< lift ask++-- | Turn a pipe over 'ReaderT' into a directly parameterized pipe.+-- Instead of getting the parameter from the globally shared 'ReaderT'+-- environment, give it directly instead.+--+-- It can be useful to "ignore" a globally shared environment and just give+-- the @r@ directly and immediately.+runReaderP+    :: Monad m+    => r+    -> Pipe i o u (ReaderT r m) a+    -> Pipe i o u m a+runReaderP r = hoistPipe (`runReaderT` r)++-- | Turn a pipe returning an @(a, w)@ tuple upon termination into a pipe+-- returning @a@, logging the @w@ in an underlying 'WriterT' context.+--+-- This can be useful for composing your pipe with other 'WriterT' pipes,+-- aggregating all to a common global log.+--+-- However, be aware that this only ever 'tell's when the pipe succesfuly+-- terminates.  It doesn't do "streaming logging" -- it only makes one+-- log payload at the point of succesful termination.  To do streaming+-- logging (logging things as you get them), you should probably just+-- directly use 'WriterT' instead, with 'Data.Conduino.Combinators.repeatM'+-- or 'Data.Conduino.Combinators.iterM' or something similar.+writerP+    :: (Monad m, Monoid w)+    => Pipe i o u m (a, w)+    -> Pipe i o u (WriterT w m) a+writerP p = do+    (x, w) <- hoistPipe lift p+    x <$ lift (tell w)++-- | Turn a 'Pipe' that runs over 'WriterT' into a 'Pipe' that returns the+-- final log when it terminates.+--+-- The main usage of this is to "isolate" the log from other pipes in the+-- same chain.  For example, of @p@, @q@, and @r@ are all pipes under+-- 'WriterT', then:+--+-- @+--     p+--  .| q+--  .| r+-- @+--+-- will all share underlying log, and all logging from any of them will+-- accumulate together in an interleaved way.  It is essentially a global+-- log.+--+-- However, if you use 'runWriterP', you can all have them use different+-- encapsulated logs.+--+-- @+--     void (runWriterP p)+--  .| void (runWriterP q)+--  .| runWriterP r+-- @+--+-- In this case, each of those three chained pipes will use their own+-- internal logs, without sharing.+--+-- This is also useful if you want to chain a pipe over 'WriterT' with+-- pipes that don't use state at all: for example if @a@ and @b@ are+-- "non-logging" pipes (/not/ over 'WriterT'), you can do:+--+-- @+--     a+--  .| void (runWriterP q)+--  .| b+-- @+--+-- And @a@ and @b@ will be none the wiser to the fact that @q@ uses+-- 'WriterT' internally.+runWriterP+    :: (Monad m, Monoid w)+    => Pipe i o u (WriterT w m) a+    -> Pipe i o u m (a, w)+runWriterP = withRecPipe (go mempty)+  where+    go w (FreeT p) = FreeT $ runWriterT p <&> \(r, (w <>)->w') ->+      case r of+        Pure x -> Pure (x, w')+        Free l -> Free $ go w' <$> l++-- | 'runWriterP', but only returning the final log after succesful+-- termination.+execWriterP+    :: (Monad m, Monoid w)+    => Pipe i o u (WriterT w m) a+    -> Pipe i o u m w+execWriterP = fmap snd . runWriterP++-- | 'writerP', but for "Control.Monad.Trans.Writer.Strict".+writerPS+    :: (Monad m, Monoid w)+    => Pipe i o u m (a, w)+    -> Pipe i o u (WS.WriterT w m) a+writerPS p = do+    (x, w) <- hoistPipe lift p+    x <$ lift (WS.tell w)++-- | 'runWriterP', but for "Control.Monad.Trans.Writer.Strict".+runWriterPS+    :: (Monad m, Monoid w)+    => Pipe i o u (WS.WriterT w m) a+    -> Pipe i o u m (a, w)+runWriterPS = withRecPipe (go mempty)+  where+    go w (FreeT p) = FreeT $ WS.runWriterT p <&> \(r, (w <>)->w') ->+      case r of+        Pure x -> Pure (x, w')+        Free l -> Free $ go w' <$> l++-- | 'execWriterP', but for "Control.Monad.Trans.Writer.Strict".+execWriterPS+    :: (Monad m, Monoid w)+    => Pipe i o u (WriterT w m) a+    -> Pipe i o u m w+execWriterPS = fmap snd . runWriterP++-- | Turn a parameterized, state-transforming, log-producing 'Pipe' into+-- a 'Pipe' over 'RWST', which can be useful for chaining it with other+-- 'RWST' pipes.+--+-- See 'stateP' and 'writerP' for more details on caveats, including:+--+-- *  Logging only happens when the @(a,s,w)@-returning pipe terminates.+--    There is no "streaming logging" --- the resulting @w@ is logged all+--    at once.+-- *  When the @(a,s,w)@-returning pipe terminates, whatever state in the+--    'RWST' is overwritten with the @s@ returned.  If other pipes in the+--    chain modify the @s@, their modifications will be overwritten.+rwsP+    :: (Monad m, Monoid w)+    => (r -> s -> Pipe i o u m (a, s, w))+    -> Pipe i o u (RWST r w s m) a+rwsP f = do+    r <- lift RWS.ask+    s <- lift RWS.get+    (x, s', w) <- hoistPipe lift (f r s)+    lift (RWS.tell w)+    x <$ lift (RWS.put s')++-- | Turn a 'Pipe' that runs over 'RWST' into a state-modifying,+-- environment-using, log-accumulating 'Pipe'.  See 'runStateP',+-- 'runWriterP', and 'runReaderP' for the uses and semantics.+runRWSP+    :: (Monad m, Monoid w)+    => r+    -> s+    -> Pipe i o u (RWST r w s m) a+    -> Pipe i o u m (a, s, w)+runRWSP r = withRecPipe . go mempty+  where+    go w s (FreeT p) = FreeT $ runRWST p r s <&> \(q, s', (w <>)->w') ->+      case q of+        Pure x -> Pure (x, s', w')+        Free l -> Free $ go w' s' <$> l++-- | 'runRWSP', but ignoring the final state.+evalRWSP+    :: (Monad m, Monoid w)+    => r+    -> s+    -> Pipe i o u (RWST r w s m) a+    -> Pipe i o u m (a, w)+evalRWSP r s = fmap (\(x,_,w) -> (x,w)) . runRWSP r s++-- | 'runRWSP', but ignoring the result value.+execRWSP+    :: (Monad m, Monoid w)+    => r+    -> s+    -> Pipe i o u (RWST r w s m) a+    -> Pipe i o u m (s, w)+execRWSP r s = fmap (\(_,s',w) -> (s',w)) . runRWSP r s++-- | 'rwsP', but for "Control.Monad.Trans.RWS.Strict".+rwsPS+    :: (Monad m, Monoid w)+    => (r -> s -> Pipe i o u m (a, s, w))+    -> Pipe i o u (RWSS.RWST r w s m) a+rwsPS f = do+    r <- lift RWSS.ask+    s <- lift RWSS.get+    (x, s', w) <- hoistPipe lift (f r s)+    lift (RWSS.tell w)+    x <$ lift (RWSS.put s')++-- | 'runRWSPS', but for "Control.Monad.Trans.RWS.Strict".+runRWSPS+    :: (Monad m, Monoid w)+    => r+    -> s+    -> Pipe i o u (RWSS.RWST r w s m) a+    -> Pipe i o u m (a, s, w)+runRWSPS r = withRecPipe . go mempty+  where+    go w s (FreeT p) = FreeT $ RWSS.runRWST p r s <&> \(q, s', (w <>)->w') ->+      case q of+        Pure x -> Pure (x, s', w')+        Free l -> Free $ go w' s' <$> l++-- | 'evalRWSPS', but for "Control.Monad.Trans.RWS.Strict".+evalRWSPS+    :: (Monad m, Monoid w)+    => r+    -> s+    -> Pipe i o u (RWSS.RWST r w s m) a+    -> Pipe i o u m (a, w)+evalRWSPS r s = fmap (\(x,_,w) -> (x,w)) . runRWSPS r s++-- | 'execRWSPS', but for "Control.Monad.Trans.RWS.Strict".+execRWSPS+    :: (Monad m, Monoid w)+    => r+    -> s+    -> Pipe i o u (RWSS.RWST r w s m) a+    -> Pipe i o u m (s, w)+execRWSPS r s = fmap (\(_,s',w) -> (s',w)) . runRWSPS r s