@@ -205,6 +205,8 @@ module Streamly.Internal.Prelude
205205 -- ** Mapping Filters
206206 , mapMaybe
207207 , mapMaybeM
208+ , rollingMapM
209+ , rollingMap
208210
209211 -- ** Scanning Filters
210212 , findIndices
@@ -219,6 +221,7 @@ module Streamly.Internal.Prelude
219221 , intersperseSuffixBySpan
220222 -- , intersperseBySpan
221223 , interjectSuffix
224+ , delayPost
222225
223226 -- ** Reordering
224227 , reverse
@@ -344,6 +347,7 @@ module Streamly.Internal.Prelude
344347 , tap
345348 , tapAsync
346349 , tapRate
350+ , pollCounts
347351
348352 -- * Windowed Classification
349353
@@ -467,7 +471,7 @@ import Streamly.Internal.Data.Pipe.Types (Pipe (..))
467471import Streamly.Internal.Data.Time.Units
468472 (AbsTime, MilliSecond64(..), addToAbsTime, diffAbsTime, toRelTime,
469473 toAbsTime)
470−import Streamly.Internal.Mutable.Prim.Var (MonadMut, Prim, Var)
474+import Streamly.Internal.Mutable.Prim.Var (Prim, Var)
471475
472476import Streamly.Internal.Data.Strict
473477
@@ -879,7 +883,7 @@ fromHandle h = go
879883-- /Internal/
880884--
881885{-# INLINE fromPrimVar #-}
882−fromPrimVar :: (IsStream t, MonadMut m, Prim a) => Var m a -> t m a
886+fromPrimVar :: (IsStream t, MonadIO m, Prim a) => Var IO a -> t m a
883887fromPrimVar = fromStreamD . D.fromPrimVar
884888
885889------------------------------------------------------------------------------
@@ -1786,6 +1790,27 @@ scan (Fold step begin done) = P.scanlMx' step begin done
17861790postscan :: (IsStream t, Monad m) => Fold m a b -> t m a -> t m b
17871791postscan (Fold step begin done) = P.postscanlMx' step begin done
17881792
1793+------------------------------------------------------------------------------
1794+-- Stateful Transformations
1795+------------------------------------------------------------------------------
1796+
1797+-- | Apply a function on every two successive elements of a stream. If the
1798+-- stream consists of a single element the output is an empty stream.
1799+--
1800+-- /Internal/
1801+--
1802+{-# INLINE rollingMap #-}
1803+rollingMap :: (IsStream t, Monad m) => (a -> a -> b) -> t m a -> t m b
1804+rollingMap f m = fromStreamD $ D.rollingMap f $ toStreamD m
1805+
1806+-- | Like 'rollingMap' but with an effectful map function.
1807+--
1808+-- /Internal/
1809+--
1810+{-# INLINE rollingMapM #-}
1811+rollingMapM :: (IsStream t, Monad m) => (a -> a -> m b) -> t m a -> t m b
1812+rollingMapM f m = fromStreamD $ D.rollingMapM f $ toStreamD m
1813+
17891814------------------------------------------------------------------------------
17901815-- Transformation by Filtering
17911816------------------------------------------------------------------------------
@@ -2004,10 +2029,9 @@ reverse' s = fromStreamD $ D.reverse' $ toStreamD s
20042029
20052030-- intersperseM = intersperseBySpan 1
20062031
2007−-- | Generate a stream by performing a monadic action between consecutive
2008−-- elements of the given stream.
2009−--
2010−-- /Concurrent (do not use with 'parallely' on infinite streams)/
2032+-- | Generate a stream by inserting the result of a monadic action between
2033+-- consecutive elements of the given stream. Note that the monadic action is
2034+-- performed after the stream action before which its result is inserted.
20112035--
20122036-- @
20132037-- > S.toList $ S.intersperseM (return ',') $ S.fromList "hello"
@@ -2039,6 +2063,28 @@ intersperse a = fromStreamS . S.intersperse a . toStreamS
20392063intersperseSuffix :: (IsStream t, MonadAsync m) => m a -> t m a -> t m a
20402064intersperseSuffix m = fromStreamD . D.intersperseSuffix m . toStreamD
20412065
2066+-- | Perform a side effect after each element of a stream. The output of the
2067+-- effectful action is discarded, therefore, the input stream remains
2068+-- unchanged.
2069+--
2070+-- @
2071+-- > S.mapM_ putChar $ S.intersperseSuffix_ (threadDelay 1000000) $ S.fromList "hello"
2072+-- @
2073+--
2074+-- /Internal/
2075+--
2076+{-# INLINE intersperseSuffix_ #-}
2077+intersperseSuffix_ :: (IsStream t, Monad m) => m b -> t m a -> t m a
2078+intersperseSuffix_ m = Serial.mapM (\x -> void m >> return x)
2079+
2080+-- | Introduces a delay of specified seconds after each element of a stream.
2081+--
2082+-- /Internal/
2083+--
2084+{-# INLINE delayPost #-}
2085+delayPost :: (IsStream t, MonadIO m) => Double -> t m a -> t m a
2086+delayPost n = intersperseSuffix_ $ liftIO $ threadDelay $ round $ n * 1000000
2087+
20422088-- | Like 'intersperseSuffix' but intersperses a monadic action into the input
20432089-- stream after every @n@ elements and after the last element.
20442090--
@@ -3614,6 +3660,30 @@ tap f xs = D.fromStreamD $ D.tap f (D.toStreamD xs)
36143660tapAsync :: (IsStream t, MonadAsync m) => FL.Fold m a b -> t m a -> t m a
36153661tapAsync f xs = D.fromStreamD $ D.tapAsync f (D.toStreamD xs)
36163662
3663+-- | Maintain the count of elements flowing in the stream and poll the count
3664+-- asynchronously from another thread. The count stream is transformed using
3665+-- the supplied transform and then folded using the supplied fold. The thread
3666+-- is automatically cleaned up if the stream stops or aborts due to exception.
3667+--
3668+-- For example, to print the count of elements processed every second:
3669+--
3670+-- @
3671+-- > S.drain $ S.pollCounts (rollingMap (-) . delayPost 1) (FL.drainBy print)
3672+-- $ S.enumerateFrom 0
3673+-- @
3674+--
3675+{-# INLINE pollCounts #-}
3676+pollCounts ::
3677+ (IsStream t, MonadAsync m)
3678+ => (t m Int -> t m Int)
3679+ -> Fold m Int b
3680+ -> t m a
3681+ -> t m a
3682+pollCounts transf f xs =
3683+ D.fromStreamD
3684+ $ D.pollCounts (D.toStreamD . transf . D.fromStreamD) f
3685+ $ (D.toStreamD xs)
3686+
36173687-- | Calls the supplied function with the number of elements consumed
36183688-- every @n@ seconds. The given function is run in a separate thread
36193689-- until the end of the stream. In case there is an exception in the