packages feed

conduino 0.1.0.0 → 0.2.0.0

raw patch · 7 files changed

+1242/−148 lines, 7 filesdep +bytestringdep +containersdep +list-transformerdep ~basePVP ok

version bump matches the API change (PVP)

Dependencies added: bytestring, containers, list-transformer, mtl

Dependency ranges changed: base

API changes (from Hackage documentation)

- Data.Conduino: dropP :: Int -> Pipe i o u m ()
- Data.Conduino: foldrP :: (a -> b -> b) -> b -> Pipe a Void u m b
- Data.Conduino: instance Control.Monad.Free.Class.MonadFree (Data.Conduino.PipeF i o u) (Data.Conduino.Pipe i o u m)
- Data.Conduino: instance Control.Monad.Trans.Class.MonadTrans (Data.Conduino.Pipe i o u)
- Data.Conduino: instance GHC.Base.Applicative (Data.Conduino.Pipe i o u m)
- Data.Conduino: instance GHC.Base.Functor (Data.Conduino.Pipe i o u m)
- Data.Conduino: instance GHC.Base.Functor (Data.Conduino.PipeF i o u)
- Data.Conduino: instance GHC.Base.Monad (Data.Conduino.Pipe i o u m)
- Data.Conduino: iterateP :: (a -> a) -> a -> Pipe i a u m r
- Data.Conduino: mapMP :: Monad m => (a -> m b) -> Pipe a b u m u
- Data.Conduino: mapP :: (a -> b) -> Pipe a b u m u
- Data.Conduino: repeatM :: Monad m => m o -> Pipe i o u m u
- Data.Conduino: sinkList :: Pipe i Void u m [i]
- Data.Conduino: sourceList :: Foldable t => t a -> Pipe i a u m ()
- Data.Conduino: unfoldP :: (b -> Maybe (a, b)) -> b -> Pipe i a u m ()
- Data.Conduino: unfoldPForever :: (b -> (a, b)) -> b -> Pipe i a u m r
- Lib: someFunc :: IO ()
+ Data.Conduino: ZipSource :: Pipe () a Void m () -> ZipSource m a
+ Data.Conduino: [getZipSource] :: ZipSource m a -> Pipe () a Void m ()
+ Data.Conduino: altSink :: Monad m => Pipe i Void u m a -> Pipe i Void u m a -> Pipe i Void u m a
+ Data.Conduino: awaitWith :: (i -> Pipe i o u m u) -> Pipe i o u m u
+ Data.Conduino: fromListT :: Monad m => ListT m (Maybe o) -> Pipe i o u m ()
+ Data.Conduino: genSource :: (forall r. (Maybe (o, m r) -> m r) -> m r) -> Pipe i o u m ()
+ Data.Conduino: instance Control.Monad.Trans.Class.MonadTrans (Data.Conduino.ZipSink i u)
+ Data.Conduino: instance Control.Monad.Trans.Class.MonadTrans Data.Conduino.ZipSource
+ Data.Conduino: instance GHC.Base.Functor (Data.Conduino.ZipSource m)
+ Data.Conduino: instance GHC.Base.Monad m => GHC.Base.Alternative (Data.Conduino.ZipSource m)
+ Data.Conduino: instance GHC.Base.Monad m => GHC.Base.Applicative (Data.Conduino.ZipSource m)
+ Data.Conduino: mapInput :: (i -> j) -> Pipe j o u m a -> Pipe i o u m a
+ Data.Conduino: mapOutput :: (p -> o) -> Pipe i p u m a -> Pipe i o u m a
+ Data.Conduino: mapUpRes :: (u -> v) -> Pipe i o v m a -> Pipe i o u m a
+ Data.Conduino: newtype ZipSource m a
+ Data.Conduino: pattern PipeList :: Monad m => ListT m (Maybe a) -> Pipe () a u m ()
+ Data.Conduino: runPipePure :: Pipe () Void Void Identity a -> a
+ Data.Conduino: toListT :: Applicative m => Pipe () o u m () -> ListT m (Maybe o)
+ Data.Conduino: trimapPipe :: (i -> j) -> (p -> o) -> (u -> v) -> Pipe j p v m a -> Pipe i o u m a
+ Data.Conduino: unconsZipSource :: Monad m => ZipSource m a -> m (Maybe (Maybe a, ZipSource m a))
+ Data.Conduino: withSource :: Pipe () o u m () -> (Maybe (o, m r) -> m r) -> m r
+ Data.Conduino: yield :: o -> Pipe i o u m ()
+ Data.Conduino: zipSink :: Monad m => Pipe i Void u m (a -> b) -> Pipe i Void u m a -> Pipe i Void u m b
+ Data.Conduino.Combinators: concat :: Foldable t => Pipe (t i) i u m u
+ Data.Conduino.Combinators: concatMap :: Foldable t => (i -> t o) -> Pipe i o u m u
+ Data.Conduino.Combinators: consecutive :: Int -> Pipe i (Seq i) u m u
+ Data.Conduino.Combinators: drop :: Int -> Pipe i o u m ()
+ Data.Conduino.Combinators: dropWhile :: (i -> Bool) -> Pipe i o u m ()
+ Data.Conduino.Combinators: filter :: (i -> Bool) -> Pipe i i u m u
+ Data.Conduino.Combinators: fold :: Monoid a => Pipe a o u m a
+ Data.Conduino.Combinators: foldMap :: Monoid a => (i -> a) -> Pipe i o u m a
+ Data.Conduino.Combinators: foldl :: (b -> a -> b) -> b -> Pipe a o u m b
+ Data.Conduino.Combinators: foldr :: (a -> b -> b) -> b -> Pipe a o u m b
+ Data.Conduino.Combinators: iterate :: (o -> o) -> o -> Pipe i o u m a
+ Data.Conduino.Combinators: iterateEither :: (o -> Either a o) -> o -> Pipe i o u m a
+ Data.Conduino.Combinators: iterateMaybe :: (o -> Maybe o) -> o -> Pipe i o u m ()
+ Data.Conduino.Combinators: last :: Pipe i o u m (Maybe i)
+ Data.Conduino.Combinators: map :: (i -> o) -> Pipe i o u m u
+ Data.Conduino.Combinators: mapAccum :: (i -> s -> (s, o)) -> s -> Pipe i o u m u
+ Data.Conduino.Combinators: mapM :: Monad m => (i -> m o) -> Pipe i o u m u
+ Data.Conduino.Combinators: pairs :: Pipe i (i, i) u m u
+ Data.Conduino.Combinators: repeat :: o -> Pipe i o u m a
+ Data.Conduino.Combinators: repeatEitherM :: Monad m => m (Either a o) -> Pipe i o u m a
+ Data.Conduino.Combinators: repeatM :: Monad m => m o -> Pipe i o u m a
+ Data.Conduino.Combinators: repeatMaybeM :: Monad m => m (Maybe o) -> Pipe i o u m ()
+ Data.Conduino.Combinators: replicate :: Int -> o -> Pipe i o u m ()
+ Data.Conduino.Combinators: replicateM :: Monad m => Int -> m o -> Pipe i o u m ()
+ Data.Conduino.Combinators: scan :: (o -> i -> o) -> o -> Pipe i o u m u
+ Data.Conduino.Combinators: sinkHandle :: MonadIO m => Handle -> Pipe ByteString o u m ()
+ Data.Conduino.Combinators: sinkList :: Pipe i o u m [i]
+ Data.Conduino.Combinators: sinkNull :: Pipe i o u m ()
+ Data.Conduino.Combinators: sourceHandle :: MonadIO m => Handle -> Pipe i ByteString u m ()
+ Data.Conduino.Combinators: sourceHandleLines :: MonadIO m => Handle -> Pipe i String u m ()
+ Data.Conduino.Combinators: sourceList :: Foldable t => t a -> Pipe i a u m ()
+ Data.Conduino.Combinators: stderr :: MonadIO m => Pipe ByteString o u m ()
+ Data.Conduino.Combinators: stdin :: MonadIO m => Pipe i ByteString u m ()
+ Data.Conduino.Combinators: stdinLines :: MonadIO m => Pipe i String u m ()
+ Data.Conduino.Combinators: stdout :: MonadIO m => Pipe ByteString o u m ()
+ Data.Conduino.Combinators: take :: Int -> Pipe i i u m ()
+ Data.Conduino.Combinators: takeWhile :: (i -> Bool) -> Pipe i i u m ()
+ Data.Conduino.Combinators: unfold :: (s -> (o, s)) -> s -> Pipe i o u m a
+ Data.Conduino.Combinators: unfoldEither :: (s -> Either a (o, s)) -> s -> Pipe i o u m a
+ Data.Conduino.Combinators: unfoldMaybe :: (s -> Maybe (o, s)) -> s -> Pipe i o u m ()
+ Data.Conduino.Internal: PAwaitF :: (u -> a) -> (i -> a) -> PipeF i o u a
+ Data.Conduino.Internal: PYieldF :: o -> a -> PipeF i o u a
+ Data.Conduino.Internal: Pipe :: FT (PipeF i o u) m a -> Pipe i o u m a
+ Data.Conduino.Internal: [pipeFree] :: Pipe i o u m a -> FT (PipeF i o u) m a
+ Data.Conduino.Internal: awaitEither :: Pipe i o u m (Either u i)
+ Data.Conduino.Internal: data PipeF i o u a
+ Data.Conduino.Internal: fromRecPipe :: Monad m => RecPipe i o u m a -> Pipe i o u m a
+ Data.Conduino.Internal: 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.Internal: instance (Control.Monad.Reader.Class.MonadReader r m, Control.Monad.Writer.Class.MonadWriter w m, Control.Monad.State.Class.MonadState s m) => Control.Monad.RWS.Class.MonadRWS r w s (Data.Conduino.Internal.Pipe i o u m)
+ Data.Conduino.Internal: instance Control.Monad.Error.Class.MonadError e m => Control.Monad.Error.Class.MonadError e (Data.Conduino.Internal.Pipe i o u m)
+ Data.Conduino.Internal: instance Control.Monad.Fail.MonadFail m => Control.Monad.Fail.MonadFail (Data.Conduino.Internal.Pipe i o u m)
+ Data.Conduino.Internal: instance Control.Monad.Free.Class.MonadFree (Data.Conduino.Internal.PipeF i o u) (Data.Conduino.Internal.Pipe i o u m)
+ Data.Conduino.Internal: instance Control.Monad.IO.Class.MonadIO m => Control.Monad.IO.Class.MonadIO (Data.Conduino.Internal.Pipe i o u m)
+ Data.Conduino.Internal: instance Control.Monad.Reader.Class.MonadReader r m => Control.Monad.Reader.Class.MonadReader r (Data.Conduino.Internal.Pipe i o u m)
+ Data.Conduino.Internal: instance Control.Monad.State.Class.MonadState s m => Control.Monad.State.Class.MonadState s (Data.Conduino.Internal.Pipe i o u m)
+ Data.Conduino.Internal: instance Control.Monad.Trans.Class.MonadTrans (Data.Conduino.Internal.Pipe i o u)
+ Data.Conduino.Internal: instance Control.Monad.Writer.Class.MonadWriter w m => Control.Monad.Writer.Class.MonadWriter w (Data.Conduino.Internal.Pipe i o u m)
+ Data.Conduino.Internal: instance GHC.Base.Applicative (Data.Conduino.Internal.Pipe i o u m)
+ Data.Conduino.Internal: instance GHC.Base.Functor (Data.Conduino.Internal.Pipe i o u m)
+ Data.Conduino.Internal: instance GHC.Base.Functor (Data.Conduino.Internal.PipeF i o u)
+ Data.Conduino.Internal: instance GHC.Base.Monad (Data.Conduino.Internal.Pipe i o u m)
+ Data.Conduino.Internal: mapInput :: (i -> j) -> Pipe j o u m a -> Pipe i o u m a
+ Data.Conduino.Internal: mapOutput :: (p -> o) -> Pipe i p u m a -> Pipe i o u m a
+ Data.Conduino.Internal: mapUpRes :: (u -> v) -> Pipe i o v m a -> Pipe i o u m a
+ Data.Conduino.Internal: newtype Pipe i o u m a
+ Data.Conduino.Internal: toRecPipe :: Monad m => Pipe i o u m a -> RecPipe i o u m a
+ Data.Conduino.Internal: trimapPipe :: (i -> j) -> (p -> o) -> (u -> v) -> Pipe j p v m a -> Pipe i o u m a
+ Data.Conduino.Internal: type RecPipe i o u = FreeT (PipeF i o u)
+ Data.Conduino.Internal: yield :: o -> Pipe i o u m ()
- Data.Conduino: awaitEither :: Pipe i o u m (Either i u)
+ Data.Conduino: awaitEither :: Pipe i o u m (Either u i)

Files

CHANGELOG.md view
@@ -1,3 +1,17 @@-# Changelog for conduino+Changelog+========= -## Unreleased changes+Version 0.2.0.0+---------------++*October 30, 2019*++<https://github.com/mstksg/conduino/releases/tag/v0.2.0.0>++*   Initial release++Version 0.1.0.0+---------------++(Accidental incomplete release made by mistake)+
README.md view
@@ -1,1 +1,135 @@ # conduino++A lightweight continuation-based stream processing library.++It is similar in nature to pipes and conduit, but useful if you just want+something quick to manage composable stream processing without focus on IO.++## 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:++```haskell+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.++## Details and usage++API-wise, is closer to *conduit* than *pipes*.  Pull-based, where the main+"running" function is:++```haskell+runPipe :: Pipe () Void u m a -> m a+```++That is, the "production" and "consumption" is integrated into one single pipe,+and then run all at once.  Contrast this to *pipes*, where consumption is not+integrated into the pipe, but rather your choice of "runner" determines how+your pipe is consumed.++One extra advantage over *conduit* is that we have the ability to model pipes+that will never stop producing output, so we can have an `await` function that+can reliably fetch items upstream.  This matches more *pipes*-style requests.++For a `Pipe i o u m a`, you have:++*    `i`: Type of input stream (the things you can `await`)+*    `o`: Type of output stream (the things you `yield`)+*    `u`: Type of the *result* of the upstream pipe (Outputted when upstream+     pipe terminates)+*    `m`: Underlying monad (the things you can `lift`)+*    `a`: Result type when pipe terminates (outputted when finished, with+     `pure` or `return`)++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` 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 `u` is `Void`, then the pipe's upstream is limitless, and never+    terminates.  This means that you can use `awaitSurely` instead of `await`,+    to get await a value that is guaranteed to come.  You'll get an `i` instead+    of a `Maybe i`.++    ```haskell+    await       :: Pipe i o u m (Maybe i)+    awaitsurely :: Pipe i o Void m 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 `runPipe` will also never+    terminate. If it is a source, it means that if you chain something+    downstream with `.|`, that downstream pipe can use `awaitSurely` to+    guarantee something being passed down.++Usually you would use it by chaining together pipes with `.|` and then running+the result with `runPipe`.++```haskell+runPipe $ someSource+       .| somePipe+       .| someOtherPipe+       .| someSink+```+++## Why does this package exist?++This package is taking some code I've used some closed-source projects and+pulling it out as a full library.  I wrote it, despite the existence of *pipes*+and *conduit*, because:++1.  I wanted conduit-style semantics for stream composition (source - producer -+    sink all in one package).+2.  I wanted type-enforced guaranteed "awaits" based on type-enforced+    guaranteed infinite producers.+3.  I wanted to be able to combine stream processors "in parallel" in+    different ways (`zipSink`, for "and", and `altSink`, for "or").+3.  I wanted something lightweight without the dependencies dealing with IO,+    since I wasn't really doing resource-sensitive IO.++*conduino* is a small, lightweight version that is focused not necessarily on+"effects" streaming, but rather on composable bits of logic.  It is basically a+lightweight version of conduit-style streaming.  It is slightly different from+pipes in terms of API.++One major difference from *conduit* is the `u` parameter, which allows for+things like `awaitSurely`, to ensure that upstream pipes will never terminate.++If you need to do some important IO and handle things like managing resources,+or leverage interoperability with existing libraries...switch to a more mature+library like *conduit* or *pipes* immediately :)
conduino.cabal view
@@ -4,12 +4,17 @@ -- -- see: https://github.com/sol/hpack ----- hash: cd71825a7b764b1f7f6f1cd7515dfaecdd7070939ea2baffd66443887143a5bf+-- hash: e4f50c4a474c128ace934290082d6a4eda455370d44bb3efb0192215a4fbcd1b  name:           conduino-version:        0.1.0.0+version:        0.2.0.0 synopsis:       Lightweight composable continuation-based stream processors-description:    Please see the README on GitHub at <https://github.com/mstksg/conduino#readme>+description:    A lightweight continuation-based stream processing library.+                .+                It is similar in nature to pipes and conduit, but useful if you just want+                something quick to manage composable stream processing without focus on IO.+                .+                See README for more information. category:       Control homepage:       https://github.com/mstksg/conduino#readme bug-reports:    https://github.com/mstksg/conduino/issues@@ -18,6 +23,7 @@ copyright:      (c) Justin Le 2019 license:        BSD3 license-file:   LICENSE+tested-with:    GHC >= 8.4 && < 8.10 build-type:     Simple extra-source-files:     README.md@@ -30,14 +36,19 @@ library   exposed-modules:       Data.Conduino-      Lib+      Data.Conduino.Combinators+      Data.Conduino.Internal   other-modules:       Paths_conduino   hs-source-dirs:       src   ghc-options: -Wall -Wcompat -Wredundant-constraints -Werror=incomplete-patterns   build-depends:-      base >=4.7 && <5+      base >=4.11 && <5+    , bytestring+    , containers     , free+    , list-transformer+    , mtl     , transformers   default-language: Haskell2010
src/Data/Conduino.hs view
@@ -2,207 +2,432 @@ {-# LANGUAGE FlexibleContexts           #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE LambdaCase                 #-}+{-# LANGUAGE PatternSynonyms            #-}+{-# LANGUAGE RankNTypes                 #-} {-# LANGUAGE ScopedTypeVariables        #-}-{-# LANGUAGE TemplateHaskell            #-} {-# LANGUAGE TypeInType                 #-}+{-# LANGUAGE ViewPatterns               #-} +-- |+-- Module      : Data.Conduino+-- Copyright   : (c) Justin Le 2019+-- License     : BSD3+--+-- Maintainer  : justin@jle.im+-- Stability   : experimental+-- Portability : non-portable+--+-- Base API for 'Pipe'.  See documentation for 'Pipe', '.|', and 'runPipe'+-- for information on usage.+--+-- 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.+-- module Data.Conduino (     Pipe   , (.|)-  , runPipe-  , awaitEither, await, awaitSurely-  , repeatM, unfoldP, unfoldPForever, iterateP, sourceList-  , awaitForever, mapP, mapMP-  , dropP-  , foldrP, sinkList+  , runPipe, runPipePure+  , awaitEither, await, awaitWith, awaitSurely, awaitForever, yield+  , mapInput, mapOutput, mapUpRes, trimapPipe+  -- * Wrappers+  , ZipSource(..)+  , unconsZipSource   , ZipSink(..)+  , zipSink, altSink+  -- * Generators+  , toListT, fromListT+  , pattern PipeList+  , withSource, genSource   ) where  import           Control.Applicative import           Control.Monad-import           Control.Monad.Free.Class-import           Control.Monad.Free.TH import           Control.Monad.Trans.Class import           Control.Monad.Trans.Free        (FreeT(..), FreeF(..)) import           Control.Monad.Trans.Free.Church-import           Data.Foldable+import           Data.Conduino.Internal+import           Data.Functor+import           Data.Functor.Identity import           Data.Void--data PipeF i o u a =-      PAwaitF (i -> a) (u -> a)-    | PYieldF o a-  deriving Functor--makeFree ''PipeF+import           List.Transformer                (ListT(..), Step(..))+import qualified List.Transformer                as LT --- | Similar to Conduit+-- | Await input from upstream.  Will block until upstream 'yield's. ----- *  @i@: Type of input stream--- *  @o@: Type of output stream--- *  @u@: Type of the /result/ of the upstream pipe (Outputted when---    upstream pipe finishes)--- *  @m@: Underlying monad--- *  @a@: Result type (Outputted when finished)+-- Will return 'Nothing' if the upstream pipe finishes and terminates. ----- Some specializations:+-- If the upstream pipe never terminates, then you can use 'awaitSurely' to+-- guarantee a result. ----- *  A pipe is a /source/ if @i@ is '()': it doesn't need anything to go---    pump out items.+-- Will always return 'Just' if @u@ is 'Void'.+await :: Pipe i o u m (Maybe i)+await = either (const Nothing) Just <$> awaitEither++-- | 'await', but directly chaining a continuation if the 'await' was+-- succesful. -----    If a pipe is source and @a@ is 'Void', it means that it will---    produce forever.+-- The await will always be succesful if @u@ is 'Void'. ----- *  A pipe is a /sink/ if @o@ is 'Void': it will never yield anything---    else downstream.+-- This is a way of writing code in a way that is agnostic to how the+-- upstream pipe terminates.+awaitWith :: (i -> Pipe i o u m u) -> Pipe i o u m u+awaitWith f = awaitEither >>= \case+    Left  r -> pure r+    Right x -> f x++-- | Await input from upstream where the upstream pipe is guaranteed to+-- never terminate. ----- *  If a pipe is both a source and a sink, it is an /effect/.+-- A common type error will occur if @u@ (upstream pipe result type) is not+-- 'Void' -- it might be @()@ or some non-'Void' type.  This means that the+-- upstream pipe terminates, so awaiting cannot be assured. ----- *  Normally you can ask for input upstream with 'await', which returns---    'Nothing' if the pipe upstream stops producing.  However, if @u@ is---    'Void', it means that the pipe upstream will never stop, so you can---    use 'awaitSurely' to get a guaranteed answer.-newtype Pipe i o u m a = Pipe { pipeFree :: FT (PipeF i o u) m a }-  deriving (Functor, Applicative, Monad, MonadTrans, MonadFree (PipeF i o u))--type Source o  = Pipe () o-type Sink   i  = Pipe i  Void-type Effect    = Pipe () Void-type Forever p = p Void--awaitEither :: Pipe i o u m (Either i u)-awaitEither = pAwaitF--yield :: o -> Pipe i o u m ()-yield = pYieldF+-- In that case, either change your upstream pipe to be one that never+-- terminates (which is most likely not possible), or use 'await' instead+-- of 'awaitSurely'.+awaitSurely :: Pipe i o Void m i+awaitSurely = either absurd id <$> awaitEither -await :: Pipe i o u m (Maybe i)-await = either Just (const Nothing) <$> awaitEither+-- | A useful utility function over repeated 'await's.  Will repeatedly+-- 'await' and then continue with the given pipe whenever the upstream pipe+-- yields.+--+-- Can be used to implement many pipe combinators:+--+-- @+-- 'Data.Conduino.Combinators.map' f = 'awaitForever' $ \x -> 'yield' (f x)+-- @+awaitForever :: (i -> Pipe i o u m a) -> Pipe i o u m u+awaitForever = awaitForeverWith pure -awaitSurely :: Pipe i o Void m i-awaitSurely = either id absurd <$> awaitEither+-- | 'awaitForever', but with a way to handle the result of the+-- upstream pipe, which will be called when the upstream pipe stops+-- producing.+awaitForeverWith+    :: (u -> Pipe () o u m b)       -- ^ how to handle upstream ending, transitioning to a source+    -> (i -> Pipe i o u m a)        -- ^ how to handle upstream output+    -> Pipe i o u m b+awaitForeverWith f g = go+  where+    go = awaitEither >>= \case+      Left x  -> mapInput (const ()) $ f x+      Right x -> g x *> go +-- | Run a pipe that is both a source and a sink (an "effect") into the+-- effect that it represents.+--+-- Usually you wouild construct this using something like:+--+-- @+-- 'runPipe' $ someSource+--        '.|' somePipe+--        .| someOtherPipe+--        .| someSink+-- @+--+-- 'runPipe' will produce the result of that final sink.+--+-- Some common errors you might receive:+--+-- *  @i@ is not @()@: If you give a pipe where the first parameter+--    ("input") is not @()@, it means that your pipe is not a producer.+--    Pre-compose it (using '.|') with a producer of the type you need.+--+--    For example, if you have a @myPipe :: 'Pipe' 'Int' o u m a@, this is+--    a pipe that is awaiting 'Int's from upstream.  Pre-compose with+--    a producer of 'Int's, like @'Data.Conduino.Combinators.sourceList'+--    [1,2,3] '.|' myPipe@, in order to be able to run it.+--+-- *  @o@ is not 'Void': If you give a pipe where the second parameter+--    ("output") is not 'Void', it means that your pipe is not a consumer.+--    Post-compose it (using '.|') with a consumer of the type you need.+--+--    For example, if you have @myPipe :: 'Pipe' i 'Int' u m a@, this is+--    a pipe that is yielding 'Int's downstream that are going unhandled.+--    Post-compose it a consumer of 'Int's, like @myPipe '.|'+--    'Data.Conduino.foldl' (+) 0@, in order to be able to run it.+--+--    If you just want to ignore all downstream yields, post-compose with+--    'Data.Conduino.Combinators.sinkNull'.+-- runPipe :: Monad m => Pipe () Void u m a -> m a runPipe = iterT go . pipeFree   where     go = \case-      PAwaitF f _ -> f ()+      PAwaitF _ f -> f ()       PYieldF o _ -> absurd o --- can this be done without going through FreeT?+-- | 'runPipe' when the underlying monad is 'Identity', and so has no+-- effects.+runPipePure :: Pipe () Void Void Identity a -> a+runPipePure = runIdentity . runPipe++-- | The main operator for chaining pipes together.  @pipe1 .| pipe2@ will+-- connect the output of @pipe1@ to the input of @pipe2@.+--+-- "Running" a pipe will draw from @pipe2@, and if @pipe2@ ever asks for+-- input (with 'await' or something similar), it will block until @pipe1@+-- outputs something (or signals termination).+--+-- The structure of a full pipeline usually looks like:+--+-- @+-- 'runPipe' $ someSource+--        '.|' somePipe+--        .| someOtherPipe+--        .| someSink+-- @+--+-- Where you route a source into a series of pipes, which eventually ends+-- up at a sink.  'runPipe' will then produce the result of that sink. (.|)     :: Monad m     => Pipe a b u m v     -> Pipe b c v m r     -> Pipe a c u m r Pipe p .| Pipe q = Pipe $ toFT $ compPipe_ (fromFT p) (fromFT q)+infixr 2 .|  compPipe_     :: forall a b c u v m r. (Monad m)-    => FreeT (PipeF a b u) m v-    -> FreeT (PipeF b c v) m r-    -> FreeT (PipeF a c u) m r-compPipe_ p q = FreeT $ runFreeT q >>= \case+    => RecPipe a b u m v+    -> RecPipe b c v m r+    -> RecPipe a c u m r+compPipe_ p q = FreeT $ runFreeT q >>= \qq -> case qq of     Pure x             -> pure . Pure $ x-    Free (PAwaitF f g) -> runFreeT p >>= \case-      Pure x'              -> runFreeT $ compPipe_ p  (g x')-      Free (PAwaitF f' g') -> pure . Free $ PAwaitF ((`compPipe_` q) . f')-                                                    ((`compPipe_` q) . g')-      Free (PYieldF x' y') -> runFreeT $ compPipe_ y' (f x')+    Free (PAwaitF f g) -> runFreeT p >>= \pp -> case pp of+      Pure x'              -> runFreeT $ compPipe_ (FreeT (pure pp)) (f x')+      Free (PAwaitF f' g') -> pure . Free $ PAwaitF ((`compPipe_` FreeT (pure qq)) . f')+                                                    ((`compPipe_` FreeT (pure qq)) . g')+      Free (PYieldF x' y') -> runFreeT $ compPipe_ y' (g x')     Free (PYieldF x y) -> pure . Free $ PYieldF x (compPipe_ p y)-infixr 2 .| -unfoldP :: (b -> Maybe (a, b)) -> b -> Pipe i a u m ()-unfoldP f = go-  where-    go z = case f z of-      Nothing      -> pure ()-      Just (x, z') -> yield x *> go z'+-- | A newtype wrapper over a source (@'Pipe' () o 'Void'@) that gives it an+-- alternative 'Applicative' and 'Alternative' instance, matching "ListT+-- done right".+--+-- '<*>' will pair up each output that the sources produce: if you 'await'+-- a value from downstream, it will wait until both paired sources yield+-- before passing them on together.+--+-- '<|>' will completely exhaust the first source before moving on to the+-- next source.+--+-- 'ZipSource' is effectively equivalent to "ListT done right", the true+-- List Monad transformer.  '<|>' is concatentation.  You can use this type+-- with 'lift' to lift a yielding action and '<|>' to sequence yields to+-- implement the pattern described in+-- <http://www.haskellforall.com/2014/11/how-to-build-library-agnostic-streaming.html>,+-- where you can write streaming producers in a polymorphic way, and have+-- it run with pipes, conduit, etc.+--+-- The main difference is that its 'Applicative' instance ("zipping") is+-- different from the traditional 'Applicative' instance for 'ListT'+-- ("all combinations").  Effectively this becomes like a "zipping"+-- 'Applicative' instance for 'ListT'.+--+-- If you want a 'Monad' (or 'Control.Monad.IO.Class.MonadIO') instance,+-- use 'ListT' instead, and convert using 'toListT'/'fromListT' or the+-- 'PipeList' pattern/constructor.+newtype ZipSource m a = ZipSource { getZipSource :: Pipe () a Void m () } -unfoldPForever :: (b -> (a, b)) -> b -> Pipe i a u m r-unfoldPForever f = go+-- | A source is equivalent to a 'ListT' producing a 'Maybe'; this pattern+-- synonym lets you treat it as such.  It essentialyl wraps over 'toListT'+-- and 'fromListT'.+pattern PipeList :: Monad m => ListT m (Maybe a) -> Pipe () a u m ()+pattern PipeList xs <- (toListT->xs)   where-    go z = yield x *> go z'-      where-        (x, z') = f z+    PipeList xs = fromListT xs+{-# COMPLETE PipeList #-} -iterateP :: (a -> a) -> a -> Pipe i a u m r-iterateP f = unfoldPForever (join (,) . f)+instance Functor (ZipSource m) where+    fmap f = ZipSource . mapOutput f . getZipSource -sourceList :: Foldable t => t a -> Pipe i a u m ()-sourceList = traverse_ yield+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) -repeatM :: Monad m => m o -> Pipe i o u m u-repeatM x = go-  where-    go = (yield =<< lift x) *> go+concatListT :: Monad m => ListT m (Maybe a) -> ListT m a+concatListT xs = ListT $ next xs >>= \case+    Nil              -> pure Nil+    Cons Nothing  ys -> next (concatListT ys)+    Cons (Just y) ys -> pure $ Cons y (concatListT ys) -awaitForever :: (i -> Pipe i o u m a) -> Pipe i o u m u-awaitForever f = go-  where-    go = awaitEither >>= \case-      Left x  -> f x *> go-      Right x -> pure x+instance Monad m => Alternative (ZipSource m) where+    empty = ZipSource $ pure ()+    ZipSource p <|> ZipSource q = ZipSource (p *> q) --- finishPipe---     :: u---     -> Pipe i o u    m a---     -> Pipe i o Void m a+instance MonadTrans ZipSource where+    lift = ZipSource . (yield =<<) . lift -mapP :: (a -> b) -> Pipe a b u m u-mapP f = awaitForever (yield . f)+-- | A source is essentially equivalent to 'ListT' producing a 'Maybe'+-- result.  This converts it to the 'ListT' it encodes.+--+-- See 'ZipSource' for a wrapper over 'Pipe' that gives the right 'Functor'+-- and 'Alternative' instances.+toListT+    :: Applicative m+    => Pipe () o u m ()+    -> ListT m (Maybe o)+toListT p = ListT $ runFT (pipeFree p)+    (\_ -> pure Nil)+    (\pNext -> \case+        PAwaitF _ g -> pure $ Cons Nothing  (ListT . pNext $ g ())+        PYieldF x y -> pure $ Cons (Just x) (ListT . pNext $ y   )+    ) -mapMP :: Monad m => (a -> m b) -> Pipe a b u m u-mapMP f = awaitForever ((yield =<<) . lift . f)+-- | A source is essentially 'ListT' producing a 'Maybe' result.  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 (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) -dropP :: Int -> Pipe i o u m ()-dropP n = replicateM_ n await+---- | 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) -foldrP :: (a -> b -> b) -> b -> Pipe a Void u m b-foldrP f z = go-  where-    go = await >>= \case-      Nothing -> pure z-      Just x  -> f x <$> go+-- | Given a "generator" of @o@ in @m@, return a /source/ that that+-- generator encodes.  Is the inverse of 'withSource'.+--+-- The generator is essentially a church-encoded 'ListT'.+genSource+    :: (forall r. (Maybe (o, m r) -> m r) -> m r)+    -> Pipe i o u m ()+genSource f = Pipe $ FT $ \pDone pFree -> f $ \case+    Nothing      -> pDone ()+    Just (x, xs) -> pFree id (PYieldF x xs) -sinkList :: Pipe i Void u m [i]-sinkList = foldrP (:) []+-- | A source can be "run" by providing a continuation to handle and+-- sequence each of its outputs.  Is ths inverse of 'genSource'.+--+-- This essentially turns a pipe into a church-encoded 'ListT'.+withSource+    :: Pipe () o u m ()+    -> (Maybe (o, m r) -> m r)    -- ^ handler ('Nothing' = done, @'Just' (x, next)@ = yielded value and next action+    -> m r+withSource p f = runFT (pipeFree p)+    (\_ -> f Nothing)+    (\pNext -> \case+        PAwaitF _ g -> pNext $ g ()+        PYieldF x y -> f (Just (x, pNext y))+    ) +-- | 'ZipSource' is effectively 'ListT' returning a 'Maybe'.  As such, you+-- can use 'unconsZipSource' to "peel off" the first yielded item, if it+-- exists, and return the "rest of the list".+unconsZipSource+    :: Monad m+    => ZipSource m a+    -> m (Maybe (Maybe a, ZipSource m a))+unconsZipSource (ZipSource (PipeList p)) = next p <&> \case+    Cons x xs -> Just (x, ZipSource (PipeList xs))+    Nil       -> Nothing++-- | A newtype wrapper over a sink (@'Pipe' i 'Void'@) that gives it an+-- alternative 'Applicative' and 'Alternative' instance.+--+-- '<*>' will distribute input over both sinks, and output a final result+-- once both sinks finish.+--+-- '<|>' will distribute input over both sinks, and output a final result+-- as soon as one or the other finishes. newtype ZipSink i u m a = ZipSink { getZipSink :: Pipe i Void u m a }   deriving Functor  zipSink_     :: Monad m-    => FreeT (PipeF i Void u) m (a -> b)-    -> FreeT (PipeF i Void u) m a-    -> FreeT (PipeF i Void u) m b-zipSink_ p q = FreeT $ go <$> runFreeT p <*> runFreeT q-  where-    go = \case-      Pure x             -> \case-        Pure x'              -> Pure $ x x'-        Free (PAwaitF f' g') -> Free $ PAwaitF (zipSink_ p . f') (zipSink_ p . g')-        Free (PYieldF x' _ ) -> absurd x'-      Free (PAwaitF f g) -> \case-        Pure _               -> Free $ PAwaitF ((`zipSink_` q) . f) ((`zipSink_` q) . g)-        Free (PAwaitF f' g') -> Free $ PAwaitF (zipSink_ <$> f <*> f') (zipSink_ <$> g <*> g')-        Free (PYieldF x' _ ) -> absurd x'-      Free (PYieldF x _) -> absurd x+    => RecPipe i Void u m (a -> b)+    -> RecPipe i Void u m a+    -> RecPipe i Void u m b+zipSink_ p q = FreeT $ runFreeT p >>= \pp -> case pp of+    Pure x             -> runFreeT q >>= \case+      Pure x'              -> pure . Pure $ x x'+      Free (PAwaitF f' g') -> pure . Free $+        PAwaitF (zipSink_ (FreeT (pure pp)) . f')+                (zipSink_ (FreeT (pure pp)) . g')+      Free (PYieldF x' _ ) -> absurd x'+    Free (PAwaitF f g) -> runFreeT q >>= \qq -> case qq of+      Pure _               -> pure . Free $+        PAwaitF ((`zipSink_` FreeT (pure qq)) . f)+                ((`zipSink_` FreeT (pure qq)) . g)+      Free (PAwaitF f' g') -> pure . Free $+        PAwaitF (zipSink_ <$> f <*> f') (zipSink_ <$> g <*> g')+      Free (PYieldF x' _ ) -> absurd x'+    Free (PYieldF x _) -> absurd x  altSink_     :: Monad m-    => FreeT (PipeF i Void u) m a-    -> FreeT (PipeF i Void u) m a-    -> FreeT (PipeF i Void u) m a-altSink_ p q = FreeT $ go <$> runFreeT p <*> runFreeT q-  where-    go = \case-      Pure x             -> \_ -> Pure x-      Free (PAwaitF f g) -> \case-        Pure x'              -> Pure x'-        Free (PAwaitF f' g') -> Free $ PAwaitF (altSink_ <$> f <*> f') (altSink_ <$> g <*> g')-        Free (PYieldF x' _ ) -> absurd x'-      Free (PYieldF x _) -> absurd x+    => RecPipe i Void u m a+    -> RecPipe i Void u m a+    -> RecPipe i Void u m a+altSink_ p q = FreeT $ runFreeT p >>= \case+    Pure x             -> pure . Pure $ x+    Free (PAwaitF f g) -> runFreeT q <&> \case+      Pure x'              -> Pure x'+      Free (PAwaitF f' g') -> Free $ PAwaitF (altSink_ <$> f <*> f') (altSink_ <$> g <*> g')+      Free (PYieldF x' _ ) -> absurd x'+    Free (PYieldF x _) -> absurd x +-- | Distribute input to both sinks, and finishes with the final result+-- once both finish.+--+-- Forms an identity with 'pure'. zipSink     :: Monad m     => Pipe i Void u m (a -> b)@@ -210,6 +435,8 @@     -> Pipe i Void u m b zipSink (Pipe p) (Pipe q) = Pipe $ toFT $ zipSink_ (fromFT p) (fromFT q) +-- | Distribute input to both sinks, and finishes with the result of+-- the one that finishes first. altSink     :: Monad m     => Pipe i Void u m a@@ -233,3 +460,6 @@       where         go = forever await     ZipSink p <|> ZipSink q = ZipSink $ altSink p q++instance MonadTrans (ZipSink i u) where+    lift = ZipSink . lift
+ src/Data/Conduino/Combinators.hs view
@@ -0,0 +1,488 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE LambdaCase   #-}+{-# LANGUAGE RankNTypes   #-}++-- |+-- Module      : Data.Conduino.Combinators+-- Copyright   : (c) Justin Le 2019+-- License     : BSD3+--+-- Maintainer  : justin@jle.im+-- Stability   : experimental+-- Portability : non-portable+--+-- A basic collection of base 'Pipe's that serve as a "prelude" for the+-- package.  This module is meant to be imported qualified.+--+-- > import qualified Data.Conduino.Combinators as C+--+module Data.Conduino.Combinators (+  -- * Sources+  -- ** Pure+  -- *** Infinite+    unfold+  , iterate+  , repeat+  -- *** Finite+  , unfoldMaybe+  , unfoldEither+  , iterateMaybe+  , iterateEither+  , sourceList+  , replicate+  -- ** Monadic+  -- *** Infinite+  , repeatM+  -- *** Finite+  , repeatMaybeM+  , repeatEitherM+  , replicateM+  , sourceHandleLines+  , stdinLines+  , sourceHandle+  , stdin+  -- * Pipes+  , map+  , mapM+  , scan+  , mapAccum+  , take+  , takeWhile+  , filter+  , concatMap+  , concat+  , pairs+  , consecutive+  -- * Sinks+  -- ** Pure+  , drop+  , dropWhile+  , foldr+  , foldl+  , foldMap+  , fold+  , sinkNull+  , sinkList+  , last+  -- ** Monadic+  , sinkHandle+  , stdout+  , stderr+  ) where++import           Control.Applicative+import           Control.Exception+import           Control.Monad hiding          (mapM, replicateM)+import           Control.Monad.IO.Class+import           Control.Monad.Trans.Class+import           Control.Monad.Trans.Maybe+import           Data.Conduino+import           Data.Either+import           Data.Foldable hiding          (foldr, foldl, fold, concat, concatMap, foldMap)+import           Data.Maybe+import           Data.Semigroup+import           Prelude hiding                (map, iterate, mapM, replicate, repeat, foldr, drop, foldl, last, take, concatMap, filter, concat, takeWhile, dropWhile, foldMap)+import           System.IO.Error+import qualified Data.ByteString               as BS+import qualified Data.ByteString.Lazy.Internal as BSL+import qualified Data.Sequence                 as Seq+import qualified System.IO                     as S++-- | A version of 'unfoldMaybe' that can choose the "result" value by+-- passing it in as 'Left'.+unfoldEither+    :: (s -> Either a (o, s))+    -> s+    -> Pipe i o u m a+unfoldEither f = go+  where+    go z = case f z of+      Left  r       -> pure r+      Right (x, z') -> yield x *> go z'++-- | A version of 'unfold' that can terminate and end by returning+-- 'Nothing'.+unfoldMaybe+    :: (s -> Maybe (o, s))+    -> s+    -> Pipe i o u m ()+unfoldMaybe f = unfoldEither (maybe (Left ()) Right . f)++-- | Repeatedly apply an "unfolding" function to a given initial state,+-- yielding the first item in the tuple as output and updating the state as+-- the second item in the tuple.  Goes on forever.  See 'unfoldMaybe' for+-- a version that stops.+unfold+    :: (s -> (o, s))+    -> s+    -> Pipe i o u m a+unfold f = go+  where+    go z = yield x *> go z'+      where+        (x, z') = f z++-- | A version of 'iterateMaybe' that can specify a result value by+-- providing it in the 'Left'.+iterateEither+    :: (o -> Either a o)+    -> o+    -> Pipe i o u m a+iterateEither f = unfoldEither (fmap (join (,)) . f)++-- | A version of 'iterate' that can choose to terminate and stop by+-- returning 'Nothing'.+iterateMaybe+    :: (o -> Maybe o)+    -> o+    -> Pipe i o u m ()+iterateMaybe f = unfoldMaybe (fmap (join (,)) . f)++-- | Repeatedly apply a function to a given starting value and yield each+-- result forever.+--+-- >>> runPipePure $ iterate succ 0+--       .| take 5+--       .| sinkList+--+-- [1,2,3,4,5]+--+-- This doesn't yield the original starting value.  However, you can yield+-- it iterate after:+--+-- >>> runPipePure $ (yield 0 >> iterate succ 0)+--       .| take 5+--       .| sinkList+--+-- [0,1,2,3,4,5]+iterate+    :: (o -> o)+    -> o+    -> Pipe i o u m a+iterate f = unfold (join (,) . f)++-- | Yield every item in a foldable container.+sourceList :: Foldable t => t a -> Pipe i a u m ()+sourceList = traverse_ yield++-- | Repeatedly yield a given item forever.+repeat :: o -> Pipe i o u m a+repeat = forever . yield++-- | Yield a given item a certain number of times.+replicate :: Int -> o -> Pipe i o u m ()+replicate n = replicateM_ n . yield++-- | Like 'repeatMaybeM', but allow specification of a final result type.+repeatEitherM+    :: Monad m+    => m (Either a o)+    -> Pipe i o u m a+repeatEitherM x = go+  where+    go = lift x >>= \case+      Left r  -> pure r+      Right y -> yield y *> go++-- | Repeat a monadic action, yielding the item in the 'Just' every time.+-- As soon as it sees 'Nothing', stop producing forever.+--+-- Remember that each item will only be "executed" when something+-- downstream requests output.+repeatMaybeM+    :: Monad m+    => m (Maybe o)+    -> Pipe i o u m ()+repeatMaybeM = repeatEitherM . fmap (maybe (Left ()) Right)++-- | Repeat a monadic action a given number of times, yielding each result,+-- and then stop producing forever.+--+-- Remember that each item will only be "executed" when something+-- downstream requests output.+replicateM+    :: Monad m+    => Int+    -> m o+    -> Pipe i o u m ()+replicateM n x = replicateM_ n $ lift x >>= yield++-- | Source from each line received from 'stdin'.  This stops as soon as+-- end-of-file is reached, or an empty line is seen.+stdinLines :: MonadIO m => Pipe i String u m ()+stdinLines = sourceHandleLines S.stdin++-- | Source from stdin, yielding bytestrings as they are drawn.  If you+-- want to retrieve each line as a string, see 'stdinLines'.+stdin :: MonadIO m => Pipe i BS.ByteString u m ()+stdin = sourceHandle S.stdin++-- | Source from a given I/O handle, yielding each line drawn as a string.+-- To draw raw bytes, use 'sourceHandle'.+--+-- This stop as soon as end-of-file is reached, or an empty line is seen.+sourceHandleLines+    :: MonadIO m+    => S.Handle+    -> Pipe i String u m ()+sourceHandleLines h = repeatMaybeM $ do+    d <- liftIO $ S.hIsEOF h+    if d+      then pure Nothing+      else liftIO . catchJust+                (guard . isEOFError)+                (mfilter (not . null) . Just <$> S.hGetLine h)+                $ \_ -> pure Nothing++-- | Source from a given I/O handle, yielding bytestrings as they are+-- pulled.  If you want to retrieve each line as a string, see+-- 'sourceHandleLines'.+sourceHandle+    :: MonadIO m+    => S.Handle+    -> Pipe i BS.ByteString u m ()+sourceHandle h = repeatMaybeM+               . fmap (mfilter (not . BS.null) . Just)+               . liftIO+               $ BS.hGetSome h BSL.defaultChunkSize++-- | Sink into a given I/O handle, writing each input to the handle.+sinkHandle+    :: MonadIO m+    => S.Handle+    -> Pipe BS.ByteString o u m ()+sinkHandle h = mapM (liftIO . BS.hPut h)+            .| sinkNull++-- | A sink into stdout.+stdout :: MonadIO m => Pipe BS.ByteString o u m ()+stdout = sinkHandle S.stdout++-- | A sink into stderr.+stderr :: MonadIO m => Pipe BS.ByteString o u m ()+stderr = sinkHandle S.stderr++-- | Repeat a monadic action forever, yielding each output.+--+-- Remember that each item will only be "executed" when something+-- downstream requests output.+repeatM+    :: Monad m+    => m o+    -> Pipe i o u m a+repeatM x = go+  where+    go = (yield =<< lift x) *> go++-- | Process every incoming item with a pure function, and yield its+-- output.+map :: (i -> o) -> Pipe i o u m u+map f = awaitForever (yield . f)++-- | 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)++-- | Map a pure "stateful" function over each incoming item.  Give+-- a function to update the state and return an output and an initial+-- state.+mapAccum+    :: (i -> s -> (s, o))       -- ^ update state and output+    -> s                        -- ^ initial state+    -> Pipe i o u m u+mapAccum f = go+  where+    go !x = awaitWith $ \y ->+        let (!x', !z) = f y x+        in  yield z *> go x'++-- | Like 'foldl', but yields every accumulator value downstream.+--+-- >>> runPipePure $ sourceList [1..10]+--       .| scan (+) 0+--       .| sinkList+-- [1,3,6,10,15,21,28,36,45,55]+-- @+scan+    :: (o -> i -> o)+    -> o+    -> Pipe i o u m u+scan f = go+  where+    go !x = awaitWith $ \y ->+      let x' = f x y+      in  yield x' *> go x'++-- | Yield consecutive pairs of values.+--+-- >>> runPipePure $ sourceList [1..5]+--       .| pairs+--       .| sinkList+-- [(1,2),(2,3),(3,4),(4,5)]+pairs :: Pipe i (i, i) u m u+pairs = awaitWith go+  where+    go x = awaitWith $ \y -> do+      yield (x, y)+      go y++-- | Yield consecutive runs of at most @n@ of values, starting with an+-- empty sequence.+--+-- To get only "full" sequences, pipe with 'filter'.+--+-- >>> runPipePure $ sourceList [1..6]+--       .| consecutive 3+--       .| map toList+--       .| sinkList+-- [[],[1],[1,2],[1,2,3],[2,3,4],[3,4,5],[4,5,6]]+--+-- >>> runPipePure $ sourceList [1..6]+--       .| consecutive 3+--       .| filter ((== 3) . Seq.length)+--       .| map toList+--       .| sinkList+-- [[1,2,3],[2,3,4],[3,4,5],[4,5,6]]+consecutive :: Int -> Pipe i (Seq.Seq i) u m u+consecutive n = go Seq.empty+  where+    go xs = do+      yield xs+      awaitWith $ \y -> go . Seq.drop (Seq.length xs - n + 1) $ (xs Seq.:|> y)+++-- | Let a given number of items pass through the stream uninhibited, and+-- then stop producing forever.+--+-- This is most useful if you sequence a second conduit after it.+--+-- >>> runPipePure $ sourceList [1..8]+--       .| (do take 3 .| map (*2)         -- double the first 3 items+--              map negate                 -- negate the rest+--          )+--       .| sinkList+-- [2,4,6,-4,-5,-6,-7,-8]+take :: Int -> Pipe i i u m ()+take n = void . runMaybeT . replicateM_ n $+    lift . yield =<< MaybeT await++-- | Let elements pass until an element is received that does not satisfy+-- the predicate, then stop producing forever.+--+-- Like 'take', is most useful if you sequence a second conduit after it.+takeWhile :: (i -> Bool) -> Pipe i i u m ()+takeWhile p = go+  where+    go = await >>= \case+      Nothing -> pure ()+      Just x+        | p x       -> yield x *> go+        | otherwise -> pure ()++-- | Only allow values satisfying a predicate to pass.+filter+    :: (i -> Bool)+    -> Pipe i i u m u+filter p = awaitForever $ \x -> when (p x) $ yield x++-- | Map a function returning a container onto every incoming item, and+-- yield all of the outputs from that function.+concatMap+    :: Foldable t+    => (i -> t o)+    -> Pipe i o u m u+concatMap f = awaitForever (sourceList . f)++-- | Take an input of containers and output each of their elements+-- successively.+concat :: Foldable t => Pipe (t i) i u m u+concat = awaitForever sourceList++-- | Right-fold every input into an accumulated value.+--+-- Essentially this builds up a giant continuation that will be run all at+-- once on the final result.+foldr :: (a -> b -> b) -> b -> Pipe a o u m b+foldr f z = go+  where+    go = await >>= \case+      Nothing -> pure z+      Just x  -> f x <$> go++-- | Left-fold every input into an accumulated value.+--+-- Essentially this maintains a state and modifies that state with every+-- input, using the given accumulating function.+foldl :: (b -> a -> b) -> b -> Pipe a o u m b+foldl f = go+  where+    go !z = await >>= \case+      Nothing -> pure z+      Just !x -> go (f z x)++-- | Fold every incoming item monoidally, and return the result once+-- finished.+fold :: Monoid a => Pipe a o u m a+fold = foldl (<>) mempty++-- | Fold every incoming item according to a monoidal projection, and+-- return the result once finished.+--+-- This can be used to implement many useful consumers, like ones that find+-- the sum or the maximum item:+--+-- @+-- sum :: Num i => Pipe i o u m i+-- sum = getSum <$> foldMap Sum+--+-- maximum :: Ord i => Pipe i o u m (Maybe i)+-- maximum = fmap getMax <$> foldMap (Just . Max)+-- @+foldMap :: Monoid a => (i -> a) -> Pipe i o u m a+foldMap f = foldl (\x y -> x <> f y) mempty++-- | Sink every incoming item into a list.+--+-- Note that this keeps the entire list in memory until it is all+-- eventually read.+sinkList :: Pipe i o u m [i]+sinkList = foldr (:) []++-- | Ignore a certain amount of items from the input stream, and then stop+-- producing forever.+--+-- This is most useful if you sequence a second consumer after it:+--+-- >>> runPipePure $ sourceList [1..8]+--       .| (drop 3 >> 'sinkList')+-- [4,5,6,7,8]+drop :: Int -> Pipe i o u m ()+drop n = replicateM_ n await++-- | Ignore items from an input stream as long as they match a predicate.+-- Afterwards, stop producing forever.+--+-- Like for 'drop', is most useful of you sequence a second consumer after+-- it.+dropWhile+    :: (i -> Bool)+    -> Pipe i o u m ()+dropWhile p = go+  where+    go = await >>= \case+      Nothing -> pure ()+      Just x+        | p x       -> go+        | otherwise -> pure ()++-- | Consume an entire input stream and ignore all of its outputs.+sinkNull :: Pipe i o u m ()+sinkNull = await >>= \case+    Nothing -> pure ()+    Just _  -> sinkNull++-- | Get the last item emitted by a stream.+--+-- To get the first item ("head"), use 'await' or 'awaitSurely'.+last :: Pipe i o u m (Maybe i)+last = fmap getLast <$> foldMap (Just . Last)
+ src/Data/Conduino/Internal.hs view
@@ -0,0 +1,223 @@+{-# LANGUAGE CPP                        #-}+{-# LANGUAGE DeriveFunctor              #-}+{-# LANGUAGE FlexibleContexts           #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE LambdaCase                 #-}+{-# LANGUAGE RankNTypes                 #-}+{-# LANGUAGE ScopedTypeVariables        #-}+{-# LANGUAGE TemplateHaskell            #-}+{-# LANGUAGE TypeInType                 #-}+{-# LANGUAGE TypeOperators              #-}+{-# OPTIONS_HADDOCK not-home            #-}++-- |+-- Module      : Data.Conduino.Internal+-- Copyright   : (c) Justin Le 2019+-- License     : BSD3+--+-- Maintainer  : justin@jle.im+-- Stability   : experimental+-- Portability : non-portable+--+-- Internal module exposing the internals of 'Pipe', including its+-- underlying representation and base functor.+--+module Data.Conduino.Internal (+    Pipe(..)+  , PipeF(..)+  , awaitEither+  , yield+  , trimapPipe, mapInput, mapOutput, mapUpRes+  , hoistPipe+  , RecPipe+  , toRecPipe, fromRecPipe+  ) where++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.Church++#if !MIN_VERSION_base(4,13,0)+import           Control.Monad.Fail+#endif++-- | 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' and 'MonadIO'+--    instances that 'FT' gives.+-- *  /Ending/ with a result is implemented by the 'Applicative' instance's+--   'pure' that 'FT' gives.+-- *  Applicative and monadic sequenceing "after a pipe is done" is+--    implemented by the 'Applicative' and 'Monad' instances that 'FT'+--    gives.+--+-- On top of these we implement 'Data.Conduino..|' 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.+data PipeF i o u a =+      PAwaitF (u -> a) (i -> a)+    | PYieldF o a+  deriving Functor++makeFree ''PipeF++-- | Similar to a conduit from the /conduit/ package.+--+-- For a @'Pipe' i o u m a@, you have:+--+-- *  @i@: Type of input stream (the things you can 'Data.Conduino.await')+-- *  @o@: Type of output stream (the things you 'yield')+-- *  @u@: Type of the /result/ of the upstream pipe (Outputted when+--    upstream pipe terminates)+-- *  @m@: Underlying monad (the things you can 'lift')+-- *  @a@: Result type when pipe terminates (outputted when finished, with+--    'pure' or 'return')+--+-- 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@ 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 @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 @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+--    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.+--+-- 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+-- 'Data.Condunio..|' and then running the result with+-- 'Data.Condunio.runPipe'.+--+-- @+-- 'Data.Conduino.runPipe' $ someSource+--        'Data.Conduino..|' somePipe+--        .| someOtherPipe+--        .| someSink+-- @+--+-- See 'Data.Condunio..|' and 'Data.Condunio.runPipe' for more information+-- on usage.+--+-- For a "prelude" of commonly used 'Pipe's, see+-- "Data.Condunio.Combinators".+--+newtype Pipe i o u m a = Pipe { pipeFree :: FT (PipeF i o u) m a }+  deriving+    ( Functor+    , Applicative+    , Monad+    , MonadTrans+    , MonadFree (PipeF i o u)+    , MonadIO+    , MonadState s+    , MonadReader r+    , MonadWriter w+    , MonadError e+    , MonadRWS r w s+    )++instance MonadFail m => MonadFail (Pipe i o u m) where+#if MIN_VERSION_base(4,13,0)+    fail = lift . fail+#else+    fail = lift . Control.Monad.Fail.fail+#endif++-- | Await on upstream output.  Will block until it receives an @i@+-- (expected input type) or a @u@ if the upstream pipe terminates.+awaitEither :: Pipe i o u m (Either u i)+awaitEither = pAwaitF++-- | Send output downstream.+yield :: o -> Pipe i o u m ()+yield = pYieldF++-- | Map over the input type, output type, and upstream result type.+--+-- If you want to map over the result type, use 'fmap'.+trimapPipe+    :: (i -> j)+    -> (p -> o)+    -> (u -> v)+    -> Pipe j p v m a+    -> Pipe i o u m a+trimapPipe f g h = Pipe . transFT go . pipeFree+  where+    go = \case+      PAwaitF a b -> PAwaitF (a . h) (b . f)+      PYieldF a x -> PYieldF (g a) x++-- | Transform the underlying monad of a pipe.+hoistPipe+    :: (Monad m, Monad n)+    => (forall x. m x -> n x)+    -> Pipe i o u m a+    -> Pipe i o u n a+hoistPipe f = Pipe . hoistFT f . pipeFree++-- | (Contravariantly) map over the expected input type.+mapInput :: (i -> j) -> Pipe j o u m a -> Pipe i o u m a+mapInput f = trimapPipe f id id++-- | Map over the downstream output type.+--+-- If you want to map over the result type, use 'fmap'.+mapOutput :: (p -> o) -> Pipe i p u m a -> Pipe i o u m a+mapOutput f = trimapPipe id f id++-- | (Contravariantly) map over the upstream result type.+mapUpRes :: (u -> v) -> Pipe i o v m a -> Pipe i o u m a+mapUpRes = trimapPipe id id++-- | A version of 'Pipe' that uses explicit, concrete recursion instead of+-- church-encoding like 'Pipe'.  Some functions --- especially ones that+-- combine multiple pipes into one --- are easier to implement in this+-- form.+type RecPipe i o u = FreeT (PipeF i o u)++-- | Convert from a 'Pipe' to a 'RecPipe'.  While most of this library is+-- defined in terms of 'Pipe', it can be easier to write certain low-level+-- pipe combining functions in terms of 'RecPipe' than 'Pipe'.+toRecPipe :: Monad m => Pipe i o u m a -> RecPipe i o u m a+toRecPipe = fromFT . pipeFree++-- | 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
− src/Lib.hs
@@ -1,6 +0,0 @@-module Lib-    ( someFunc-    ) where--someFunc :: IO ()-someFunc = putStrLn "someFunc"