on master help sign in
Add modular scanning functions for transformations (#1809)

Usable across folds and streams. Implemented common transformations using scans. Performance of single operation benchmark seems to be unaffected but in some cases combinations of multiple benchmarks or the same operation multiple times is degraded. The perf impact could perhaps be improved if we have a separate Scan type where we do not need a pre-initialization of state.

commit935f69e46e9159a397b96ea12757a42f7d123d46 authorHarendra Kumar <harendra@composewell.com> dateMon 5 Sep 2022, 18:17 (4 years ago) parentf4de841
8 files changed+622−191
benchmark/Streamly/Benchmark/Data/Fold.hs+10−10 core/src/Streamly/Data/Stream.hs+14−1 core/src/Streamly/Internal/Data/Fold.hs+293−88 core/src/Streamly/Internal/Data/Fold/Type.hs+151−17 core/src/Streamly/Internal/Data/Fold/Window.hs+37 core/src/Streamly/Internal/Data/Stream/Bottom.hs+31 core/src/Streamly/Internal/Data/Stream/StreamD/Transform.hs+26−16 core/src/Streamly/Internal/Data/Stream/Transform.hs+60−59
benchmark/Streamly/Benchmark/Data/Fold.hs+10−10
@@ -93,16 +93,16 @@ sequence_ value =
9393filter :: Monad m => Int -> Stream m Int -> m ()
9494filter _ = Stream.fold (FL.filter even FL.drain)
9595
96−{-# INLINE foldFilter #-}
97−foldFilter :: Monad m => Int -> Stream m Int -> m ()
98−foldFilter _ = Stream.fold (FL.foldFilter (FL.satisfy even) FL.drain)
96+{-# INLINE scanMaybe #-}
97+scanMaybe :: Monad m => Int -> Stream m Int -> m ()
98+scanMaybe _ = Stream.fold (FL.scanMaybe (FL.filtering even) FL.drain)
9999
100−{-# INLINE foldFilter2 #-}
101−foldFilter2 :: Monad m => Int -> Stream m Int -> m ()
102−foldFilter2 _ =
100+{-# INLINE scanMaybe2 #-}
101+scanMaybe2 :: Monad m => Int -> Stream m Int -> m ()
102+scanMaybe2 _ =
103103 Stream.fold
104− $ FL.foldFilter (FL.satisfy even)
105− $ FL.foldFilter (FL.satisfy odd) FL.drain
104+ $ FL.scanMaybe (FL.filtering even)
105+ $ FL.scanMaybe (FL.filtering odd) FL.drain
106106
107107-------------------------------------------------------------------------------
108108-- Splitting by serial application
@@ -395,8 +395,8 @@ o_1_space_serial_composition value =
395395 [ bgroup
396396 "composition"
397397 [ benchIOSink value "filter even" $ filter value
398− , benchIOSink value "foldFilter even" $ foldFilter value
399− , benchIOSink value "foldFilter even, odd" $ foldFilter2 value
398+ , benchIOSink value "scanMaybe even" $ scanMaybe value
399+ , benchIOSink value "scanMaybe even, odd" $ scanMaybe2 value
400400 , benchIOSink value "foldBreak (recursive)" foldBreak
401401 , benchIOSink value "serialWith (all, any)" $ splitAllAny value
402402 , benchIOSink value "serial_ (all, any)" $ serial_ value
core/src/Streamly/Data/Stream.hs+14−1
@@ -101,7 +101,8 @@ module Streamly.Data.Stream
101101 -- , consM2 -- fused version
102102
103103 -- ** Unfolding
104− -- | Generalized way of generating a stream efficiently.
104+ -- | 'unfoldrM' is the most general way of generating a stream efficiently.
105+ -- All other generation operations can be expressed using it.
105106 , unfoldr
106107 , unfoldrM
107108
@@ -226,6 +227,8 @@ module Streamly.Data.Stream
226227 , uncons
227228
228229 -- ** Folding
230+ -- XXX Need to have a general parse operation here which can be used to
231+ -- express all others.
229232 , fold -- XXX rename to run? We can have a Stream.run and Fold.run.
230233 -- XXX fold1 can be achieved using Monoids or Refolds.
231234 , foldBreak
@@ -292,6 +295,8 @@ module Streamly.Data.Stream
292295 -- | Remove elements from the stream.
293296
294297 -- ** Stateless Filters
298+ -- | 'mapMaybeM' is the most general stateless filtering operation. All
299+ -- other filtering operations can be expressed using it.
295300 -- EXPLANATION:
296301 -- In imperative terms a filter over a stream corresponds to a loop with a
297302 -- @continue@ clause for the cases when the predicate fails.
@@ -302,6 +307,11 @@ module Streamly.Data.Stream
302307 , filterM
303308
304309 -- ** Stateful Filters
310+ -- | 'scanMaybe' is the most general stateful filtering operation. The
311+ -- filtering folds (folds returning a 'Maybe' type) in
312+ -- "Streamly.Internal.Data.Fold" can be used along with 'scanMaybe' to
313+ -- perform stateful filtering operations in general.
314+ , scanMaybe
305315 , take
306316 , takeWhile
307317 , takeWhileM
@@ -389,6 +399,7 @@ module Streamly.Data.Stream
389399
390400 -- * Buffered Operations
391401 -- | Operations that require buffering of the stream.
402+ -- Reverse is essentially a left fold followed by an unfold.
392403 , reverse
393404
394405 -- * Multi-Stream folds
@@ -414,6 +425,8 @@ module Streamly.Data.Stream
414425 , handle
415426
416427 -- * Resource Management
428+ -- | 'bracket' is the most general resource management operation, all other
429+ -- operations can be expressed using it.
417430 , before
418431 , after
419432 , finally
core/src/Streamly/Internal/Data/Fold.hs+293−88
@@ -36,6 +36,11 @@ module Streamly.Internal.Data.Fold
3636 , foldr
3737 , foldrM
3838
39+ -- * Mappers
40+ -- | Monadic functions useful with mapM/lmapM on folds or streams.
41+ , tracing
42+ , trace
43+
3944 -- * Folds
4045 -- ** Identity
4146 , fromPure
@@ -51,7 +56,6 @@ module Streamly.Internal.Data.Fold
5156 -- *** Reducers
5257 , drain
5358 , drainBy
54− , last
5559 , the
5660 , length
5761 , genericLength
@@ -63,7 +67,6 @@ module Streamly.Internal.Data.Fold
6367 , rollingHashWithSalt
6468 , rollingHashFirstN
6569 -- , rollingHashLastN
66− , rollingMapM
6770
6871 -- *** Saturating Reducers
6972 -- | 'product' terminates if it becomes 0. Other folds can theoretically
@@ -86,15 +89,41 @@ module Streamly.Internal.Data.Fold
8689 , toStream
8790 , toStreamRev
8891 , toMap
92+ , topBy
8993 , top
9094 , bottom
9195
96+ -- *** Scanners
97+ -- | Stateful transformation of the elements. Useful in combination with
98+ -- the 'scanMaybe' combinator. For scanners the result of the fold is
99+ -- usually a transformation of the current element rather than an
100+ -- aggregation of all elements till now.
101+ , last -- XXX prev
102+ -- , nthLast -- using Ring array
103+ , indexingWith
104+ , indexing
105+ , indexingRev
106+ , rollingMapM
107+
108+ -- *** Filters
109+ -- | Useful in combination with the 'scanMaybe' combinator.
110+ , filtering
111+ , deleteBy
112+ , nub
113+ , nubInt
114+ , uniqBy
115+ , uniq
116+ , repeated
117+ , findIndices
118+ , elemIndices
119+
92120 -- ** Terminating Folds
93121 -- Element folds. Terminate after inspecting one element. All these can be
94122 -- implemented in terms of the 'maybe' fold.
95123 , head
96124 , one
97125 , null
126+ , satisfy
98127 , maybe
99128
100129 -- Sequence folds. Terminate after inspecting a sequence of elements.
@@ -115,6 +144,18 @@ module Streamly.Internal.Data.Fold
115144 , and
116145 , or
117146
147+ -- ** Trimmers
148+ -- | Useful in combination with the 'scanMaybe' combinator.
149+ , taking
150+ , dropping
151+ , takingEndByM
152+ , takingEndBy
153+ , takingEndByM_
154+ , takingEndBy_
155+ , droppingWhileM
156+ , droppingWhile
157+ , prune
158+
118159 -- * Combinators
119160 -- ** Utilities
120161 , with
@@ -131,42 +172,35 @@ module Streamly.Internal.Data.Fold
131172 , lmap
132173 --, lsequence
133174 , lmapM
175+
176+ -- ** Sliding Window
177+ , slide2
178+
179+ -- ** Scanning Input
134180 , scan
135181 , scanMany
136182 , postscan
137183 , indexed
138− , trace
139184
140185 -- ** Zipping Input
141186 , zipWithM
142187 , zip
143188
144− -- ** Filtering
189+ -- ** Filtering Input
190+ , catMaybes
191+ , mapMaybeM
192+ , mapMaybe
193+ , scanMaybe
145194 , filter
146195 , filterM
147− , foldFilter
148− , satisfy
149196 , sampleFromthen
150− -- , ldeleteBy
151− -- , luniq
152− , nub
153− , nubInt
154−
155− -- ** Mapping Filters
156− , catMaybes
157− , mapMaybe
158− -- , mapMaybeM
159197
160198 -- Either streams
161199 , lefts
162200 , rights
163201 , both
164202
165− -- ** Scanning Filters
166− , findIndices
167203 {-
168− , elemIndices
169−
170204 -- ** Insertion
171205 -- | Insertion adds more elements to the stream.
172206
@@ -304,10 +338,10 @@ import Data.Functor.Identity (Identity(..))
304338import Data.Int (Int64)
305339import Data.IORef (newIORef, readIORef, writeIORef)
306340import Data.Map.Strict (Map)
307−import Data.Maybe (isJust, fromJust)
308341import Data.Word (Word32)
309342import Foreign.Storable (peek, sizeOf)
310343import Streamly.Internal.Data.IsMap (IsMap(..))
344+import Streamly.Internal.Data.Maybe.Strict (Maybe'(..), toMaybe)
311345import Streamly.Internal.Data.Pipe.Type (Pipe (..), PipeState(..))
312346import Streamly.Internal.Data.Unboxed (Unboxed)
313347import Streamly.Internal.Data.Unfold.Type (Unfold(..))
@@ -339,17 +373,18 @@ import Streamly.Internal.Data.Fold.Type
339373-- >>> :m
340374-- >>> :set -package streamly
341375-- >>> import Prelude hiding (break, map, span, splitAt)
342−-- >>> import qualified Streamly.Data.Stream as Stream
376+-- >>> import qualified Streamly.Data.Array.Unboxed as Array
343377-- >>> import qualified Streamly.Data.Fold as Fold
378+-- >>> import qualified Streamly.Data.Stream as Stream
379+-- >>> import qualified Streamly.Internal.Data.Array.Unboxed.Mut.Type as MA
344380-- >>> import qualified Streamly.Internal.Data.Fold as Fold
345381-- >>> import qualified Streamly.Internal.Data.Fold.Type as Fold
346382-- >>> import qualified Streamly.Internal.Data.Fold.Window as FoldW
347−-- >>> import qualified Streamly.Data.Array.Unboxed as Array
348383-- >>> import qualified Streamly.Internal.Data.Parser as Parser
349384-- >>> import qualified Streamly.Internal.Data.Stream.Type as Stream
385+-- >>> import qualified Streamly.Internal.Data.Unfold as Unfold
350386-- >>> import Streamly.Internal.Data.Stream.Type (Stream)
351387-- >>> import Data.IORef (newIORef, readIORef, writeIORef)
352−-- >>> import qualified Streamly.Internal.Data.Array.Unboxed.Mut.Type as MA
353388
354389------------------------------------------------------------------------------
355390-- hoist
@@ -390,10 +425,20 @@ sequence = rmapM id
390425mapM :: Monad m => (b -> m c) -> Fold m a b -> Fold m a c
391426mapM = rmapM
392427
428+-- |
429+-- >>> mapMaybeM f = Fold.lmapM f . Fold.catMaybes
430+--
431+{-# INLINE mapMaybeM #-}
432+mapMaybeM :: Monad m => (a -> m (Maybe b)) -> Fold m b r -> Fold m a r
433+mapMaybeM f = lmapM f . catMaybes
434+
393435-- | @mapMaybe f fold@ maps a 'Maybe' returning function @f@ on the input of
394436-- the fold, filters out 'Nothing' elements, and return the values extracted
395437-- from 'Just'.
396438--
439+-- >>> mapMaybe f = Fold.lmap f . Fold.catMaybes
440+-- >>> mapMaybe f = Fold.mapMaybeM (return . f)
441+--
397442-- >>> f x = if even x then Just x else Nothing
398443-- >>> fld = Fold.mapMaybe f Fold.toList
399444-- >>> Stream.fold fld (Stream.enumerateFromTo 1 10)
@@ -401,13 +446,25 @@ mapM = rmapM
401446--
402447-- @since 0.8.0
403448{-# INLINE mapMaybe #-}
404−mapMaybe :: (Monad m) => (a -> Maybe b) -> Fold m b r -> Fold m a r
405−mapMaybe f = lmap f . filter isJust . lmap fromJust
449+mapMaybe :: Monad m => (a -> Maybe b) -> Fold m b r -> Fold m a r
450+mapMaybe f = lmap f . catMaybes
406451
407452------------------------------------------------------------------------------
408453-- Transformations on fold inputs
409454------------------------------------------------------------------------------
410455
456+-- | Apply a monadic function on the input and return the input.
457+--
458+-- >>> Stream.fold (Fold.lmapM (Fold.tracing print) Fold.drain) $ (Stream.enumerateFromTo (1 :: Int) 2)
459+-- 1
460+-- 2
461+--
462+-- /Pre-release/
463+--
464+{-# INLINE tracing #-}
465+tracing :: Monad m => (a -> m b) -> (a -> m a)
466+tracing f x = void (f x) >> return x
467+
411468-- | Apply a monadic function to each element flowing through and discard the
412469-- results.
413470--
@@ -415,10 +472,12 @@ mapMaybe f = lmap f . filter isJust . lmap fromJust
415472-- 1
416473-- 2
417474--
475+-- >>> trace f = Fold.lmapM (Fold.tracing f)
476+--
418477-- /Pre-release/
419478{-# INLINE trace #-}
420479trace :: Monad m => (a -> m b) -> Fold m a r -> Fold m a r
421−trace f = lmapM (\x -> void (f x) >> return x)
480+trace f = lmapM (tracing f)
422481
423482-- rename to lpipe?
424483--
@@ -516,82 +575,110 @@ scan = scanWith False
516575scanMany :: Monad m => Fold m a b -> Fold m b c -> Fold m a c
517576scanMany = scanWith True
518577
519−-- | Postscan the input of a 'Fold' to change it in a stateful manner using
520−-- another 'Fold'.
521−-- /Pre-release/
522−{-# INLINE postscan #-}
523−postscan :: Monad m => Fold m a b -> Fold m b c -> Fold m a c
524−postscan (Fold stepL initialL extractL) (Fold stepR initialR extractR) =
525− Fold step initial extract
578+------------------------------------------------------------------------------
579+-- Filters
580+------------------------------------------------------------------------------
581+
582+-- | A filtering scan that deletes the first occurrence of the element in the
583+-- stream that satisfies the given equality predicate.
584+--
585+-- >>> input = Stream.unfold Unfold.fromList [1,3,3,5]
586+-- >>> Stream.fold Fold.toList $ Stream.scanMaybe (Fold.deleteBy (==) 3) input
587+-- [1,3,5]
588+--
589+{-# INLINE_NORMAL deleteBy #-}
590+deleteBy :: Monad m => (a -> a -> Bool) -> a -> Fold m a (Maybe a)
591+deleteBy eq x0 = fmap extract $ foldl' step (Tuple' False Nothing)
526592
527593 where
528594
529− {-# INLINE runStep #-}
530− runStep actionL sR = do
531− rL <- actionL
532− case rL of
533− Done bL -> do
534− rR <- stepR sR bL
535− case rR of
536− Partial sR1 -> Done <$> extractR sR1
537− Done bR -> return $ Done bR
538− Partial sL -> do
539− !b <- extractL sL
540− rR <- stepR sR b
541− return
542− $ case rR of
543− Partial sR1 -> Partial (sL, sR1)
544− Done bR -> Done bR
595+ step (Tuple' False _) x =
596+ if eq x x0
597+ then Tuple' True Nothing
598+ else Tuple' False (Just x)
599+ step (Tuple' True _) x = Tuple' True (Just x)
545600
546− initial = do
547− r <- initialR
548− rL <- initialL
549− case r of
550− Partial sR ->
551− case rL of
552− Done _ -> Done <$> extractR sR
553− Partial sL -> return $ Partial (sL, sR)
554− Done b -> return $ Done b
601+ extract (Tuple' _ x) = x
555602
556− step (sL, sR) x = runStep (stepL sL x) sR
603+-- | Provide a sliding window of length 2 elements.
604+--
605+-- See "Streamly.Internal.Data.Fold.Window".
606+--
607+{-# INLINE slide2 #-}
608+slide2 :: Monad m => Fold m (a, Maybe a) b -> Fold m a b
609+slide2 (Fold step1 initial1 extract1) = Fold step initial extract
557610
558− extract = extractR . snd
611+ where
559612
560−------------------------------------------------------------------------------
561−-- Filters
562−------------------------------------------------------------------------------
613+ initial =
614+ first (Tuple' Nothing) <$> initial1
615+
616+ step (Tuple' prev s) cur =
617+ first (Tuple' (Just cur)) <$> step1 s (cur, prev)
618+
619+ extract (Tuple' _ s) = extract1 s
563620
564−-- | Convert a predicate into a filtering fold.
621+-- | Drop repeated elements that are adjacent to each other using the supplied
622+-- comparison function.
565623--
566−-- >>> f = Fold.foldFilter (Fold.satisfy (> 5)) Fold.sum
567−-- >>> Stream.fold f $ Stream.fromList [1..10]
568−-- 40
624+-- To strip duplicate path separators:
625+--
626+-- >>> input = Stream.unfold Unfold.fromList "//a//b"
627+-- >>> f x y = x == '/' && y == '/'
628+-- >>> Stream.fold Fold.toList $ Stream.scanMaybe (Fold.uniqBy f) input
629+-- "/a/b"
630+--
631+-- Space: @O(1)@
632+--
633+-- See also: 'nubBy'.
569634--
570635-- /Pre-release/
571−{-# INLINE satisfy #-}
572−satisfy :: Monad m => (a -> Bool) -> Fold m a (Maybe a)
573−satisfy f = Fold step (return $ Partial ()) (const (return Nothing))
636+--
637+{-# INLINE uniqBy #-}
638+uniqBy :: Monad m => (a -> a -> Bool) -> Fold m a (Maybe a)
639+uniqBy eq = rollingMap f
574640
575641 where
576642
577− step () a = return $ Done $ if f a then Just a else Nothing
643+ f pre curr =
644+ case pre of
645+ Nothing -> Just curr
646+ Just x -> if x `eq` curr then Nothing else Just curr
578647
579−-- | Use a 'Maybe' returning fold as a filtering scan.
648+-- | Drop repeated elements that are adjacent to each other.
580649--
581−-- >>> f = Fold.foldFilter (Fold.satisfy (> 5)) Fold.sum
582−-- >>> Stream.fold f $ Stream.fromList [1..10]
583−-- 40
650+-- >>> uniq = Fold.uniqBy (==)
584651--
585−-- The above snippet is equivalent to:
652+{-# INLINE uniq #-}
653+uniq :: (Monad m, Eq a) => Fold m a (Maybe a)
654+uniq = uniqBy (==)
655+
656+-- | Strip all leading and trailing occurrences of an element passing a
657+-- predicate and make all other consecutive occurrences uniq.
586658--
587−-- >>> f = Fold.filter (> 5) Fold.sum
588−-- >>> Stream.fold f $ Stream.fromList [1..10]
589−-- 40
659+-- >> prune p = Stream.dropWhileAround p $ Stream.uniqBy (x y -> p x && p y)
590660--
591−-- /Pre-release/
592−{-# INLINE foldFilter #-}
593−foldFilter :: Monad m => Fold m a (Maybe b) -> Fold m b c -> Fold m a c
594−foldFilter f1 f2 = many f1 (catMaybes f2)
661+-- @
662+-- > Stream.prune isSpace (Stream.fromList " hello world! ")
663+-- "hello world!"
664+--
665+-- @
666+--
667+-- Space: @O(1)@
668+--
669+-- /Unimplemented/
670+{-# INLINE prune #-}
671+prune ::
672+ -- (Monad m, Eq a) =>
673+ (a -> Bool) -> Fold m a (Maybe a)
674+prune = error "Not implemented yet!"
675+
676+-- | Emit only repeated elements, once.
677+--
678+-- /Unimplemented/
679+repeated :: -- (Monad m, Eq a) =>
680+ Fold m a (Maybe a)
681+repeated = error "Not implemented yet!"
595682
596683-- | Used as a scan. Returns 'Just' for the first occurrence of an element,
597684-- returns 'Nothing' for any other occurrences.
@@ -960,6 +1047,9 @@ rollingHash = rollingHashWithSalt defaultSalt
9601047rollingHashFirstN :: (Monad m, Enum a) => Int -> Fold m a Int64
9611048rollingHashFirstN n = take n rollingHash
9621049
1050+-- XXX Compare this with the implementation in Fold.Window, preferrably use the
1051+-- latter if performance is good.
1052+
9631053-- | Apply a function on every two successive elements of a stream. The first
9641054-- argument of the map function is the previous element and the second argument
9651055-- is the current element. When processing the very first element in the
@@ -983,6 +1073,10 @@ rollingMapM f = Fold step initial extract
9831073
9841074 extract = return . snd
9851075
1076+{-# INLINE rollingMap #-}
1077+rollingMap :: Monad m => (Maybe a -> a -> b) -> Fold m a b
1078+rollingMap f = rollingMapM (\x y -> return $ f x y)
1079+
9861080------------------------------------------------------------------------------
9871081-- Monoidal left folds
9881082------------------------------------------------------------------------------
@@ -1122,6 +1216,17 @@ index = genericIndex
11221216maybe :: Monad m => (a -> Maybe b) -> Fold m a (Maybe b)
11231217maybe f = foldt' (const (Done . f)) (Partial Nothing) id
11241218
1219+-- | Test if the next element satisfies the supplied predicate.
1220+--
1221+-- /Pre-release/
1222+{-# INLINE satisfy #-}
1223+satisfy :: Monad m => (a -> Bool) -> Fold m a (Maybe a)
1224+satisfy f = Fold step (return $ Partial ()) (const (return Nothing))
1225+
1226+ where
1227+
1228+ step () a = return $ Done $ if f a then Just a else Nothing
1229+
11251230-- Naming notes:
11261231--
11271232-- "head" and "next" are two alternative names for the same API. head sounds
@@ -1227,6 +1332,7 @@ findIndex predicate = foldt' step (Partial 0) (const Nothing)
12271332{-# INLINE findIndices #-}
12281333findIndices :: Monad m => (a -> Bool) -> Fold m a (Maybe Int)
12291334findIndices predicate =
1335+ -- XXX implement by combining indexing and filtering scans
12301336 fmap (either (const Nothing) Just) $ foldl' step (Left (-1))
12311337
12321338 where
@@ -1236,6 +1342,15 @@ findIndices predicate =
12361342 then Right (either id id i + 1)
12371343 else Left (either id id i + 1)
12381344
1345+-- | Find all the indices where the value of the element in the stream is equal
1346+-- to the given value.
1347+--
1348+-- >>> elemIndices a = Fold.findIndices (== a)
1349+--
1350+{-# INLINE elemIndices #-}
1351+elemIndices :: (Monad m, Eq a) => a -> Fold m a (Maybe Int)
1352+elemIndices a = findIndices (== a)
1353+
12391354-- | Returns the first index where a given value is found in the stream.
12401355--
12411356-- > elemIndex a = Fold.findIndex (== a)
@@ -1393,6 +1508,73 @@ splitAt n fld = serialWith (,) (take n fld)
13931508-- Binary APIs
13941509------------------------------------------------------------------------------
13951510
1511+{-# INLINE takingEndByM #-}
1512+takingEndByM :: Monad m => (a -> m Bool) -> Fold m a (Maybe a)
1513+takingEndByM p = Fold step initial (return . toMaybe)
1514+
1515+ where
1516+
1517+ initial = return $ Partial Nothing'
1518+
1519+ step _ a = do
1520+ r <- p a
1521+ return
1522+ $ if r
1523+ then Done $ Just a
1524+ else Partial $ Just' a
1525+
1526+-- |
1527+--
1528+-- >>> takingEndBy p = Fold.takingEndByM (return . p)
1529+--
1530+{-# INLINE takingEndBy #-}
1531+takingEndBy :: Monad m => (a -> Bool) -> Fold m a (Maybe a)
1532+takingEndBy p = takingEndByM (return . p)
1533+
1534+{-# INLINE takingEndByM_ #-}
1535+takingEndByM_ :: Monad m => (a -> m Bool) -> Fold m a (Maybe a)
1536+takingEndByM_ p = Fold step initial (return . toMaybe)
1537+
1538+ where
1539+
1540+ initial = return $ Partial Nothing'
1541+
1542+ step _ a = do
1543+ r <- p a
1544+ return
1545+ $ if r
1546+ then Done Nothing
1547+ else Partial $ Just' a
1548+
1549+-- |
1550+--
1551+-- >>> takingEndBy_ p = Fold.takingEndByM_ (return . p)
1552+--
1553+{-# INLINE takingEndBy_ #-}
1554+takingEndBy_ :: Monad m => (a -> Bool) -> Fold m a (Maybe a)
1555+takingEndBy_ p = takingEndByM_ (return . p)
1556+
1557+{-# INLINE droppingWhileM #-}
1558+droppingWhileM :: Monad m => (a -> m Bool) -> Fold m a (Maybe a)
1559+droppingWhileM p = Fold step initial (return . toMaybe)
1560+
1561+ where
1562+
1563+ initial = return $ Partial Nothing'
1564+
1565+ step Nothing' a = do
1566+ r <- p a
1567+ return
1568+ $ Partial
1569+ $ if r
1570+ then Nothing'
1571+ else Just' a
1572+ step _ a = return $ Partial $ Just' a
1573+
1574+{-# INLINE droppingWhile #-}
1575+droppingWhile :: Monad m => (a -> Bool) -> Fold m a (Maybe a)
1576+droppingWhile p = droppingWhileM (return . p)
1577+
13961578-- Note: Keep this consistent with S.splitOn. In fact we should eliminate
13971579-- S.splitOn in favor of the fold.
13981580--
@@ -1414,6 +1596,7 @@ splitAt n fld = serialWith (,) (take n fld)
14141596-- @since 0.8.0
14151597{-# INLINE takeEndBy_ #-}
14161598takeEndBy_ :: Monad m => (a -> Bool) -> Fold m a b -> Fold m a b
1599+-- takeEndBy_ predicate = scanMaybe (takingEndBy_ predicate)
14171600takeEndBy_ predicate (Fold fstep finitial fextract) =
14181601 Fold step finitial fextract
14191602
@@ -1441,6 +1624,7 @@ takeEndBy_ predicate (Fold fstep finitial fextract) =
14411624-- @since 0.8.0
14421625{-# INLINE takeEndBy #-}
14431626takeEndBy :: Monad m => (a -> Bool) -> Fold m a b -> Fold m a b
1627+-- takeEndBy predicate = scanMaybe (takingEndBy predicate)
14441628takeEndBy predicate (Fold fstep finitial fextract) =
14451629 Fold step finitial fextract
14461630
@@ -2469,11 +2653,30 @@ zip = zipWithM (curry return)
24692653
24702654-- | Pair each element of a fold input with its index, starting from index 0.
24712655--
2472−-- /Unimplemented/
2656+{-# INLINE indexingWith #-}
2657+indexingWith :: Monad m => Int -> (Int -> Int) -> Fold m a (Maybe (Int, a))
2658+indexingWith i f = fmap toMaybe $ foldl' step initial
2659+
2660+ where
2661+
2662+ initial = Nothing'
2663+
2664+ step Nothing' a = Just' (i, a)
2665+ step (Just' (n, _)) a = Just' (f n, a)
2666+
2667+{-# INLINE indexing #-}
2668+indexing :: Monad m => Fold m a (Maybe (Int, a))
2669+indexing = indexingWith 0 (+ 1)
2670+
2671+{-# INLINE indexingRev #-}
2672+indexingRev :: Monad m => Int -> Fold m a (Maybe (Int, a))
2673+indexingRev n = indexingWith n (subtract 1)
2674+
2675+-- | Pair each element of a fold input with its index, starting from index 0.
2676+--
24732677{-# INLINE indexed #-}
2474−indexed :: -- forall m a b. Monad m =>
2475− Fold m (Int, a) b -> Fold m a b
2476−indexed = undefined -- zip (Stream.enumerateFrom 0 :: Stream m Int)
2678+indexed :: Monad m => Fold m (Int, a) b -> Fold m a b
2679+indexed = scanMaybe indexing
24772680
24782681-- | Change the predicate function of a Fold from @a -> b@ to accept an
24792682-- additional state input @(s, a) -> b@. Convenient to filter with an
@@ -2493,10 +2696,12 @@ with ::
24932696 -> (((s, a) -> c) -> Fold m a b -> Fold m a b)
24942697with f comb g = f . comb g . lmap snd
24952698
2699+-- XXX Implement as a filter
2700+-- sampleFromthen :: Monad m => Int -> Int -> Fold m a (Maybe a)
2701+
24962702-- | @sampleFromthen offset stride@ samples the element at @offset@ index and
24972703-- then every element at strides of @stride@.
24982704--
2499−-- /Unimplemented/
25002705{-# INLINE sampleFromthen #-}
25012706sampleFromthen :: Monad m => Int -> Int -> Fold m a b -> Fold m a b
25022707sampleFromthen offset size =
core/src/Streamly/Internal/Data/Fold/Type.hs+151−17
@@ -12,9 +12,22 @@
1212-- We can classify stream consumers in the following categories in order of
1313-- increasing complexity and power:
1414--
15−-- == Folds that never terminate
16−--
17−-- An Accumulator is the simplest type of fold, it never fails and never
15+-- * Accumulators: Tee/Zip is simple, cannot be appended, good for scanning.
16+-- * Terminating folds: Tee/Zip varies based on termination, can be appended,
17+-- good for scanning, nesting (many) is easy.
18+-- * Non-failing (backtracking only) parsers: cannot be used as scans because
19+-- of backtracking, nesting is complicated because of backtracking, appending
20+-- is efficient because of no Alternative, Alternative does not make sense
21+-- because it cannot fail.
22+-- * Parsers: Alternative on failure, appending is not as efficient because of
23+-- buffering for Alternative.
24+--
25+-- First two are represented by the 'Fold' type and the last two by the
26+-- 'Parser' type.
27+--
28+-- == Folds that never terminate (Accumulators)
29+--
30+-- An @Accumulator@ is the simplest type of fold, it never fails and never
1831-- terminates. It can always accept more inputs (never terminates) and the
1932-- accumulator is always valid. For example 'Streamly.Internal.Data.Fold.sum'.
2033-- Traditional Haskell left folds like 'foldl' are accumulators.
@@ -40,7 +53,7 @@
4053--
4154-- == Folds that terminate after one or more input
4255--
43−-- Terminating folds are accumulators that can terminate, like accumulators
56+-- @Terminating folds@ are accumulators that can terminate, like accumulators
4457-- they do not fail. Once a fold terminates it no longer accepts any more
4558-- inputs. Terminating folds can be appended, the next fold can be
4659-- applied after the first one terminates. Because they cannot fail, they do
@@ -130,7 +143,9 @@
130143-- wordBy
131144-- @
132145--
133−-- However, it creates several complications.
146+-- However, it creates several complications. The most important one is that we
147+-- cannot use such folds for scanning. We cannot backtrack after producing an
148+-- output in a scan.
134149--
135150-- === Nested backtracking
136151--
@@ -349,17 +364,22 @@ module Streamly.Internal.Data.Fold.Type
349364 -- ** Mapping Input
350365 , lmap
351366 , lmapM
367+ , postscan
352368
353369 -- ** Filtering
370+ , catMaybes
371+ , scanMaybe
354372 , filter
373+ , filtering
355374 , filterM
356− , catMaybes
357375 , lefts
358376 , rights
359377 , both
360378
361379 -- ** Trimming
362380 , take
381+ , taking
382+ , dropping
363383
364384 -- ** Sequential application
365385 , serialWith -- rename to "append"
@@ -395,10 +415,10 @@ module Streamly.Internal.Data.Fold.Type
395415where
396416
397417#include "inline.hs"
418+
398419import Control.Monad ((>=>))
399420import Data.Bifunctor (Bifunctor(..))
400421import Data.Either (fromLeft, fromRight, isLeft, isRight)
401−import Data.Maybe (isJust, fromJust)
402422import Fusion.Plugin.Types (Fuse(..))
403423import Streamly.Internal.Data.Fold.Step (Step(..), mapMStep, chainStepM)
404424import Streamly.Internal.Data.Maybe.Strict (Maybe'(..), toMaybe)
@@ -413,6 +433,7 @@ import Prelude hiding (concatMap, filter, foldr, map, take)
413433-- >>> :m
414434-- >>> :set -XFlexibleContexts
415435-- >>> :set -package streamly
436+-- >>> import Data.Maybe (fromJust, isJust)
416437-- >>> import Streamly.Data.Fold (Fold)
417438-- >>> import Streamly.Internal.Data.Stream.Type (Stream)
418439-- >>> import qualified Data.Foldable as Foldable
@@ -602,7 +623,7 @@ foldt' step initial extract =
602623-- | Make a terminating fold with an effectful step function and initial state,
603624-- and a state extraction function.
604625--
605−-- > mkFoldM = Fold
626+-- >>> foldtM' = Fold.Fold
606627--
607628-- We can just use 'Fold' but it is provided for completeness.
608629--
@@ -1110,26 +1131,113 @@ lmapM f (Fold step begin done) = Fold step' begin done
11101131 where
11111132 step' x a = f a >>= step x
11121133
1134+-- | Postscan the input of a 'Fold' to change it in a stateful manner using
1135+-- another 'Fold'.
1136+--
1137+-- @postscan scanner collector@
1138+--
1139+-- /Pre-release/
1140+{-# INLINE postscan #-}
1141+postscan :: Monad m => Fold m a b -> Fold m b c -> Fold m a c
1142+postscan (Fold stepL initialL extractL) (Fold stepR initialR extractR) =
1143+ Fold step initial extract
1144+
1145+ where
1146+
1147+ {-# INLINE runStep #-}
1148+ runStep actionL sR = do
1149+ rL <- actionL
1150+ case rL of
1151+ Done bL -> do
1152+ rR <- stepR sR bL
1153+ case rR of
1154+ Partial sR1 -> Done <$> extractR sR1
1155+ Done bR -> return $ Done bR
1156+ Partial sL -> do
1157+ !b <- extractL sL
1158+ rR <- stepR sR b
1159+ return
1160+ $ case rR of
1161+ Partial sR1 -> Partial (sL, sR1)
1162+ Done bR -> Done bR
1163+
1164+ initial = do
1165+ r <- initialR
1166+ rL <- initialL
1167+ case r of
1168+ Partial sR ->
1169+ case rL of
1170+ Done _ -> Done <$> extractR sR
1171+ Partial sL -> return $ Partial (sL, sR)
1172+ Done b -> return $ Done b
1173+
1174+ step (sL, sR) x = runStep (stepL sL x) sR
1175+
1176+ extract = extractR . snd
1177+
11131178------------------------------------------------------------------------------
11141179-- Filtering
11151180------------------------------------------------------------------------------
11161181
1182+-- | Modify a fold to receive a 'Maybe' input, the 'Just' values are unwrapped
1183+-- and sent to the original fold, 'Nothing' values are discarded.
1184+--
1185+-- >>> catMaybes = Fold.mapMaybe id
1186+-- >>> catMaybes = Fold.filter isJust . Fold.lmap fromJust
1187+--
1188+-- @since 0.8.0
1189+{-# INLINE_NORMAL catMaybes #-}
1190+catMaybes :: Monad m => Fold m a b -> Fold m (Maybe a) b
1191+catMaybes (Fold step initial extract) = Fold step1 initial extract
1192+
1193+ where
1194+
1195+ step1 s a =
1196+ case a of
1197+ Nothing -> return $ Partial s
1198+ Just x -> step s x
1199+
1200+-- | Use a 'Maybe' returning fold as a filtering scan.
1201+--
1202+-- >>> scanMaybe p f = Fold.postscan p (Fold.catMaybes f)
1203+--
1204+-- /Pre-release/
1205+{-# INLINE scanMaybe #-}
1206+scanMaybe :: Monad m => Fold m a (Maybe b) -> Fold m b c -> Fold m a c
1207+scanMaybe f1 f2 = postscan f1 (catMaybes f2)
1208+
1209+-- | A scanning fold for filtering elements based on a predicate.
1210+--
1211+{-# INLINE filtering #-}
1212+filtering :: Monad m => (a -> Bool) -> Fold m a (Maybe a)
1213+filtering f = foldl' step Nothing
1214+
1215+ where
1216+
1217+ step _ a = if f a then Just a else Nothing
1218+
11171219-- | Include only those elements that pass a predicate.
11181220--
11191221-- >>> Stream.fold (Fold.filter (> 5) Fold.sum) $ Stream.fromList [1..10]
11201222-- 40
11211223--
1122−-- > filter f = Fold.filterM (return . f)
1224+-- >>> filter p = Fold.scanMaybe (Fold.filtering p)
1225+-- >>> filter p = Fold.filterM (return . p)
1226+-- >>> filter p = Fold.mapMaybe (\x -> if p x then Just x else Nothing)
11231227--
11241228-- @since 0.8.0
11251229{-# INLINE filter #-}
11261230filter :: Monad m => (a -> Bool) -> Fold m a r -> Fold m a r
1231+-- filter p = scanMaybe (filtering p)
11271232filter f (Fold step begin done) = Fold step' begin done
11281233 where
11291234 step' x a = if f a then step x a else return $ Partial x
11301235
11311236-- | Like 'filter' but with a monadic predicate.
11321237--
1238+-- >>> f p x = p x >>= \r -> return $ if r then Just x else Nothing
1239+-- >>> filterM p = Fold.mapMaybeM (f p)
1240+--
11331241-- @since 0.8.0
11341242{-# INLINE filterM #-}
11351243filterM :: Monad m => (a -> m Bool) -> Fold m a r -> Fold m a r
@@ -1139,14 +1247,6 @@ filterM f (Fold step begin done) = Fold step' begin done
11391247 use <- f a
11401248 if use then step x a else return $ Partial x
11411249
1142−-- | Modify a fold to receive a 'Maybe' input, the 'Just' values are unwrapped
1143−-- and sent to the original fold, 'Nothing' values are discarded.
1144−--
1145−-- @since 0.8.0
1146−{-# INLINE_NORMAL catMaybes #-}
1147−catMaybes :: Monad m => Fold m a b -> Fold m (Maybe a) b
1148−catMaybes = filter isJust . lmap fromJust
1149−
11501250------------------------------------------------------------------------------
11511251-- Either streams
11521252------------------------------------------------------------------------------
@@ -1184,6 +1284,39 @@ both = lmap (either id id)
11841284{-# ANN type Tuple'Fused Fuse #-}
11851285data Tuple'Fused a b = Tuple'Fused !a !b deriving Show
11861286
1287+{-# INLINE taking #-}
1288+taking :: Monad m => Int -> Fold m a (Maybe a)
1289+taking n = foldt' step initial extract
1290+
1291+ where
1292+
1293+ initial =
1294+ if n <= 0
1295+ then Done Nothing
1296+ else Partial (Tuple'Fused n Nothing)
1297+
1298+ step (Tuple'Fused i _) a =
1299+ if i > 1
1300+ then Partial (Tuple'Fused (i - 1) (Just a))
1301+ else Done (Just a)
1302+
1303+ extract (Tuple'Fused _ r) = r
1304+
1305+{-# INLINE dropping #-}
1306+dropping :: Monad m => Int -> Fold m a (Maybe a)
1307+dropping n = foldt' step initial extract
1308+
1309+ where
1310+
1311+ initial = Partial (Tuple'Fused n Nothing)
1312+
1313+ step (Tuple'Fused i _) a =
1314+ if i > 0
1315+ then Partial (Tuple'Fused (i - 1) Nothing)
1316+ else Partial (Tuple'Fused i (Just a))
1317+
1318+ extract (Tuple'Fused _ r) = r
1319+
11871320-- | Take at most @n@ input elements and fold them using the supplied fold. A
11881321-- negative count is treated as 0.
11891322--
@@ -1193,6 +1326,7 @@ data Tuple'Fused a b = Tuple'Fused !a !b deriving Show
11931326-- @since 0.8.0
11941327{-# INLINE take #-}
11951328take :: Monad m => Int -> Fold m a b -> Fold m a b
1329+-- take n = scanMaybe (taking n)
11961330take n (Fold fstep finitial fextract) = Fold step initial extract
11971331
11981332 where
core/src/Streamly/Internal/Data/Fold/Window.hs+37
@@ -18,6 +18,9 @@
1818-- For more advanced statistical measures see the @streamly-statistics@
1919-- package.
2020
21+-- XXX A window fold can be driven either using the Ring.slidingWindow
22+-- combinator or by zipping nthLast fold and last fold.
23+
2124module Streamly.Internal.Data.Fold.Window
2225 (
2326 -- * Incremental Folds
@@ -36,6 +39,9 @@ module Streamly.Internal.Data.Fold.Window
3639 lmap
3740 , cumulative
3841
42+ , rollingMap
43+ , rollingMapM
44+
3945 -- ** Sums
4046 , length
4147 , sum
@@ -98,6 +104,37 @@ lmap f = Fold.lmap (bimap f (f <$>))
98104cumulative :: Fold m (a, Maybe a) b -> Fold m a b
99105cumulative = Fold.lmap (, Nothing)
100106
107+-- XXX Exchange the first two arguments of rollingMap or exchange the order in
108+-- the fold input tuple.
109+
110+-- | Apply an effectful function on the latest and the oldest element of the
111+-- window.
112+{-# INLINE rollingMapM #-}
113+rollingMapM :: Monad m =>
114+ (Maybe a -> a -> m (Maybe b)) -> Fold m (a, Maybe a) (Maybe b)
115+rollingMapM f = Fold.foldlM' f1 initial
116+
117+ where
118+
119+ initial = return Nothing
120+
121+ f1 _ (a, ma) = f ma a
122+
123+-- | Apply a pure function on the latest and the oldest element of the window.
124+--
125+-- >>> rollingMap f = FoldW.rollingMapM (\x y -> return $ f x y)
126+--
127+{-# INLINE rollingMap #-}
128+rollingMap :: Monad m =>
129+ (Maybe a -> a -> Maybe b) -> Fold m (a, Maybe a) (Maybe b)
130+rollingMap f = Fold.foldl' f1 initial
131+
132+ where
133+
134+ initial = Nothing
135+
136+ f1 _ (a, ma) = f ma a
137+
101138-------------------------------------------------------------------------------
102139-- Sum
103140-------------------------------------------------------------------------------
core/src/Streamly/Internal/Data/Stream/Bottom.hs+31
@@ -28,6 +28,9 @@ module Streamly.Internal.Data.Stream.Bottom
2828 , smapM
2929 -- $smapM_Notes
3030 , postscan
31+ , catMaybes
32+ , scanMaybe
33+
3134 , take
3235 , takeWhile
3336 , takeEndBy
@@ -79,6 +82,7 @@ import Streamly.Internal.Data.Stream.Type
7982-- >>> import Control.Monad (join)
8083-- >>> import Control.Monad.Trans.Class (lift)
8184-- >>> import Data.Function (fix, (&))
85+-- >>> import Data.Maybe (fromJust, isJust)
8286-- >>> import Data.Semigroup (cycle1)
8387-- >>> import Prelude hiding (take, takeWhile, drop, reverse)
8488-- >>> import System.IO.Unsafe (unsafePerformIO)
@@ -305,6 +309,28 @@ smapM step initial stream =
305309 (fmap (,undefined) initial)
306310 in fmap snd $ postscan f stream
307311
312+-- | In a stream of 'Maybe's, discard 'Nothing's and unwrap 'Just's.
313+--
314+-- >>> catMaybes = Stream.mapMaybe id
315+-- >>> catMaybes = fmap fromJust . Stream.filter isJust
316+--
317+-- /Pre-release/
318+--
319+{-# INLINE catMaybes #-}
320+catMaybes :: Monad m => Stream m (Maybe a) -> Stream m a
321+-- catMaybes = fmap fromJust . filter isJust
322+catMaybes = fromStreamD . D.catMaybes . toStreamD
323+
324+-- | Use a filtering fold on a stream.
325+--
326+-- >>> scanMaybe f = Stream.catMaybes . Stream.postscan f
327+--
328+-- /Pre-release/
329+--
330+{-# INLINE scanMaybe #-}
331+scanMaybe :: Monad m => Fold m a (Maybe b) -> Stream m a -> Stream m b
332+scanMaybe p = catMaybes . postscan p
333+
308334------------------------------------------------------------------------------
309335-- Transformation - Trimming
310336------------------------------------------------------------------------------
@@ -313,22 +339,26 @@ smapM step initial stream =
313339--
314340{-# INLINE take #-}
315341take :: Monad m => Int -> Stream m a -> Stream m a
342+-- take n = scanMaybe (Fold.taking n)
316343take n m = fromStreamD $ D.take n $ toStreamD m
317344
318345-- | End the stream as soon as the predicate fails on an element.
319346--
320347{-# INLINE takeWhile #-}
321348takeWhile :: Monad m => (a -> Bool) -> Stream m a -> Stream m a
349+-- takeWhile p = scanMaybe (Fold.takingEndBy_ (not . p))
322350takeWhile p m = fromStreamD $ D.takeWhile p $ toStreamD m
323351
324352{-# INLINE takeEndBy #-}
325353takeEndBy :: Monad m => (a -> Bool) -> Stream m a -> Stream m a
354+-- takeEndBy p = scanMaybe (Fold.takingEndBy p)
326355takeEndBy p m = fromStreamD $ D.takeEndBy p $ toStreamD m
327356
328357-- | Discard first 'n' elements from the stream and take the rest.
329358--
330359{-# INLINE drop #-}
331360drop :: Monad m => Int -> Stream m a -> Stream m a
361+-- drop n = scanMaybe (Fold.dropping n)
332362drop n m = fromStreamD $ D.drop n $ toStreamD m
333363
334364------------------------------------------------------------------------------
@@ -342,6 +372,7 @@ drop n m = fromStreamD $ D.drop n $ toStreamD m
342372--
343373{-# INLINE findIndices #-}
344374findIndices :: Monad m => (a -> Bool) -> Stream m a -> Stream m Int
375+-- findIndices p = scanMaybe (Fold.findIndices p)
345376findIndices p m = fromStreamD $ D.findIndices p (toStreamD m)
346377
347378------------------------------------------------------------------------------
core/src/Streamly/Internal/Data/Stream/StreamD/Transform.hs+26−16
@@ -68,9 +68,9 @@ module Streamly.Internal.Data.Stream.StreamD.Transform
6868
6969 -- * Filtering
7070 -- | Produce a subset of the stream.
71+ , scanMaybe
7172 , filter
7273 , filterM
73− , foldFilter
7474 , deleteBy
7575 , uniq
7676 , nub
@@ -120,6 +120,7 @@ module Streamly.Internal.Data.Stream.StreamD.Transform
120120 -- * Maybe Streams
121121 , mapMaybe
122122 , mapMaybeM
123+ , catMaybes
123124 )
124125where
125126
@@ -705,19 +706,6 @@ scanl1' f = scanl1M' (\x y -> return (f x y))
705706-- Filtering
706707-------------------------------------------------------------------------------
707708
708−-- XXX nested foldMany does not fuse, therefore, this may not be very useful as
709−-- a filter unless that is fixed.
710−--
711−-- | Use a filtering fold on a stream.
712−--
713−-- > Stream.sum $ Stream.foldFilter (Fold.satisfy (> 5)) $ Stream.fromList [1..10]
714−-- 40
715−--
716−-- /Pre-release/
717−{-# INLINE foldFilter #-}
718−foldFilter :: Monad m => Fold m a (Maybe b) -> Stream m a -> Stream m b
719−foldFilter f = catMaybes . foldMany f
720−
721709-- Adapted from the vector package
722710{-# INLINE_NORMAL filterM #-}
723711filterM :: Monad m => (a -> m Bool) -> Stream m a -> Stream m a
@@ -1259,5 +1247,27 @@ mapMaybeM :: Monad m => (a -> m (Maybe b)) -> Stream m a -> Stream m b
12591247mapMaybeM f = fmap fromJust . filter isJust . mapM f
12601248
12611249{-# INLINE catMaybes #-}
1262−catMaybes :: (Monad m) => Stream m (Maybe a) -> Stream m a
1263−catMaybes = fmap fromJust . filter isJust
1250+catMaybes :: Monad m => Stream m (Maybe a) -> Stream m a
1251+-- catMaybes = fmap fromJust . filter isJust
1252+catMaybes (Stream step state) = Stream step1 state
1253+
1254+ where
1255+
1256+ {-# INLINE_LATE step1 #-}
1257+ step1 gst st = do
1258+ r <- step (adaptState gst) st
1259+ case r of
1260+ Yield x s -> do
1261+ return
1262+ $ case x of
1263+ Just a -> Yield a s
1264+ Nothing -> Skip s
1265+ Skip s -> return $ Skip s
1266+ Stop -> return Stop
1267+
1268+-- | Use a filtering fold on a stream.
1269+--
1270+-- /Pre-release/
1271+{-# INLINE scanMaybe #-}
1272+scanMaybe :: Monad m => Fold m a (Maybe b) -> Stream m a -> Stream m b
1273+scanMaybe f = catMaybes . postscanOnce f
core/src/Streamly/Internal/Data/Stream/Transform.hs+60−59
@@ -38,14 +38,21 @@ module Streamly.Internal.Data.Stream.Transform
3838 -- the elements. We can use a concatMap and scan for filtering but these
3939 -- combinators are more efficient and convenient.
4040
41+ -- mapMaybeM is a general filtering combinator as we can map the stream to
42+ -- Just/Nothing using any stateful fold and then use this to filter out.
43+ , mapMaybeM
44+ , mapMaybe
45+ , catMaybes
46+ , scanMaybe
47+
4148 , with
4249 , deleteBy
4350 , filter
4451 , filterM
45− , foldFilter
52+
53+ -- Stateful/scanning filters
4654 , uniq
4755 , uniqBy
48− , nubBy
4956 , prune
5057 , repeated
5158
@@ -59,6 +66,22 @@ module Streamly.Internal.Data.Stream.Transform
5966 , dropWhile
6067 , dropWhileM
6168
69+ -- * Position Indexing
70+ , indexed
71+ , indexedR
72+
73+ -- * Searching
74+ , findIndices -- XXX indicesBy
75+ , elemIndices -- XXX indicesOf
76+
77+ -- * Rolling map
78+ -- | Map using the previous element.
79+ , rollingMapM
80+ , rollingMap
81+ , rollingMap2
82+
83+ -- Merge
84+
6285 -- * Inserting Elements
6386 -- | Produce a superset of the stream. This is the opposite of
6487 -- filtering/sampling. We can always use concatMap and scan for inserting
@@ -89,31 +112,13 @@ module Streamly.Internal.Data.Stream.Transform
89112 -- , intersperseByBefore
90113 -- , intersperseByAfter
91114
115+ -- Fold and Unfold, Buffering
116+
92117 -- * Reordering
93118 , reverse
94119 , reverse'
95120 , reassembleBy
96121
97− -- * Position Indexing
98− , indexed
99− , indexedR
100−
101− -- * Searching
102− , findIndices -- XXX indicesBy
103− , elemIndices -- XXX indicesOf
104−
105− -- * Rolling map
106− -- | Map using the previous element.
107− , rollingMapM
108− , rollingMap
109− , rollingMap2
110−
111− -- * Maybe Streams
112− -- Move these to Streamly.Data.Maybe.Stream?
113− , catMaybes -- XXX justs (like lefts/rights)
114− , mapMaybe
115− , mapMaybeM
116−
117122 -- * Either Streams
118123 -- Move these to Streamly.Data.Either.Stream?
119124 , lefts
@@ -135,6 +140,7 @@ import Streamly.Internal.Data.Fold.Type (Fold)
135140import Streamly.Internal.Data.Pipe (Pipe)
136141
137142import qualified Streamly.Internal.Data.Fold as FL
143+-- import qualified Streamly.Internal.Data.Fold.Window as Window
138144import qualified Streamly.Internal.Data.Stream.StreamD.Transform as D
139145import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
140146import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
@@ -153,12 +159,14 @@ import Prelude hiding
153159-- >>> import Control.Monad.IO.Class (MonadIO (liftIO))
154160-- >>> import Control.Monad.Trans (lift)
155161-- >>> import Control.Monad.Trans.Identity (runIdentityT)
162+-- >>> import Data.Either (fromLeft, fromRight, isLeft, isRight, either)
156163-- >>> import Data.Function ((&))
157164-- >>> import Data.Maybe (fromJust, isJust)
158165-- >>> import Prelude hiding (filter, drop, dropWhile, take, takeWhile, foldr, map, mapM, sequence, reverse, foldr1 , scanl, scanl1)
159166-- >>> import Streamly.Internal.Data.Stream as Stream
160167-- >>> import qualified Streamly.Data.Fold as Fold
161168-- >>> import qualified Streamly.Internal.Data.Fold as Fold
169+-- >>> import qualified Streamly.Internal.Data.Fold.Window as Window
162170-- >>> import qualified Streamly.Internal.Data.Unfold as Unfold
163171-- >>> import System.IO (stdout, hSetBuffering, BufferMode(LineBuffering))
164172--
@@ -432,28 +440,24 @@ with f comb g = fmap snd . comb g . f
432440
433441-- | Include only those elements that pass a predicate.
434442--
443+-- >>> filter p = Stream.filterM (return . p)
444+-- >>> filter p = Stream.mapMaybe (\x -> if p x then Just x else Nothing)
445+-- >>> filter p = Stream.scanMaybe (Fold.filtering p)
446+--
435447{-# INLINE filter #-}
436448filter :: Monad m => (a -> Bool) -> Stream m a -> Stream m a
449+-- filter p = scanMaybe (FL.filtering p)
437450filter p m = fromStreamD $ D.filter p $ toStreamD m
438451
439452-- | Same as 'filter' but with a monadic predicate.
440453--
454+-- >>> f p x = p x >>= \r -> return $ if r then Just x else Nothing
455+-- >>> filterM p = Stream.mapMaybeM (f p)
456+--
441457{-# INLINE filterM #-}
442458filterM :: Monad m => (a -> m Bool) -> Stream m a -> Stream m a
443459filterM p m = fromStreamD $ D.filterM p $ toStreamD m
444460
445−-- | Use a filtering fold on a stream.
446−--
447−-- >>> input = Stream.fromList [1..10]
448−-- >>> Stream.fold Fold.sum $ Stream.foldFilter (Fold.satisfy (> 5)) input
449−-- 40
450−--
451−-- /Pre-release/
452−--
453−{-# INLINE foldFilter #-}
454−foldFilter :: Monad m => Fold m a (Maybe b) -> Stream m a -> Stream m b
455−foldFilter p = fromStreamD . D.foldFilter p . toStreamD
456−
457461-- | Drop repeated elements that are adjacent to each other using the supplied
458462-- comparison function.
459463--
@@ -475,6 +479,7 @@ foldFilter p = fromStreamD . D.foldFilter p . toStreamD
475479{-# INLINE uniqBy #-}
476480uniqBy :: Monad m =>
477481 (a -> a -> Bool) -> Stream m a -> Stream m a
482+-- uniqBy eq = scanMaybe (FL.uniqBy eq)
478483uniqBy eq = catMaybes . rollingMap f
479484
480485 where
@@ -486,8 +491,11 @@ uniqBy eq = catMaybes . rollingMap f
486491
487492-- | Drop repeated elements that are adjacent to each other.
488493--
494+-- >>> uniq = Stream.uniqBy (==)
495+--
489496{-# INLINE uniq #-}
490497uniq :: (Eq a, Monad m) => Stream m a -> Stream m a
498+-- uniq = scanMaybe FL.uniq
491499uniq = fromStreamD . D.uniq . toStreamD
492500
493501-- | Strip all leading and trailing occurrences of an element passing a
@@ -524,21 +532,6 @@ repeated :: -- (Monad m, Eq a) =>
524532 Stream m a -> Stream m a
525533repeated = undefined
526534
527−-- We can have more efficient implementations for nubOrd and nubInt by using
528−-- Set and IntSet to find/remove duplication. For Hashable we can use a
529−-- hashmap. Use rewrite rules to specialize to more efficient impls.
530−
531−-- | Drop repeated elements anywhere in the stream.
532−--
533−-- /Caution: not scalable for infinite streams/
534−--
535−-- /Unimplemented/
536−--
537−{-# INLINE nubBy #-}
538−nubBy :: -- Monad m =>
539− (a -> a -> Bool) -> Stream m a -> Stream m a
540−nubBy = undefined -- fromStreamD . D.nubBy . toStreamD
541−
542535-- | Deletes the first occurrence of the element in the stream that satisfies
543536-- the given equality predicate.
544537--
@@ -548,6 +541,7 @@ nubBy = undefined -- fromStreamD . D.nubBy . toStreamD
548541--
549542{-# INLINE deleteBy #-}
550543deleteBy :: Monad m => (a -> a -> Bool) -> a -> Stream m a -> Stream m a
544+-- deleteBy cmp x = scanMaybe (FL.deleteBy cmp x)
551545deleteBy cmp x m = fromStreamD $ D.deleteBy cmp x (toStreamD m)
552546
553547------------------------------------------------------------------------------
@@ -558,6 +552,7 @@ deleteBy cmp x m = fromStreamD $ D.deleteBy cmp x (toStreamD m)
558552--
559553{-# INLINE takeWhileM #-}
560554takeWhileM :: Monad m => (a -> m Bool) -> Stream m a -> Stream m a
555+-- takeWhileM p = scanMaybe (FL.takingEndByM_ (\x -> not <$> p x))
561556takeWhileM p m = fromStreamD $ D.takeWhileM p $ toStreamD m
562557
563558-- | Drop elements in the stream as long as the predicate succeeds and then
@@ -565,12 +560,14 @@ takeWhileM p m = fromStreamD $ D.takeWhileM p $ toStreamD m
565560--
566561{-# INLINE dropWhile #-}
567562dropWhile :: Monad m => (a -> Bool) -> Stream m a -> Stream m a
563+-- dropWhile p = scanMaybe (FL.droppingWhile p)
568564dropWhile p m = fromStreamD $ D.dropWhile p $ toStreamD m
569565
570566-- | Same as 'dropWhile' but with a monadic predicate.
571567--
572568{-# INLINE dropWhileM #-}
573569dropWhileM :: Monad m => (a -> m Bool) -> Stream m a -> Stream m a
570+-- dropWhileM p = scanMaybe (FL.droppingWhileM p)
574571dropWhileM p m = fromStreamD $ D.dropWhileM p $ toStreamD m
575572
576573------------------------------------------------------------------------------
@@ -763,6 +760,7 @@ reassembleBy = undefined
763760--
764761{-# INLINE indexed #-}
765762indexed :: Monad m => Stream m a -> Stream m (Int, a)
763+-- indexed = scanMaybe FL.indexing
766764indexed = fromStreamD . D.indexed . toStreamD
767765
768766-- |
@@ -780,6 +778,7 @@ indexed = fromStreamD . D.indexed . toStreamD
780778--
781779{-# INLINE indexedR #-}
782780indexedR :: Monad m => Int -> Stream m a -> Stream m (Int, a)
781+-- indexedR n = scanMaybe (FL.indexingRev n)
783782indexedR n = fromStreamD . D.indexedR n . toStreamD
784783
785784------------------------------------------------------------------------------
@@ -811,6 +810,7 @@ elemIndices a = findIndices (== a)
811810--
812811{-# INLINE rollingMap #-}
813812rollingMap :: Monad m => (Maybe a -> a -> b) -> Stream m a -> Stream m b
813+-- rollingMap f = scanMaybe (FL.slide2 $ Window.rollingMap f)
814814rollingMap f m = fromStreamD $ D.rollingMap f $ toStreamD m
815815
816816-- | Like 'rollingMap' but with an effectful map function.
@@ -819,6 +819,7 @@ rollingMap f m = fromStreamD $ D.rollingMap f $ toStreamD m
819819--
820820{-# INLINE rollingMapM #-}
821821rollingMapM :: Monad m => (Maybe a -> a -> m b) -> Stream m a -> Stream m b
822+-- rollingMapM f = scanMaybe (FL.slide2 $ Window.rollingMapM f)
822823rollingMapM f m = fromStreamD $ D.rollingMapM f $ toStreamD m
823824
824825-- | Like 'rollingMap' but requires at least two elements in the stream,
@@ -841,7 +842,7 @@ rollingMap2 f m = fromStreamD $ D.rollingMap2 f $ toStreamD m
841842--
842843-- Equivalent to:
843844--
844−-- >>> mapMaybe f = fmap fromJust . Stream.filter isJust . fmap f
845+-- >>> mapMaybe f = Stream.catMaybes . fmap f
845846--
846847{-# INLINE mapMaybe #-}
847848mapMaybe :: Monad m => (a -> Maybe b) -> Stream m a -> Stream m b
@@ -851,27 +852,23 @@ mapMaybe f m = fromStreamD $ D.mapMaybe f $ toStreamD m
851852--
852853-- Equivalent to:
853854--
854−-- >>> mapMaybeM f = fmap fromJust . Stream.filter isJust . Stream.mapM f
855+-- >>> mapMaybeM f = Stream.catMaybes . Stream.mapM f
856+--
857+-- >>> mapM f = Stream.mapMaybeM (\x -> Just <$> f x)
855858--
856859{-# INLINE_EARLY mapMaybeM #-}
857860mapMaybeM :: Monad m
858861 => (a -> m (Maybe b)) -> Stream m a -> Stream m b
859862mapMaybeM f = fmap fromJust . filter isJust . mapM f
860863
861−-- | In a stream of 'Maybe's, discard 'Nothing's and unwrap 'Just's.
862−--
863−-- /Pre-release/
864−--
865−{-# INLINE catMaybes #-}
866−catMaybes :: Monad m => Stream m (Maybe a) -> Stream m a
867−catMaybes = fmap fromJust . filter isJust
868−
869864------------------------------------------------------------------------------
870865-- Either streams
871866------------------------------------------------------------------------------
872867
873868-- | Discard 'Right's and unwrap 'Left's in an 'Either' stream.
874869--
870+-- >>> lefts = fmap (fromLeft undefined) . Stream.filter isLeft
871+--
875872-- /Pre-release/
876873--
877874{-# INLINE lefts #-}
@@ -880,6 +877,8 @@ lefts = fmap (fromLeft undefined) . filter isLeft
880877
881878-- | Discard 'Left's and unwrap 'Right's in an 'Either' stream.
882879--
880+-- >>> rights = fmap (fromRight undefined) . Stream.filter isRight
881+--
883882-- /Pre-release/
884883--
885884{-# INLINE rights #-}
@@ -889,6 +888,8 @@ rights = fmap (fromRight undefined) . filter isRight
889888-- | Remove the either wrapper and flatten both lefts and as well as rights in
890889-- the output stream.
891890--
891+-- >>> both = fmap (either id id)
892+--
892893-- /Pre-release/
893894--
894895{-# INLINE both #-}