diff --git a/benchmark/Streamly/Benchmark/Data/Fold.hs b/benchmark/Streamly/Benchmark/Data/Fold.hs index 4a82a4a67c..36a700e0a0 100644 --- a/benchmark/Streamly/Benchmark/Data/Fold.hs +++ b/benchmark/Streamly/Benchmark/Data/Fold.hs @@ -808,10 +808,10 @@ unzipWithMinM n = withStream n $ Stream.fold (FL.unzipWithMinM f FL.sum FL.lengt -- Nested ------------------------------------------------------------------------------- -{-# INLINE unfoldMany #-} -unfoldMany :: Int -> IO () -unfoldMany n = - Stream.fold (FL.unfoldMany Unfold.replicateM FL.drain) +{-# INLINE unfoldEach #-} +unfoldEach :: Int -> IO () +unfoldEach n = + Stream.fold (FL.unfoldEach Unfold.replicateM FL.drain) $ Stream.fromPure (n, randomRIO (1, 1 :: Int)) ------------------------------------------------------------------------------- @@ -1114,7 +1114,7 @@ benchmarks env value = , benchIO "split_ (all, any)" $ split_ value , benchIO "tee (all, any)" $ teeAllAny value , benchIO "many drain (take 1)" $ many value - , benchIO "unfoldMany" $ unfoldMany value + , benchIO "unfoldEach" $ unfoldEach value , benchIO "shortest (sum, length)" $ shortest value , benchIO "longest (sum, length)" $ longest value , benchIO "tee (sum, length)" $ teeSumLength value diff --git a/core/src/Streamly/Data/Fold.hs b/core/src/Streamly/Data/Fold.hs index d5f6a5ce12..5c2edfcc99 100644 --- a/core/src/Streamly/Data/Fold.hs +++ b/core/src/Streamly/Data/Fold.hs @@ -136,26 +136,28 @@ -- Streams are producers, transformations on streams happen on the output side: -- -- >>> :{ --- f stream = --- Stream.filter odd stream +-- f fold = +-- Stream.fromList [1..100 :: Int] +-- & Stream.filter odd -- & fmap (+1) --- & Stream.fold Fold.sum +-- & Stream.fold fold -- :} -- --- >>> f $ Stream.fromList [1..100 :: Int] +-- >>> f Fold.sum -- 2550 -- -- Folds are stream consumers with an input stream and an output value, stream -- transformations on folds happen on the input side: -- -- >>> :{ --- f = --- Fold.filter odd +-- f stream = +-- Fold.drive stream +-- $ Fold.filter odd -- $ Fold.lmap (+1) -- $ Fold.sum -- :} -- --- >>> Stream.fold f $ Stream.fromList [1..100 :: Int] +-- >>> f $ Stream.fromList [1..100 :: Int] -- 2550 -- -- Notice the similiarity in the definition of @f@ in both cases, the only diff --git a/core/src/Streamly/Internal/Data/Fold/Combinators.hs b/core/src/Streamly/Internal/Data/Fold/Combinators.hs index 8bb6c5b15e..2e3af36c0a 100644 --- a/core/src/Streamly/Internal/Data/Fold/Combinators.hs +++ b/core/src/Streamly/Internal/Data/Fold/Combinators.hs @@ -201,10 +201,11 @@ module Streamly.Internal.Data.Fold.Combinators , intersperseWithQuotes -- ** Nesting - , unfoldMany + , unfoldEach , concatSequence -- * Deprecated + , unfoldMany , drainBy , head , sequence @@ -592,8 +593,7 @@ repeated = error "Not implemented yet!" -- >>> drainMapM f = Fold.lmapM f Fold.drain -- >>> drainMapM f = Fold.foldMapM (void . f) -- --- Drain all input after passing it through a monadic function. This is the --- dual of mapM_ on stream producers. +-- Drain all input after passing it through a monadic function. -- {-# INLINE drainMapM #-} drainMapM :: Monad m => (a -> m b) -> Fold m a () @@ -1741,7 +1741,7 @@ tee = teeWith (,) -- XXX use "List" instead of "[]"?, use Array for output to scale it to a large -- number of consumers? For polymorphic case a vector could be helpful. For -- Unboxs we can use arrays. Will need separate APIs for those. --- + -- | Distribute one copy of the stream to each fold and collect the results in -- a container. -- @@ -1759,8 +1759,6 @@ tee = teeWith (,) -- -- >>> distribute = Prelude.foldr (Fold.teeWith (:)) (Fold.fromPure []) -- --- This is the consumer side dual of the producer side 'sequence' operation. --- -- Stops when all the folds stop. -- {-# INLINE distribute #-} @@ -1876,8 +1874,6 @@ partitionByMUsing t f fld1 fld2 = -- (67,33) -- -- --- This is the consumer side dual of the producer side 'mergeBy' operation. --- -- When one fold is done, any input meant for it is ignored until the other -- fold is also done. -- @@ -1921,7 +1917,9 @@ partitionByMinM = partitionByMUsing teeWithMin -- :} -- ("Even 50","Odd 50") -- --- /Pre-release/ +-- NOTE: This is the exact analogue of the @choose@ method of the Contravariant +-- functor typeclass Decidable. +-- {-# INLINE partitionBy #-} partitionBy :: Monad m => (a -> Either b c) -> Fold m b x -> Fold m c y -> Fold m a (x, y) @@ -2004,7 +2002,9 @@ unzipWithMinM = unzipWithMUsing teeWithMin -- -- This fold terminates when both the input folds terminate. -- --- /Pre-release/ +-- NOTE: This is the exact analogue of the @divide@ method of the Contravariant +-- functor typeclass Divisible. +-- {-# INLINE unzipWith #-} unzipWith :: Monad m => (a -> (b,c)) -> Fold m b x -> Fold m c y -> Fold m a (x,y) @@ -2025,8 +2025,6 @@ unzipWith f = unzipWithM (return . f) -- -- >>> unzip = Fold.unzipWith id -- --- This is the consumer side dual of the producer side 'zip' operation. --- {-# INLINE unzip #-} unzip :: Monad m => Fold m a x -> Fold m b y -> Fold m (a,b) (x,y) unzip = unzipWith id @@ -2199,13 +2197,13 @@ toStreamRev = fromScanl Scanl.toStreamRev -- | Unfold and flatten the input stream of a fold. -- -- @ --- Stream.fold (unfoldMany u f) = Stream.fold f . Stream.unfoldMany u +-- Stream.fold (unfoldEach u f) = Stream.fold f . Stream.unfoldEach u -- @ -- -- /Pre-release/ -{-# INLINE unfoldMany #-} -unfoldMany :: Monad m => Unfold m a b -> Fold m b c -> Fold m a c -unfoldMany (Unfold ustep inject) (Fold fstep initial extract final) = +{-# INLINE unfoldEach #-} +unfoldEach :: Monad m => Unfold m a b -> Fold m b c -> Fold m a c +unfoldEach (Unfold ustep inject) (Fold fstep initial extract final) = Fold consume initial extract final where @@ -2226,6 +2224,11 @@ unfoldMany (Unfold ustep inject) (Fold fstep initial extract final) = {-# INLINE_LATE consume #-} consume s a = inject a >>= produce s +-- {-# DEPRECATED unfoldMany "Use unfoldEach instead." #-} +{-# INLINE unfoldMany #-} +unfoldMany :: Monad m => Unfold m a b -> Fold m b c -> Fold m a c +unfoldMany = unfoldEach + -- | Get the bottom most @n@ elements using the supplied comparison function. -- {-# INLINE bottomBy #-} diff --git a/core/src/Streamly/Internal/Data/Fold/Type.hs b/core/src/Streamly/Internal/Data/Fold/Type.hs index ef873f6515..c7b8a2873a 100644 --- a/core/src/Streamly/Internal/Data/Fold/Type.hs +++ b/core/src/Streamly/Internal/Data/Fold/Type.hs @@ -342,6 +342,16 @@ -- can be expressed using 'mconcat' and a suitable 'Monoid'. Instead of -- writing folds we can write Monoids and turn them into folds. -- +-- = Initial, Final Duality +-- +-- Currently there is a discrepancy between folds and streams, folds have an +-- "initial" to initialize the fold without any input. A producer side dual +-- would be a finalizer called whenever the stream stops. +-- +-- However, the initial action in folds creates a discrepancy with the CPS +-- folds, and the same may be the case if we have a stop/cleanup operation in +-- streams. + module Streamly.Internal.Data.Fold.Type ( module Streamly.Internal.Data.Fold.Step @@ -816,6 +826,8 @@ fromRefold (Refold step inject extract) c = -- Basic Folds ------------------------------------------------------------------------------ +-- drain is a dual of infinite streams. This is the /dev/null of folds. + -- | A fold that drains all its input, running the effects and discarding the -- results. -- @@ -830,6 +842,8 @@ drain = fromScanl Scanl.drain -- To Containers ------------------------------------------------------------------------------ +-- toList is a dual of Stream.fromList. + -- | Folds the input stream to a list. -- -- /Warning!/ working on large lists accumulated as buffers in memory could be @@ -876,6 +890,8 @@ toListRev = foldl' (flip (:)) [] toStreamKRev :: Monad m => Fold m a (K.StreamK n a) toStreamKRev = fromScanl Scanl.toStreamKRev +-- toStreamK is a dual of Stream.fromStreamK + -- | A fold that buffers its input to a pure stream. -- -- >>> toStreamK = foldr StreamK.cons StreamK.nil @@ -909,6 +925,9 @@ genericLength = fromScanl Scanl.genericLength length :: Monad m => Fold m a Int length = fromScanl Scanl.length +-- Naming: "last" makes better sense for folds whereas "latest" makes better +-- sense for scans. + -- | Returns the latest element of the input stream, if any. -- -- >>> latest = Fold.foldl1' (\_ x -> x) @@ -939,39 +958,27 @@ instance Functor m => Functor (Fold m a) where step s b = fmap2 f (step1 s b) fmap2 g = fmap (fmap g) --- XXX These are singleton folds that are closed for input. The correspondence --- to a nil stream would be a nil fold that returns "Done" in "initial" i.e. it --- does not produce any accumulator value. However, we do not have a --- representation of an empty value in folds, because the Done constructor --- always produces a value (Done b). We can potentially use "Partial s b" and --- "Done" to make the type correspond to the stream type. That may be possible --- if we introduce the "Skip" constructor as well because after the last --- "Partial s b" we have to emit a "Skip to Done" state to keep cranking the --- fold until it is done. --- --- There is also the asymmetry between folds and streams because folds have an --- "initial" to initialize the fold without any input. A similar concept is --- possible in streams as well to stop the stream. That would be a "closing" --- operation for the stream which can be called even without consuming any item --- from the stream or when we are done consuming. +-- NOTE: Canonical producer side dual of this is "Stream.nil" which generates a +-- nil stream regardless of the seed. This folds a nil stream to some known +-- value, reversing the producer side process. -- --- However, the initial action in folds creates a discrepancy with the CPS --- folds, and the same may be the case if we have a stop/cleanup operation in --- streams. +-- Alternate names: const --- | Make a fold that yields the supplied value without consuming any further --- input. --- --- /Pre-release/ +-- | Make a fold that immediately returns a constant output without consuming +-- any input. -- {-# INLINE fromPure #-} fromPure :: Applicative m => b -> Fold m a b fromPure b = Fold undefined (pure $ Done b) pure pure --- | Make a fold that yields the result of the supplied effectful action --- without consuming any further input. +-- NOTE: Canonical producer side dual of this is "Stream.nilM" which generates +-- a nil stream and an effect. This folds a nil stream to some known value and +-- an effect, reverse of the producer. -- --- /Pre-release/ +-- Alternative names: constM + +-- | Make a fold that immediately returns a constant output along with an +-- effect without consuming any input. -- {-# INLINE fromEffect #-} fromEffect :: Applicative m => m b -> Fold m a b @@ -980,6 +987,9 @@ fromEffect b = Fold undefined (Done <$> b) pure pure {-# ANN type SeqFoldState Fuse #-} data SeqFoldState sl f sr = SeqFoldL !sl | SeqFoldR !f !sr +-- dual of Stream.append +-- Alternative names: appendWith, serialWith. + -- | Sequential fold application. Apply two folds sequentially to an input -- stream. The input is provided to the first fold, when it is done - the -- remaining input is provided to the second fold. When the second fold is done @@ -1426,7 +1436,22 @@ concatMap f (Fold stepa initiala _ finala) = -- Mapping on input ------------------------------------------------------------------------------ --- | @lmap f fold@ maps the function @f@ on the input of the fold. +-- | Transform the input of a fold using a pure function. +-- +-- @lmap f fold@ applies @f@ to each input element before passing it to +-- @fold@. +-- +-- Think of @lmap f fold@ as plugging @f@ into the input socket of +-- @fold@. The input of the resulting fold is the input of the plug f. +-- The output of the plug must match the input of the fold. +-- +-- @ +-- +-----------+ +-----------------+ +-- a -> | f | -> b -> | Fold m b c | -> c +-- +-----------+ +-----------------+ +-- \\_______________________/ +-- Fold m a c +-- @ -- -- Definition: -- @@ -1439,13 +1464,15 @@ concatMap f (Fold stepa initiala _ finala) = -- 338350 -- {-# INLINE lmap #-} -lmap :: (a -> b) -> Fold m b r -> Fold m a r +lmap :: (a -> b) -> Fold m b c -> Fold m a c lmap f (Fold step begin done final) = Fold step' begin done final where step' x a = step x (f a) -- | @lmapM f fold@ maps the monadic function @f@ on the input of the fold. -- +-- See 'lmap' for detailed documentation. +-- {-# INLINE lmapM #-} lmapM :: Monad m => (a -> m b) -> Fold m b r -> Fold m a r lmapM f (Fold step begin done final) = Fold step' begin done final @@ -2037,6 +2064,8 @@ ifThen predicate -- 'duplicate' essentially appends a stream to the fold without finishing the -- fold. Compare with 'snoc' which appends a singleton value to the fold. -- +-- See 'Fold.addStream' as well. +-- -- /Pre-release/ {-# INLINE duplicate #-} duplicate :: Monad m => Fold m a b -> Fold m a (Fold m a b) @@ -2069,7 +2098,7 @@ reduce (Fold step initial extract final) = do i <- initial return $ Fold step (return i) extract final --- This is the dual of Stream @cons@. +-- This is a fold builder equivalent of the stream builder @cons@. -- | Append an effect to the fold lazily, in other words run a single -- step of the fold. diff --git a/core/src/Streamly/Internal/Data/Parser/Type.hs b/core/src/Streamly/Internal/Data/Parser/Type.hs index bb46383b95..851d5e8fb1 100644 --- a/core/src/Streamly/Internal/Data/Parser/Type.hs +++ b/core/src/Streamly/Internal/Data/Parser/Type.hs @@ -1503,15 +1503,35 @@ instance (MonadIO m) => MonadIO (Parser a m) where -- Mapping on input ------------------------------------------------------------------------------ --- | @lmap f parser@ maps the function @f@ on the input of the parser. +-- | Transform the input of a parser using a pure function. -- --- >>> Stream.parse (Parser.lmap (\x -> x * x) (Parser.fromFold Fold.sum)) (Stream.enumerateFromTo 1 100) --- Right 338350 +-- @lmap f parser@ applies @f@ to each input element before passing it to +-- @parser@. +-- +-- Think of @lmap f parser@ as plugging @f@ into the input socket of +-- @parser@. The input of the resulting parser is the input of the plug f. +-- The output of the plug must match the input of the fold. +-- +-- @ +-- +-----------+ +-----------------+ +-- a -> | f | -> b -> | Parser b m c | -> c +-- +-----------+ +-----------------+ +-- \\_______________________/ +-- Parser a m c +-- @ +-- +-- Definition: -- --- > lmap = Parser.lmapM return +-- >>> lmap = Fold.lmapM return +-- +-- Example: +-- +-- >>> sumSquared = Parser.lmap (\x -> x * x) (Parser.fromFold Fold.sum) +-- >>> Stream.parse sumSquared (Stream.enumerateFromTo 1 100) +-- Right 338350 -- {-# INLINE lmap #-} -lmap :: (a -> b) -> Parser b m r -> Parser a m r +lmap :: (a -> b) -> Parser b m c -> Parser a m c lmap f (Parser step begin done) = Parser step1 begin done where @@ -1521,7 +1541,7 @@ lmap f (Parser step begin done) = Parser step1 begin done -- | @lmapM f parser@ maps the monadic function @f@ on the input of the parser. -- {-# INLINE lmapM #-} -lmapM :: Monad m => (a -> m b) -> Parser b m r -> Parser a m r +lmapM :: Monad m => (a -> m b) -> Parser b m c -> Parser a m c lmapM f (Parser step begin done) = Parser step1 begin done where diff --git a/core/src/Streamly/Internal/Data/Scanl/Combinators.hs b/core/src/Streamly/Internal/Data/Scanl/Combinators.hs index a9739e366d..04e9551fde 100644 --- a/core/src/Streamly/Internal/Data/Scanl/Combinators.hs +++ b/core/src/Streamly/Internal/Data/Scanl/Combinators.hs @@ -195,10 +195,11 @@ module Streamly.Internal.Data.Scanl.Combinators -- , intersperseWithQuotes -- ** Nesting - , unfoldMany + , unfoldEach -- , concatSequence -- * Deprecated + , unfoldMany , scanl , scanlMany ) @@ -2187,13 +2188,13 @@ toStreamRev = fmap StreamD.fromList toListRev -- | Unfold and flatten the input stream of a scan. -- -- @ --- Stream.scanl (unfoldMany u f) == Stream.scanl f . Stream.unfoldMany u +-- Stream.scanl (unfoldEach u f) == Stream.scanl f . Stream.unfoldEach u -- @ -- -- /Pre-release/ -{-# INLINE unfoldMany #-} -unfoldMany :: Monad m => Unfold m a b -> Scanl m b c -> Scanl m a c -unfoldMany (Unfold ustep inject) (Scanl fstep initial extract final) = +{-# INLINE unfoldEach #-} +unfoldEach :: Monad m => Unfold m a b -> Scanl m b c -> Scanl m a c +unfoldEach (Unfold ustep inject) (Scanl fstep initial extract final) = Scanl consume initial extract final where @@ -2214,6 +2215,11 @@ unfoldMany (Unfold ustep inject) (Scanl fstep initial extract final) = {-# INLINE_LATE consume #-} consume s a = inject a >>= produce s +-- {-# DEPRECATED unfoldMany "Use unfoldEach instead." #-} +{-# INLINE unfoldMany #-} +unfoldMany :: Monad m => Unfold m a b -> Scanl m b c -> Scanl m a c +unfoldMany = unfoldEach + -- | Get the bottom most @n@ elements using the supplied comparison function. -- {-# INLINE bottomBy #-} diff --git a/core/src/Streamly/Internal/Data/Scanl/Type.hs b/core/src/Streamly/Internal/Data/Scanl/Type.hs index 33483a1131..d53ef2ffa7 100644 --- a/core/src/Streamly/Internal/Data/Scanl/Type.hs +++ b/core/src/Streamly/Internal/Data/Scanl/Type.hs @@ -1231,7 +1231,22 @@ concatMap f (Fold stepa initiala _ finala) = -- Mapping on input ------------------------------------------------------------------------------ --- | @lmap f scan@ maps the function @f@ on the input of the scan. +-- | Transform the input of a @scan@ using a pure function. +-- +-- @lmap f scan@ applies @f@ to each input element before passing it to +-- @scan@. +-- +-- Think of @lmap f scan@ as plugging @f@ into the input socket of +-- @scan@. The input of the resulting scan is the input of the plug f. +-- The output of the plug must match the input of the scan. +-- +-- @ +-- +-----------+ +-----------------+ +-- a -> | f | -> b -> | Scanl m b c | -> c +-- +-----------+ +-----------------+ +-- \\_______________________/ +-- Scanl m a c +-- @ -- -- Definition: -- @@ -1251,6 +1266,8 @@ lmap f (Scanl step begin done final) = Scanl step' begin done final -- | @lmapM f scan@ maps the monadic function @f@ on the input of the scan. -- +-- See 'lmap' for detailed documentation. +-- {-# INLINE lmapM #-} lmapM :: Monad m => (a -> m b) -> Scanl m b r -> Scanl m a r lmapM f (Scanl step begin done final) = Scanl step' begin done final diff --git a/core/src/Streamly/Internal/Data/Unfold.hs b/core/src/Streamly/Internal/Data/Unfold.hs index 2bf44fb5e9..2ebcadc562 100644 --- a/core/src/Streamly/Internal/Data/Unfold.hs +++ b/core/src/Streamly/Internal/Data/Unfold.hs @@ -124,6 +124,7 @@ where #include "inline.hs" #include "ArrayMacros.h" +#include "deprecation.h" import Control.Exception (Exception, mask_) import Control.Monad.Catch (MonadCatch) @@ -446,9 +447,9 @@ postscanlM' f z = postscanl (Scanl.scanlM' f z) -- Convert streams into unfolds ------------------------------------------------------------------------------- -{-# INLINE_NORMAL fromStreamD #-} -fromStreamD :: Applicative m => Unfold m (Stream m a) a -fromStreamD = Unfold step pure +{-# INLINE_NORMAL fromStream #-} +fromStream, fromStreamD :: Applicative m => Unfold m (Stream m a) a +fromStream = Unfold step pure where @@ -459,6 +460,8 @@ fromStreamD = Unfold step pure Skip s -> Skip (Stream step1 s) Stop -> Stop) <$> step1 defState state1 +RENAME(fromStreamD,fromStream) + {-# INLINE_NORMAL fromStreamK #-} fromStreamK :: Applicative m => Unfold m (K.StreamK m a) a fromStreamK = Unfold step pure @@ -471,10 +474,6 @@ fromStreamK = Unfold step pure Just (x, xs) -> Yield x xs Nothing -> Stop) <$> K.uncons stream -{-# INLINE fromStream #-} -fromStream :: Applicative m => Unfold m (Stream m a) a -fromStream = fromStreamD - ------------------------------------------------------------------------------- -- Unfolds ------------------------------------------------------------------------------- diff --git a/core/src/Streamly/Internal/Data/Unfold/Type.hs b/core/src/Streamly/Internal/Data/Unfold/Type.hs index 35e3a55e9a..fbd37b73e0 100644 --- a/core/src/Streamly/Internal/Data/Unfold/Type.hs +++ b/core/src/Streamly/Internal/Data/Unfold/Type.hs @@ -72,8 +72,8 @@ module Streamly.Internal.Data.Unfold.Type , fromTuple -- * Transformations - , lmap -- XXX plug - , lmapM -- XXX plugM + , lmap + , lmapM , map , mapM , supply @@ -296,28 +296,39 @@ unfoldr step = unfoldrM (pure . step) -- Map input ------------------------------------------------------------------------------ --- | Map a function on the input argument of the 'Unfold'. +-- | Transform the input of an 'Unfold' using a pure function. +-- +-- @lmap f unfold@ applies @f@ to the input seed before passing it to +-- @unfold@. +-- +-- Think of @lmap f unfold@ as plugging @f@ into the input socket of +-- @unfold@. The input of the resulting unfold is the input of the plug f. +-- The output of the plug must match the input of the fold. +-- +-- @ +-- +-----------+ +-----------------+ +-- a -> | f | -> b -> | Unfold m b c | -> c +-- +-----------+ +-----------------+ +-- \\_______________________/ +-- Unfold m a c +-- @ +-- +-- Example: -- -- >>> u = Unfold.lmap (fmap (+1)) Unfold.fromList -- >>> Unfold.fold Fold.toList u [1..5] -- [2,3,4,5,6] -- --- Definition: --- --- >>> lmap f = Unfold.unfoldEach (Unfold.function f) --- {-# INLINE_NORMAL lmap #-} -lmap :: (a -> c) -> Unfold m c b -> Unfold m a b +lmap :: (a -> b) -> Unfold m b c -> Unfold m a c lmap f (Unfold ustep uinject) = Unfold ustep (uinject . f) -- | Map an action on the input argument of the 'Unfold'. -- --- Definition: --- --- lmapM f = Unfold.unfoldEach (Unfold.functionM f) +-- See 'lmap' for detailed documentation. -- {-# INLINE_NORMAL lmapM #-} -lmapM :: Monad m => (a -> m c) -> Unfold m c b -> Unfold m a b +lmapM :: Monad m => (a -> m b) -> Unfold m b c -> Unfold m a c lmapM f (Unfold ustep uinject) = Unfold ustep (f >=> uinject) -- | Supply the seed to an unfold closing the input end of the unfold.