-- | -- Module : Streamly.Internal.Data.Stream.Reduce -- Copyright : (c) 2017 Composewell Technologies -- License : BSD-3-Clause -- Maintainer : streamly@composewell.com -- Stability : experimental -- Portability : GHC -- -- Reduce streams by streams, folds or parsers. module Streamly.Internal.Data.Stream.Reduce ( -- * Reduce By Streams dropPrefix , dropInfix , dropSuffix -- * Reduce By Folds -- | -- Reduce a stream by folding or parsing chunks of the stream. Functions -- generally ending in these shapes: -- -- @ -- f (Fold m a b) -> Stream m a -> Stream m b -- f (Parser m a b) -> Stream m a -> Stream m b -- @ -- ** Generic Folding -- | Apply folds on a stream. , foldMany , foldManyPost , refoldMany , foldSequence , foldIterateM , refoldIterateM -- ** Chunking -- | Element unaware grouping. , arraysOf -- * Reduce By Parsers -- ** Generic Parsing -- | Apply parsers on a stream. , parseMany , parseManyD , parseManyTill , parseSequence , parseIterate ) where import Control.Monad.Catch (MonadThrow) import Control.Monad.IO.Class (MonadIO(..)) import Streamly.Internal.Data.Array.Unboxed.Type (Array) import Streamly.Internal.Data.Fold.Type (Fold (..)) import Streamly.Internal.Data.Parser (Parser (..)) import Streamly.Internal.Data.Refold.Type (Refold (..)) import Streamly.Internal.Data.Stream.Bottom (foldManyPost) import Streamly.Internal.Data.Stream.Type (Stream, fromStreamD, toStreamD) import Streamly.Internal.Data.Unboxed (Unboxed) import qualified Streamly.Internal.Data.Array.Unboxed.Type as Array import qualified Streamly.Internal.Data.Parser.ParserD as ParserD import qualified Streamly.Internal.Data.Stream.StreamD as D import Prelude hiding (concatMap, map) -- $setup -- >>> :m -- >>> import Prelude hiding (zipWith, concatMap, concat) -- >>> import Streamly.Internal.Data.Stream as Stream -- >>> import qualified Streamly.Data.Fold as Fold -- >>> import qualified Streamly.Internal.Data.Fold as Fold -- >>> import qualified Streamly.Internal.Data.Unfold as Unfold -- >>> import qualified Streamly.Internal.Data.Parser as Parser -- >>> import qualified Streamly.Data.Array.Unboxed as Array ------------------------------------------------------------------------------ -- Trimming ------------------------------------------------------------------------------ -- | Drop prefix from the input stream if present. -- -- Space: @O(1)@ -- -- /Unimplemented/ {-# INLINE dropPrefix #-} dropPrefix :: -- (Monad m, Eq a) => Stream m a -> Stream m a -> Stream m a dropPrefix = error "Not implemented yet!" -- | Drop all matching infix from the input stream if present. Infix stream -- may be consumed multiple times. -- -- Space: @O(n)@ where n is the length of the infix. -- -- /Unimplemented/ {-# INLINE dropInfix #-} dropInfix :: -- (Monad m, Eq a) => Stream m a -> Stream m a -> Stream m a dropInfix = error "Not implemented yet!" -- | Drop suffix from the input stream if present. Suffix stream may be -- consumed multiple times. -- -- Space: @O(n)@ where n is the length of the suffix. -- -- /Unimplemented/ {-# INLINE dropSuffix #-} dropSuffix :: -- (Monad m, Eq a) => Stream m a -> Stream m a -> Stream m a dropSuffix = error "Not implemented yet!" ------------------------------------------------------------------------------ -- Folding ------------------------------------------------------------------------------ -- Splitting operations that take a predicate and a Fold can be -- expressed using parseMany. Operations like chunksOf, intervalsOf, split*, -- can be expressed using parseMany when used with an appropriate Parser. -- -- XXX We need takeGE/takeBetween to implement "some" using "many". -- | Apply a 'Fold' repeatedly on a stream and emit the fold outputs in the -- output stream. -- -- To sum every two contiguous elements in a stream: -- -- >>> f = Fold.take 2 Fold.sum -- >>> Stream.fold Fold.toList $ Stream.foldMany f $ Stream.fromList [1..10] -- [3,7,11,15,19] -- -- On an empty stream the output is empty: -- -- >>> Stream.fold Fold.toList $ Stream.foldMany f $ Stream.fromList [] -- [] -- -- Note @Stream.foldMany (Fold.take 0)@ would result in an infinite loop in a -- non-empty stream. -- -- Note 'foldMany' on an empty stream results in an empty stream. Therefore, -- @Stream.fold f@ is not the same as @Stream.head . Stream.foldMany f@. -- -- @since 0.9.0 {-# INLINE foldMany #-} foldMany :: Monad m => Fold m a b -> Stream m a -> Stream m b foldMany f m = fromStreamD $ D.foldMany f (toStreamD m) -- | Like 'foldMany' but using the 'Refold' type instead of 'Fold'. -- -- /Pre-release/ {-# INLINE refoldMany #-} refoldMany :: Monad m => Refold m c a b -> m c -> Stream m a -> Stream m b refoldMany f action = fromStreamD . D.refoldMany f action . toStreamD -- | Apply a stream of folds to an input stream and emit the results in the -- output stream. -- -- /Unimplemented/ -- {-# INLINE foldSequence #-} foldSequence :: -- Monad m => Stream m (Fold m a b) -> Stream m a -> Stream m b foldSequence _f _m = undefined -- | Iterate a fold generator on a stream. The initial value @b@ is used to -- generate the first fold, the fold is applied on the stream and the result of -- the fold is used to generate the next fold and so on. -- -- >>> import Data.Monoid (Sum(..)) -- >>> f x = return (Fold.take 2 (Fold.sconcat x)) -- >>> s = fmap Sum $ Stream.fromList [1..10] -- >>> Stream.fold Fold.toList $ fmap getSum $ Stream.foldIterateM f (pure 0) s -- [3,10,21,36,55,55] -- -- This is the streaming equivalent of monad like sequenced application of -- folds where next fold is dependent on the previous fold. -- -- /Pre-release/ -- {-# INLINE foldIterateM #-} foldIterateM :: Monad m => (b -> m (Fold m a b)) -> m b -> Stream m a -> Stream m b foldIterateM f i m = fromStreamD $ D.foldIterateM f i (toStreamD m) -- | Like 'foldIterateM' but using the 'Refold' type instead. This could be -- much more efficient due to stream fusion. -- -- /Internal/ {-# INLINE refoldIterateM #-} refoldIterateM :: Monad m => Refold m b a b -> m b -> Stream m a -> Stream m b refoldIterateM c i m = fromStreamD $ D.refoldIterateM c i (toStreamD m) ------------------------------------------------------------------------------ -- Parsing ------------------------------------------------------------------------------ -- | Apply a 'Parser' repeatedly on a stream and emit the parsed values in the -- output stream. -- -- This is the streaming equivalent of the 'Streamly.Internal.Data.Parser.many' -- parse combinator. -- -- >>> s = Stream.fromList [1..10] -- >>> parser = Parser.takeBetween 0 2 Fold.sum -- >>> Stream.fold Fold.toList $ Stream.parseMany parser s -- [3,7,11,15,19] -- -- foldMany f = Stream.parseMany (Parser.fromFold f) -- -- Known Issues: When the parser fails there is no way to get the remaining -- stream. -- -- /Pre-release/ -- {-# INLINE parseMany #-} parseMany :: MonadThrow m => Parser m a b -> Stream m a -> Stream m b parseMany p m = fromStreamD $ D.parseMany (ParserD.fromParserK p) (toStreamD m) -- | Same as parseMany but for StreamD streams. -- -- /Internal/ -- {-# INLINE parseManyD #-} parseManyD :: MonadThrow m => ParserD.Parser m a b -> Stream m a -> Stream m b parseManyD p m = fromStreamD $ D.parseMany p (toStreamD m) -- | Apply a stream of parsers to an input stream and emit the results in the -- output stream. -- -- /Unimplemented/ -- {-# INLINE parseSequence #-} parseSequence :: -- Monad m => Stream m (Parser m a b) -> Stream m a -> Stream m b parseSequence _f _m = undefined -- XXX Change the parser arguments' order -- | @parseManyTill collect test stream@ tries the parser @test@ on the input, -- if @test@ fails it backtracks and tries @collect@, after @collect@ succeeds -- @test@ is tried again and so on. The parser stops when @test@ succeeds. The -- output of @test@ is discarded and the output of @collect@ is emitted in the -- output stream. The parser fails if @collect@ fails. -- -- /Unimplemented/ -- {-# INLINE parseManyTill #-} parseManyTill :: -- MonadThrow m => Parser m a b -> Parser m a x -> t m a -> t m b parseManyTill = undefined -- | Iterate a parser generating function on a stream. The initial value @b@ is -- used to generate the first parser, the parser is applied on the stream and -- the result is used to generate the next parser and so on. -- -- >>> import Data.Monoid (Sum(..)) -- >>> s = Stream.fromList [1..10] -- >>> Stream.fold Fold.toList $ fmap getSum $ Stream.parseIterate (\b -> Parser.takeBetween 0 2 (Fold.sconcat b)) 0 $ fmap Sum s -- [3,10,21,36,55,55] -- -- This is the streaming equivalent of monad like sequenced application of -- parsers where next parser is dependent on the previous parser. -- -- /Pre-release/ -- {-# INLINE parseIterate #-} parseIterate :: MonadThrow m => (b -> Parser m a b) -> b -> Stream m a -> Stream m b parseIterate f i m = fromStreamD $ D.parseIterate (ParserD.fromParserK . f) i (toStreamD m) ------------------------------------------------------------------------------ -- Chunking ------------------------------------------------------------------------------ -- | @arraysOf n stream@ groups the elements in the input stream into arrays of -- @n@ elements each. -- -- Same as the following but may be more efficient: -- -- >>> arraysOf n = Stream.foldMany (Array.writeN n) -- -- /Pre-release/ {-# INLINE arraysOf #-} arraysOf :: (MonadIO m, Unboxed a) => Int -> Stream m a -> Stream m (Array a) arraysOf n = fromStreamD . Array.arraysOf n . toStreamD