packages feed

pipes-group (empty) → 1.0.0

raw patch · 5 files changed

+739/−0 lines, 5 filesdep +basedep +freedep +pipessetup-changed

Dependencies added: base, free, pipes, pipes-parse, transformers

Files

+ LICENSE view
@@ -0,0 +1,24 @@+Copyright (c) 2014 Gabriel Gonzalez+All rights reserved.++Redistribution and use in source and binary forms, with or without modification,+are permitted provided that the following conditions are met:+    * Redistributions of source code must retain the above copyright notice,+      this list of conditions and the following disclaimer.+    * Redistributions in binary form must reproduce the above copyright notice,+      this list of conditions and the following disclaimer in the documentation+      and/or other materials provided with the distribution.+    * Neither the name of Gabriel Gonzalez nor the names of other contributors+      may be used to endorse or promote products derived from this software+      without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ pipes-group.cabal view
@@ -0,0 +1,38 @@+Name: pipes-group+Version: 1.0.0+Cabal-Version: >=1.8.0.2+Build-Type: Simple+License: BSD3+License-File: LICENSE+Copyright: 2014 Gabriel Gonzalez+Author: Gabriel Gonzalez+Maintainer: Gabriel439@gmail.com+Bug-Reports: https://github.com/Gabriel439/Haskell-Pipes-Group-Library/issues+Synopsis: Group streams into substreams+Description: @pipes-group@ uses @FreeT@ and lenses to group streams into+    sub-streams.  Notable features include:+    .+    * /Perfect Streaming/: Group elements without collecting them into memory+    .+    * /Lens Support/: Use lenses to simplify many common operations+    .+    @Pipes.Group@ contains the full documentation for this library.+    .+    Read @Pipes.Group.Tutorial@ for an extensive tutorial.+Category: Control, Pipes+Source-Repository head+    Type: git+    Location: https://github.com/Gabriel439/Haskell-Pipes-Group-Library++Library+    HS-Source-Dirs: src+    Build-Depends:+        base         >= 4       && < 5  ,+        free         >= 3.2     && < 5  ,+        pipes        >= 4.0     && < 4.2,+        pipes-parse  >= 3.0.0   && < 3.1,+        transformers >= 0.2.0.0 && < 0.4+    Exposed-Modules:+        Pipes.Group+        Pipes.Group.Tutorial+    GHC-Options: -O2 -Wall
+ src/Pipes/Group.hs view
@@ -0,0 +1,321 @@+{-| Element-agnostic grouping utilities for @pipes@++    See "Pipes.Group.Tutorial" for an extended tutorial+-}++{-# LANGUAGE RankNTypes #-}++module Pipes.Group (+    -- * Lenses+    groupsBy,+    groups,+    chunksOf,++    -- * Transformations+    takes,+    takes',+    drops,+    maps,+    individually,++    -- * Joiners+    concats,+    intercalates,++    -- * Folds+    -- $folds+    folds,+    foldsM,++    -- * Re-exports+    -- $reexports+    module Control.Monad.Trans.Class,+    module Control.Monad.Trans.Free,+    module Pipes+    ) where++import Control.Monad.Trans.Class (lift)+import Control.Monad.Trans.Free (FreeF(Pure, Free), FreeT(FreeT, runFreeT))+import qualified Control.Monad.Trans.Free as F+import Data.Functor.Constant (Constant(Constant, getConstant))+import Data.Functor.Identity (Identity(Identity, runIdentity))+import Pipes (Producer, yield, next)+import Pipes.Parse (span, splitAt)+import qualified Pipes as P++import Prelude hiding (span, splitAt)++type Lens' a b = forall f . Functor f => (b -> f b) -> (a -> f a)+type Setter a' a b' b = (b' -> Identity b) -> (a' -> Identity a)++(^.) :: a -> ((b -> Constant b b) -> (a -> Constant b a)) -> b+a ^. lens = getConstant (lens Constant a)++{-| 'groupsBy' splits a 'Producer' into a 'FreeT' of 'Producer's grouped using+    the given equality predicate+-}+groupsBy+    :: Monad m+    => (a -> a -> Bool) -> Lens' (Producer a m x) (FreeT (Producer a m) m x)+groupsBy equals k p0 = fmap concats (k (_groupsBy p0))+  where+--  _groupsBy :: Monad m => Producer a m r -> FreeT (Producer a m) m r+    _groupsBy p = FreeT $ do+        x <- next p+        return $ case x of+            Left   r      -> Pure r+            Right (a, p') -> Free $+                fmap _groupsBy ((yield a >> p')^.span (equals a))+{-# INLINABLE groupsBy #-}++-- | Like 'groupsBy', where the equality predicate is ('==')+groups :: (Monad m, Eq a) => Lens' (Producer a m x) (FreeT (Producer a m) m x)+groups = groupsBy (==)+{-# INLINABLE groups #-}++{-| 'chunksOf' is an splits a 'Producer' into a 'FreeT' of 'Producer's of fixed+    length+-}+chunksOf+    :: Monad m => Int -> Lens' (Producer a m x) (FreeT (Producer a m) m x)+chunksOf n0 k p0 = fmap concats (k (_chunksOf p0))+  where+--  _chunksOf :: Monad m => Producer a m x -> FreeT (Producer a m) m x+    _chunksOf p = FreeT $ do+        x <- next p+        return $ case x of+            Left   r      -> Pure r+            Right (a, p') -> Free $ fmap _chunksOf ((yield a >> p')^.splitAt n0)+{-# INLINABLE chunksOf #-}++-- | Join a 'FreeT'-delimited stream of 'Producer's into a single 'Producer'+concats :: Monad m => FreeT (Producer a m) m x -> Producer a m x+concats = go+  where+    go f = do+        x <- lift (runFreeT f)+        case x of+            Pure r -> return r+            Free p -> do+                f' <- p+                go f'+{-# INLINABLE concats #-}++{-| Join a 'FreeT'-delimited stream of 'Producer's into a single 'Producer' by+    intercalating a 'Producer' in between them+-}+intercalates+    :: Monad m => Producer a m () -> FreeT (Producer a m) m x -> Producer a m x+intercalates sep = go0+  where+    go0 f = do+        x <- lift (runFreeT f)+        case x of+            Pure r -> return r+            Free p -> do+                f' <- p+                go1 f'+    go1 f = do+        x <- lift (runFreeT f)+        case x of+            Pure r -> return r+            Free p -> do+                sep+                f' <- p+                go1 f'+{-# INLINABLE intercalates #-}++{-| @(takes n)@ only keeps the first @n@ functor layers of a 'FreeT'++    You can think of this as:++> takes+>     :: (Functor f, Monad m)+>     => Int -> FreeT (Producer a m) m () -> FreeT (Producer a m) m ()+-}+takes :: (Functor f, Monad m) => Int -> FreeT f m () -> FreeT f m ()+takes = go+  where+    go n f =+        if (n > 0)+        then FreeT $ do+            x <- runFreeT f+            case x of+                Pure () -> return (Pure ())+                Free w  -> return (Free (fmap (go $! n - 1) w))+        else return ()+{-# INLINABLE takes #-}++{-| @(takes' n)@ only keeps the first @n@ 'Producer's of a 'FreeT'++    'takes'' differs from 'takes' by draining unused 'Producer's in order+    to preserve the return value.  This makes it a suitable argument for 'maps'.+-}+takes' :: Monad m => Int -> FreeT (Producer a m) m x -> FreeT (Producer a m) m x+takes' = go0+  where+    go0 n f = FreeT $+        if (n > 0)+        then do+            x <- runFreeT f+            return $ case x of+                Pure r -> Pure r+                Free p -> Free $ fmap (go0 $! n - 1) p+        else go1 f+    go1 f = do+        x <- runFreeT f+        case x of+            Pure r -> return (Pure r)+            Free p -> do+                f' <- P.runEffect (P.for p P.discard)+                go1 f'+{-# INLINABLE takes' #-}++{-| @(drops n)@ peels off the first @n@ 'Producer' layers of a 'FreeT'++    Use carefully: the peeling off is not free.   This runs the first @n@+    layers, just discarding everything they produce.+-}+drops :: Monad m => Int -> FreeT (Producer a m) m x -> FreeT (Producer a m) m x+drops = go+  where+    go n ft+        | n <= 0 = ft+        | otherwise = FreeT $ do+            ff <- runFreeT ft+            case ff of+                Pure _ -> return ff+                Free f -> do+                    ft' <- P.runEffect $ P.for f P.discard+                    runFreeT $ go (n-1) ft'+{-# INLINABLE drops #-}++{-| Transform each individual functor layer of a 'FreeT'++    You can think of this as:++> maps+>     :: (forall r . Producer a m r -> Producer b m r)+>     -> FreeT (Producer a m) m x -> FreeT (Producer b m) m x++    This is just a synonym for 'F.transFreeT'+-}+maps+    :: (Monad m, Functor g)+    => (forall r . f r -> g r) -> FreeT f m x -> FreeT g m x+maps = F.transFreeT+{-# INLINABLE maps #-}++{-| Lens to transform each individual functor layer of a 'FreeT'++> over individually = maps  -- ... with a less general type+-}+individually+    :: (Monad m, Functor g)+    => Setter (FreeT f m x) (FreeT g m x) (f (FreeT f m x)) (g (FreeT f m x))+individually nat f0 = Identity (go f0)+  where+    nat' = runIdentity . nat+    go f = FreeT $ do+        x <- runFreeT f+        return $ case x of+            Pure r -> Pure r+            Free w -> Free (fmap go (nat' w))+{-# INLINABLE individually #-}++{- $folds+    These folds are designed to be compatible with the @foldl@ library.  See+    the 'Control.Foldl.purely' and 'Control.Foldl.impurely' functions from that+    library for more details.++    For example, to count the number of 'Producer' layers in a 'FreeT', you can+    write:++> import Control.Applicative (pure)+> import qualified Control.Foldl as L+> import Pipes.Group+> import qualified Pipes.Prelude as P+>+> count :: Monad m => FreeT (Producer a m) m () -> m Int+> count = P.sum . L.purely folds (pure 1)+-}+{-| Fold each 'Producer' of a 'FreeT'++> purely folds+>     :: Monad m => Fold a b -> FreeT (Producer a m) m r -> Producer b m r+-}+folds+    :: Monad m+    => (x -> a -> x)+    -- ^ Step function+    -> x+    -- ^ Initial accumulator+    -> (x -> b)+    -- ^ Extraction function+    -> FreeT (Producer a m) m r+    -- ^+    -> Producer b m r+folds step begin done = go+  where+    go f = do+        x <- lift (runFreeT f)+        case x of+            Pure r -> return r+            Free p -> do+	        (f', b) <- lift (fold p begin)+	        yield b+	        go f'++    fold p x = do+        y <- next p+        case y of+            Left   f      -> return (f, done x)+            Right (a, p') -> fold p' $! step x a+{-# INLINABLE folds #-}++{-| Fold each 'Producer' of a 'FreeT', monadically++> impurely foldsM+>     :: Monad m => FoldM a b -> FreeT (Producer a m) m r -> Producer b m r+-}+foldsM+    :: Monad m+    => (x -> a -> m x)+    -- ^ Step function+    -> m x+    -- ^ Initial accumulator+    -> (x -> m b)+    -- ^ Extraction function+    -> FreeT (Producer a m) m r+    -- ^+    -> Producer b m r+foldsM step begin done = go+  where+    go f = do+        y <- lift (runFreeT f)+        case y of+            Pure r -> return r+            Free p -> do+                (f', b) <- lift $ do+                    x <- begin+		    foldM p x+                yield b+                go f'++    foldM p x = do+        y <- next p+        case y of+            Left   f      -> do+                b <- done x+                return (f, b)+            Right (a, p') -> do+                x' <- step x a+                foldM p' $! x'++{- $reexports+    "Control.Monad.Trans.Class" re-exports 'lift'.++    "Control.Monad.Trans.Free" re-exports 'FreeF' and 'FreeT'++    "Pipes" re-exports 'Producer', 'yield', and 'next'.+-}
+ src/Pipes/Group/Tutorial.hs view
@@ -0,0 +1,354 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-}++{-| @pipes-group@ builds upon @pipes@ to establish idioms for grouping streams+    into sub-streams without collecting elements into memory.  This tutorial+    assumes familiarity with @pipes@ and @pipes-parse@.+-}++module Pipes.Group.Tutorial (+    -- * Motivation+    -- $motivation++    -- * FreeT+    -- $freeT++    -- * How FreeT Works+    -- $advanced++    -- * Conclusion+    -- $conclusion+    ) where++import Pipes+import Pipes.Group++{- $motivation+    Dividing a stream into sub-streams is non-trivial.  To illustrate the+    problem, consider the following task: limit a stream to the first three+    groups of elements (a group means consecutive equal elements).++    The wrong way to do it is to read each group into memory like this:++> import Lens.Family.State.Strict (zoom)+> import Pipes+> import Pipes.Parse+> import qualified Pipes.Prelude as P+> +> threeGroups :: (Monad m, Eq a) => Producer a m () -> Producer a m ()+> threeGroups p0 = loop 3 p0+>   where+>     loop 0 _ = return ()+>     loop n p = do+>         (as, p') <- lift $ runStateT (zoom group drawAll) p+>         each as+>         loop (n - 1) p'++    The first problem is that this approach does not output any elements from+    each group until after parsing the entire group:++>>> runEffect $ threeGroups P.stdinLn >-> P.stdoutLn+1<Enter>+1<Enter>+2<Enter>+1+1+2<Enter>+2<Enter>+3<Enter>+2+2+2+4<Enter>+3+>>>++    Worse, this program will crash without outputting a single value if fed an+    infinitely long group of identical elements:++>>> runEffect $ threeGroups (each (repeat 1)) >-> P.print+<Consumes all memory and crashes>++    A better approach is to just stream directly from the first three groups+    instead of storing the groups in intermediate lists:++> import Lens.Family ((^.))+> import Pipes+> import Pipes.Parse+> import qualified Pipes.Prelude as P+> +> threeGroups :: (Monad m, Eq a) => Producer a m () -> Producer a m ()+> threeGroups p0 = loop 3 p0+>   where+>     loop 0 _ = return ()+>     loop n p = do+>         p' <- p ^. group+>         loop (n - 1) p'++    This will run in constant memory and stream values immediately:++>>> runEffect $ threeGroups P.stdinLn >-> P.stdoutLn+1<Enter>+1+1<Enter>+1+2<Enter>+2+2<Enter>+2+2<Enter>+2+3<Enter>+3+4<Enter>++    However, this code is not very modular: we have to integrate our group+    creation logic with our group consumption logic.  This conflicts with the+    @pipes@ philosophy of decoupling streaming programs into modular components.++    An more modular approach would be to split our logic into three steps:++    * Split our 'Producer' into groups++    * Take the first three groups++    * Join these three groups back into a 'Producer'++    But how do we split our 'Producer' into groups without loading an entire+    group into memory?  We want to avoid solutions like the following code:++> import Control.Monad (when, liftM2)+> import Lens.Family.State.Strict (zoom)+> import Pipes.Parse+> +> split :: (Monad m, Eq a) => Producer a m () -> Producer [a] m ()+> split p = do+>     ((as, eof), p') <- lift (runStateT parser p)+>     yield as+>     when (not eof) (split p')+>   where+>     parser = liftM2 (,) (zoom group drawAll) isEndOfInput++    ... because then we're back where we started, loading entire groups into+    memory.+-}++{- $freeT+    Fortunately, you can group elements while still streaming individual+    elements at a time.  The 'FreeT' type from the @free@ package solves this+    problem by allowing us to build \"linked lists\" of 'Producer's.  This lets+    you work with streams in a list-like manner.++    The key idea is that:++> -- '~' means "is analogous to"+>+> -- If a Producer is like a list+> Producer a m ()            ~   [a]+>+> -- ... then a 'FreeT'-delimited 'Producer' is like a list of lists+> FreeT (Producer a m) m ()  ~  [[a]]++    Think of @(FreeT (Producer a m) m ())@ as a \"list of 'Producer's\".+    'FreeT' nests each subsequent 'Producer' within the return value of the+    previous 'Producer' so that you cannot access the next 'Producer' until you+    completely drain the current 'Producer'.  However, you rarely need to work+    with 'FreeT' directly.  Instead, you can structure most things using+    \"splitters\", \"transformations\" and \"joiners\":++> -- A "splitter"+> Producer a m ()           -> FreeT (Producer a m) m ()  ~   [a]  -> [[a]]+>+> -- A "transformation"+> FreeT (Producer a m) m () -> FreeT (Producer a m) m ()  ~  [[a]] -> [[a]]+>+> -- A "joiner"+> FreeT (Producer a m) m () -> Producer a m ()            ~  [[a]] ->  [a]++    An example splitter is @(view groups)@, which splits a 'Producer' into+    'FreeT'-delimited 'Producer's, one for each group of consecutive equal+    elements:++> view groups :: (Eq a, Monad m) => Producer a m x -> FreeT (Producer a m) m x++    An example transformation is @(takes 3)@, which takes the first three+    'Producer's from a 'FreeT' and drops the rest:++> takes 3 :: Monad m => FreeT (Producer a m) m () -> FreeT (Producer a m) m ()++    An example joiner is @concats@, which collapses a 'FreeT' of 'Producer's+    back down into a single 'Producer':++> concats :: Monad m => FreeT (Producer a m) m x -> Producer a m x++    If you compose these three functions together, you will create a function+    that transforms a 'Producer' to keep only the first three groups of+    consecutive equal elements:++> import Lens.Family+> import Pipes+> import Pipes.Group+> import qualified Pipes.Prelude as P+>+> threeGroups :: (Monad m, Eq a) => Producer a m () -> Producer a m ()+> threeGroups = concats . takes 3 . view groups++    Both splitting and joining preserve the streaming nature of 'Producer's and+    do not collect or buffer any values.  The transformed 'Producer' still+    outputs values immediately and does not wait for groups to complete before+    producing results.++>>> runEffect $ threeGroups P.stdinLn >-> P.stdoutLn+1<Enter>+1+1<Enter>+1+2<Enter>+2+2<Enter>+2+2<Enter>+2+3<Enter>+3+4<Enter>+>>>++    Also, lenses simplify things even further.  The reason that 'groups' is a+    lens is because it actually combines both a splitter and joiner into a+    single package.  We can then use 'over' to handle both the splitting and+    joining for us:++>>> runEffect $ over groups (takes 3) P.stdinLn >-> P.stdoutLn+<Exact same behavior>++    This behaves the same because 'over' takes care of calling the splitter+    before applying the transformation, then calling the inverse joiner+    afterward.++    Another useful lens is 'individually', which lets you apply transformations+    to each 'Producer' layer of a 'FreeT'.  For example, if we wanted to+    add an extra @"!"@ line to the end of every group, we would write:++>>> import Control.Applicative ((<*))+>>> runEffect $ over (groups . individually) (<* yield "!") P.stdinLn >-> P.stdoutLn+1<Enter>+1+1<Enter>+1+2<Enter>+!+2+2<Enter>+2+2<Enter>+2+3<Enter>+!+3+4<Enter>+!+>>>++    Note that 'individually' is only compatible with the @lens@ package.  You+    can alternatively use 'maps' if you are using @lens-family-core@:++>>> runEffect $ over groups (maps (<* yield "!")) P.stdinLn >-> P.stdoutLn+<Exact same behavior>++-}++{- $advanced+    You don't necessarily have to restrict yourself to predefined 'FreeT'+    functions.  You can also manually build or recurse over 'FreeT's of+    'Producer's.++    For example, here is how 'concats' is implemented, which collapses all the+    'Producer's within a 'FreeT' into a single 'Producer':++> concats :: Monad m => FreeT (Producer a m) m x -> Producer a m x+> concats = go+>   where+>     go f = do+>         x <- lift (runFreeT f)  -- Match against the "head" of the "list"+>         case x of+>             Pure r -> return r  -- The "list" is empty+>             Free p -> do        -- The "list" is non-empty+>                 f' <- p         -- The return value of the 'Producer' is+>                 go f'           --     the "tail" of the "list"++    Many patterns for 'FreeT's have equivalent analogs for lists.  'runFreeT'+    behaves like pattern matching on the list, except that you have to bind the+    result.  'Pure' is analogous to @[]@ and 'Free' is analogous to @(:)@.++    When you receive a 'Free' constructor that means you have a 'Producer' whose+    return value is the rest of the list (i.e. another 'FreeT').  You cannot+    access the rest of the list without running the 'Producer' to completion to+    retrieve this return value.  The above example just runs the entire+    'Producer', binds the remainder of the list to @f'@ and then recurses on+    that value.++    You can also build 'FreeT's in a manner similar to lists.  For example, the+    'chunksOf' lens uses the following splitter function internally:++> _chunksOf :: Monad m => Producer a m x -> FreeT (Producer a m) m x+> _chunksOf p = FreeT $ do+>     x <- next p                     -- Pattern match on the 'Producer'+>     return $ case x of+>         Left   r      -> Pure r     -- Build an empty "list"+>         Right (a, p') -> Free $ do  -- Build a non-empty "list"+>             p'' <- (yield a >> p')^.splitAt n0  -- Emit the "head"+>             return (_chunksOf p'')              -- Return the "tail"++    'Pure' signifies an empty 'FreeT' (one with no 'Producer' layers), just like+    @[]@ signifies an empty list (one with no elements).  We return 'Pure'+    whenever we cannot emit any more 'Producer's.++    'Free' indicates that we wish to emit a 'Producer' followed by another+    \"list\".  The 'Producer' we run directly within the body of the 'Free'.+    However, we store the remainder of the \"list\" within the return value of+    the 'Producer'.  This is where @_chunksOf@ recurses to build the rest of the+    \"list\".++    To gain a better understanding for how 'FreeT' works, consult the definition+    of the type, which you can find in "Control.Monad.Trans.Free":++> newtype FreeT f m a = FreeT { runFreeT :: m (FreeF f a (FreeT f m a)) }+>+> data FreeF f a b = Pure a | Free (f b)++    ... and just replace all occurences of @f@ with @(Producer e m)@:++> -- This is pseudocode+>+> newtype FreeT' m a = FreeT { runFreeT :: m (FreeF' a (FreeT' m a)) }+>+> data FreeF' a b = Pure a | Free (Producer e m b)++    ... which you can further think of as:++> -- More pseudocode+>+> newtype FreeT' m a =+>     FreeT { runFreeT :: m (Pure a | Producer e m (FreeT' m a)) }++    In other words, 'runFreeT' unwraps a 'FreeT' to produce an action in the+    base monad which either finishes with a value of type @a@ or continues with+    a 'Producer' which returns a new 'FreeT'.  Vice versa, if you want to build+    a 'FreeT', you must create an action in the base monad which returns either+    a 'Pure' or a 'Producer' wrapping another 'FreeT'.+-}++{- $conclusion+    This library is very small since it only contains element-agnostic grouping+    utilities.  Downstream libraries that provide richer grouping utilities+    include @pipes-bytestring@ and @pipes-text@.++    To learn more about @pipes-group@, ask questions, or follow development, you+    can subscribe to the @haskell-pipes@ mailing list at:++    <https://groups.google.com/forum/#!forum/haskell-pipes>++    ... or you can mail the list directly at:++    <mailto:haskell-pipes@googlegroups.com>+-}