on b95b3c1 help sign in
15 files changed+324−506
since splitsnapshots
unifiedsplit
core/src/DocTestDataStream.hs−2 core/src/Streamly/Data/Stream.hs+20−129 core/src/Streamly/Internal/Data/Stream/Container.hs+48−56 core/src/Streamly/Internal/Data/Stream/Eliminate.hs+23−3 core/src/Streamly/Internal/Data/Stream/Exception.hs+6−9 core/src/Streamly/Internal/Data/Stream/Generate.hs+12 core/src/Streamly/Internal/Data/Stream/Nesting.hs+64−10 core/src/Streamly/Internal/Data/Stream/Top.hs+115−243 core/src/Streamly/Internal/Data/Stream/Transform.hs+26−1 core/src/Streamly/Internal/Data/Stream/Transformer.hs+1−2 core/src/Streamly/Internal/Data/Stream/Type.hs+3−31 core/src/Streamly/Internal/Data/Unfold.hs+4−6 core/src/Streamly/Internal/Unicode/Stream.hs+1−1 docs/Developer/optimization-guidelines.md−12 src/Streamly/Internal/Data/Stream/IsStream/Top.hs+1−1
core/src/DocTestDataStream.hs−2
@@ -6,7 +6,6 @@
66>>> import Control.Monad.IO.Class (MonadIO (liftIO))
77>>> import Control.Monad.Trans.Class (lift)
88>>> import Control.Monad.Trans.Identity (runIdentityT)
9−>>> import Data.Char (isSpace)
109>>> import Data.Either (fromLeft, fromRight, isLeft, isRight, either)
1110>>> import Data.Maybe (fromJust, isJust)
1211>>> import Data.Function (fix, (&))
@@ -33,7 +32,6 @@ For APIs that have not been released yet.
3332>>> import qualified Streamly.Internal.Data.Fold as Fold
3433>>> import qualified Streamly.Internal.Data.Parser as Parser
3534>>> import qualified Streamly.Internal.Data.Stream as Stream
36−>>> import qualified Streamly.Internal.Data.StreamK as StreamK
3735>>> import qualified Streamly.Internal.Data.Unfold as Unfold
3836>>> import qualified Streamly.Internal.FileSystem.Dir as Dir
3937-}
core/src/Streamly/Data/Stream.hs+20−129
@@ -70,15 +70,6 @@ module Streamly.Data.Stream
7070
7171 -- ** From Values
7272 -- | Generate a monadic stream from a seed value or values.
73− --
74− -- Non-empty pure streams can be cycled using repeat and unfold:
75− --
76− -- >>> cycle = Stream.unfoldMany Unfold.fromList . Stream.repeat
77− --
78− -- Non-empty StreamK can be cycled using append:
79− --
80− -- >>> cycle xs = let ys = xs `StreamK.append` ys in ys
81− --
8273 , fromPure
8374 , fromEffect
8475 , repeat
@@ -221,57 +212,21 @@ module Streamly.Data.Stream
221212 -- operations like mapping a function over the stream.
222213 , foldrM
223214 , foldr
224− -- foldr1
225215
226216 -- ** Specific Folds
227− -- | Streams are folded using folds in "Streamly.Data.Fold". Here are some
228− -- idioms and equivalents of Data.List APIs using folds:
217+ -- | Usually you can use the folds in "Streamly.Data.Fold". However, some
218+ -- folds that may be commonly used or may have an edge in performance in
219+ -- some cases are provided here.
220+ --
221+ -- Useful idioms:
229222 --
230223 -- >>> foldlM' f a = Stream.fold (Fold.foldlM' f a)
231− -- >>> foldl1' f = Stream.fold (Fold.foldl1' f)
224+ -- >>> foldl1 f = Stream.fold (Fold.foldl1' f)
232225 -- >>> foldl' f a = Stream.fold (Fold.foldl' f a)
233226 -- >>> drain = Stream.fold Fold.drain
234227 -- >>> mapM_ f = Stream.fold (Fold.drainMapM f)
235228 -- >>> length = Stream.fold Fold.length
236− -- >>> genericLength = Stream.fold Fold.lengthGeneric
237229 -- >>> head = Stream.fold Fold.one
238− -- >>> last = Stream.fold Fold.latest
239− -- >>> null = Stream.fold Fold.null
240− -- >>> and = Stream.fold Fold.and
241− -- >>> or = Stream.fold Fold.or
242− -- >>> any p = Stream.fold (Fold.any p)
243− -- >>> all p = Stream.fold (Fold.all p)
244− -- >>> sum = Stream.fold Fold.sum
245− -- >>> product = Stream.fold Fold.product
246− -- >>> maximum = Stream.fold Fold.maximum
247− -- >>> maximumBy cmp = Stream.fold (Fold.maximumBy cmp)
248− -- >>> minimum = Stream.fold Fold.minimum
249− -- >>> minimumBy cmp = Stream.fold (Fold.minimumBy cmp)
250− -- >>> elem x = Stream.fold (Fold.elem x)
251− -- >>> notElem x = Stream.fold (Fold.notElem x)
252− -- >>> lookup x = Stream.fold (Fold.lookup x)
253− -- >>> find p = Stream.fold (Fold.find p)
254− -- >>> (!?) i = Stream.fold (Fold.index i)
255− -- >>> genericIndex i = Stream.fold (Fold.indexGeneric i)
256− -- >>> elemIndex x = Stream.fold (Fold.elemIndex x)
257− -- >>> findIndex p = Stream.fold (Fold.findIndex p)
258− --
259− -- Some equivalents of Data.List APIs from the Stream module:
260− --
261− -- >>> head = fmap (fmap fst) . Stream.uncons
262− -- >>> tail = fmap (fmap snd) . Stream.uncons
263− -- >>> tail = Stream.tail -- unreleased API
264− -- >>> init = Stream.init -- unreleased API
265− --
266− -- A Stream based toList fold implementation is provided below because it
267− -- has a better performance compared to the fold.
268−
269− -- Functions in Data.List, missing here:
270− -- unsnoc = Stream.parseBreak (Parser.init Fold.toList)
271− -- genericTake
272− -- genericDrop
273− -- genericSplitAt
274− -- genericReplicate
275230 , toList
276231
277232 -- * Mapping
@@ -325,15 +280,6 @@ module Streamly.Data.Stream
325280 , scan
326281 , postscan
327282 -- XXX postscan1 can be implemented using Monoids or Refolds.
328− -- The following scans from Data.List are not provided.
329− -- XXX scanl
330− -- XXX scanl1
331− -- XXX scanr
332− -- XXX scanr1
333− -- XXX mapAccumL
334− -- XXX mapAccumR
335− -- XXX inits
336− -- XXX tails
337283
338284 -- ** Specific scans
339285 -- Indexing can be considered as a special type of zipping where we zip a
@@ -342,8 +288,6 @@ module Streamly.Data.Stream
342288
343289 -- * Insertion
344290 -- | Add elements to the stream.
345− --
346− -- >>> insert = Stream.insertBy compare
347291
348292 -- Inserting elements is a special case of interleaving/merging streams.
349293 , insertBy
@@ -379,15 +323,12 @@ module Streamly.Data.Stream
379323 -- "Streamly.Internal.Data.Fold" can be used along with 'scanMaybe' to
380324 -- perform stateful filtering operations in general.
381325 --
382− -- Idioms and equivalents of Data.List APIs:
326+ -- Useful idioms:
383327 --
384328 -- >>> deleteBy cmp x = Stream.scanMaybe (Fold.deleteBy cmp x)
385− -- >>> deleteBy = Stream.deleteBy -- unreleased API
386− -- >>> delete = deleteBy (==)
387329 -- >>> findIndices p = Stream.scanMaybe (Fold.findIndices p)
388330 -- >>> elemIndices a = findIndices (== a)
389331 -- >>> uniq = Stream.scanMaybe (Fold.uniqBy (==))
390− -- >>> partition p = Stream.fold (Fold.partition Fold.toList Fold.toList) . fmap (if p then Left else Right)
391332 , scanMaybe
392333 , take
393334 , takeWhile
@@ -395,8 +336,6 @@ module Streamly.Data.Stream
395336 , drop
396337 , dropWhile
397338 , dropWhileM
398− -- XXX write to an array in reverse and then read in reverse
399− -- > dropWhileEnd = reverse . dropWhile p . reverse
400339
401340 -- XXX These are available as scans in folds. We need to check the
402341 -- performance though. If these are common and we need convenient stream
@@ -433,9 +372,6 @@ module Streamly.Data.Stream
433372 -- >>> s = s1 `Stream.append` s2
434373
435374 -- ** Appending
436− -- | Equivalent of Data.List append:
437− --
438− -- >>> (++) = Stream.append
439375 , append
440376
441377 -- ** Interleaving
@@ -446,14 +382,8 @@ module Streamly.Data.Stream
446382 , mergeByM
447383
448384 -- ** Zipping
449− -- | Idioms and equivalents of Data.List APIs:
450− --
451− -- >>> zip = Stream.zipWith (,)
452− -- >>> unzip = Stream.fold (Fold.unzip Fold.toList Fold.toList)
453385 , zipWith
454386 , zipWithM
455− -- XXX zipWith3,4,5,6,7
456− -- XXX unzip3,4,5,6,7
457387 -- , ZipStream (..)
458388
459389 -- ** Cross Product
@@ -486,89 +416,48 @@ module Streamly.Data.Stream
486416 -- One dimension loops are just a special case of nested loops. For
487417 -- example, 'concatMap' can degenerate to a simple map operation:
488418 --
489− -- >>> map f m = Stream.concatMap (\x -> Stream.fromPure (f x)) m
419+ -- > map f m = S.concatMap (\x -> S.fromPure (f x)) m
490420 --
491421 -- Similarly, 'concatMap' can perform filtering by mapping an element to a
492422 -- 'nil' stream:
493423 --
494− -- >>> filter p m = Stream.concatMap (\x -> if p x then Stream.fromPure x else Stream.nil) m
495− --
496− -- Idioms and equivalents of Data.List APIs:
424+ -- > filter p m = S.concatMap (\x -> if p x then S.fromPure x else S.nil) m
497425 --
498− -- >>> concat = Stream.concatMap id
499− -- >>> unlines = Stream.interposeSuffix '\n'
500− -- >>> unlines = Stream.intercalateSuffix Unfold.fromList "\n"
501− -- >>> unwords = Stream.interpose ' '
502− -- >>> unwords = Stream.intercalate Unfold.fromList " "
503426
504427 , concatEffect
505428 , concatMap
506429 , concatMapM
507430
508431 -- * Repeated Fold
509− -- | Idioms and equivalents of Data.List APIs:
432+ -- | Useful idioms:
510433 --
511− -- >>> groupsOf n = Stream.foldMany (Fold.take n Fold.toList)
512− -- >>> groupBy eq = Stream.groupsWhile eq Fold.toList
513− -- >>> groupBy eq = Stream.parseMany (Parser.groupBy eq Fold.toList)
514− -- >>> groupsByRolling eq = Stream.parseMany (Parser.groupByRolling eq Fold.toList)
515− -- >>> groups = groupBy (==)
434+ -- >>> splitWithSuffix p f = Stream.foldMany (Fold.takeEndBy p f)
435+ -- >>> splitOnSuffix p f = Stream.foldMany (Fold.takeEndBy_ p f)
436+ -- >>> groupsBy eq f = Stream.parseMany (Parser.groupBy eq f)
437+ -- >>> groupsByRolling eq f = Stream.parseMany (Parser.groupByRolling eq f)
438+ -- >>> groupsOf n f = Stream.foldMany (Fold.take n f)
516439 , foldMany -- XXX Rename to foldRepeat
517440 , groupsOf
518441 , parseMany
519442
520443 -- * Splitting
521− -- | Idioms and equivalents of Data.List APIs:
522− --
523− -- >>> splitWithSuffix p f = Stream.foldMany (Fold.takeEndBy p f)
524− -- >>> splitOnSuffix p f = Stream.foldMany (Fold.takeEndBy_ p f)
525− -- >>> lines = splitOnSuffix (== '\n')
526− -- >>> words = Stream.wordsBy isSpace
527− -- >>> splitAt n = Stream.fold (Fold.splitAt n Fold.toList Fold.toList)
528− -- >>> span p = Parser.splitWith (,) (Parser.takeWhile p Fold.toList) (Parser.fromFold Fold.toList)
529− -- >>> break p = span (not . p)
530444 , splitOn
531445 , wordsBy
532446
533447 -- * Buffered Operations
534448 -- | Operations that require buffering of the stream.
535449 -- Reverse is essentially a left fold followed by an unfold.
536− --
537− -- Idioms and equivalents of Data.List APIs:
538− --
539− -- >>> nub = Stream.fold Fold.toList . Stream.scanMaybe Fold.nub
540− -- >>> nub = Stream.ordNub -- unreleased API
541− -- >>> sortBy = StreamK.sortBy
542− -- >>> sortOn f = StreamK.sortOn -- unreleased API
543− -- >>> deleteFirstsBy = Stream.deleteFirstsBy -- unreleased
544− -- >>> (\\) = Stream.deleteFirstsBy (==) -- unreleased
545− -- >>> intersectBy = Stream.intersectBy -- unreleased
546− -- >>> intersect = Stream.intersectBy (==) -- unreleased
547− -- >>> unionBy = Stream.unionBy -- unreleased
548− -- >>> union = Stream.unionBy (==) -- unreleased
549− --
550450 , reverse
551− -- XXX transpose: write the streams to arrays and then stream transposed.
552− -- XXX subsequences
553− -- XXX permutations
554− -- , nub
555− -- , ordNub
556− -- , nubBy
557451
558452 -- * Multi-Stream folds
559453 -- | Operations that consume multiple streams at the same time.
560454 , eqBy
561455 , cmpBy
562456 , isPrefixOf
563− , isInfixOf
564− -- , isSuffixOf
565− -- , isSuffixOfUnbox
566457 , isSubsequenceOf
567458
568459 -- trimming sequences
569− -- , stripPrefix
570− -- , stripSuffix
571− -- , stripSuffixUnbox
460+ , stripPrefix
572461
573462 -- Exceptions and resource management depend on the "exceptions" package
574463 -- XXX We can have IO Stream operations not depending on "exceptions"
@@ -632,8 +521,10 @@ where
632521import Streamly.Internal.Data.Stream
633522import Prelude
634523 hiding (filter, drop, dropWhile, take, takeWhile, zipWith, foldr,
635− mapM, sequence, reverse, iterate, foldr1, repeat, replicate,
636− concatMap)
524+ foldl, map, mapM, mapM_, sequence, all, any, sum, product, elem,
525+ notElem, maximum, minimum, head, last, tail, length, null,
526+ reverse, iterate, init, and, or, lookup, foldr1, (!!),
527+ scanl, scanl1, repeat, replicate, concatMap, span)
637528
638529import qualified Streamly.Internal.Data.Array.Type as Array
639530
core/src/Streamly/Internal/Data/Stream/Container.hs+48−56
@@ -11,17 +11,16 @@
1111
1212module Streamly.Internal.Data.Stream.Container
1313 (
14− -- * Deduplication
15− ordNub
14+ nub
1615
17− -- * Joins
18− , leftJoin
19− , outerJoin
16+ -- * Joins for unconstrained types
17+ , joinLeftGeneric
18+ , joinOuterGeneric
2019
21− -- * Ord Joins
22− , innerOrdJoin
23− , leftOrdJoin
24− , outerOrdJoin
20+ -- * Joins with Ord constraint
21+ , joinInner
22+ , joinLeft
23+ , joinOuter
2524 )
2625where
2726
@@ -48,15 +47,12 @@ import qualified Streamly.Internal.Data.Stream.Transformer as Stream
4847
4948#include "DocTestDataStream.hs"
5049
51−-- | @nub@ specialized to 'Ord' types for better performance. Returns a
52−-- subsequence of the stream removing any duplicate elements.
53−--
54−-- The memory used is proportional to the number of unique elements in the
55−-- stream. One way to limit the memory is to use @take@ on the resulting
56−-- stream to limit the unique elements in the stream.
57−{-# INLINE_NORMAL ordNub #-}
58−ordNub :: (Monad m, Ord a) => Stream m a -> Stream m a
59−ordNub (Stream step1 state1) = Stream step (Set.empty, state1)
50+-- | The memory used is proportional to the number of unique elements in the
51+-- stream. If we want to limit the memory we can just use "take" to limit the
52+-- uniq elements in the stream.
53+{-# INLINE_NORMAL nub #-}
54+nub :: (Monad m, Ord a) => Stream m a -> Stream m a
55+nub (Stream step1 state1) = Stream step (Set.empty, state1)
6056
6157 where
6258
@@ -82,8 +78,8 @@ toMap =
8278--
8379-- XXX An IntMap may be faster when the keys are Int.
8480-- XXX Use hashmap instead of map?
85−
86−-- | 'innerJoin' specialized to 'Ord' types for better performance.
81+--
82+-- | Like 'joinInner' but uses a 'Map' for efficiency.
8783--
8884-- If the input streams have duplicate keys, the behavior is undefined.
8985--
@@ -94,10 +90,10 @@ toMap =
9490-- Time: O(m + n)
9591--
9692-- /Pre-release/
97−{-# INLINE innerOrdJoin #-}
98−innerOrdJoin :: (Monad m, Ord k) =>
93+{-# INLINE joinInner #-}
94+joinInner :: (Monad m, Ord k) =>
9995 Stream m (k, a) -> Stream m (k, b) -> Stream m (k, a, b)
100−innerOrdJoin s1 s2 =
96+joinInner s1 s2 =
10197 Stream.concatEffect $ do
10298 km <- toMap s2
10399 pure $ Stream.mapMaybe (joinAB km) s1
@@ -110,33 +106,32 @@ innerOrdJoin s1 s2 =
110106 Nothing -> Nothing
111107
112108-- XXX We can do this concurrently.
113−-- XXX Check performance of StreamD vs StreamK
114109-- XXX If the second stream is sorted and passed as an Array or a seek capable
115110-- stream then we could use binary search if we have an Ord instance or
116111-- Ordering returning function. The time complexity would then become (m x log
117112-- n).
118113
119−-- | Like 'innerJoin' but emits @(a, Just b)@ whenever a and b are equal, for
120−-- those @a@'s that are not equal to any @b@ emits @(a, Nothing)@.
121−--
122−-- This is a generalization of 'innerJoin' to include all elements from the
123−-- left stream and not just those which have an equal in the right stream. This
124−-- is not a commutative operation, the order of the stream arguments matters.
114+-- XXX Check performance of StreamD vs StreamK
115+
116+-- | Like 'joinInner' but emit @(a, Just b)@, and additionally, for those @a@'s
117+-- that are not equal to any @b@ emit @(a, Nothing)@.
125118--
126−-- All the caveats mentioned in 'innerJoin' apply here as well. Right join is
127−-- not provided because it is just a flipped left join:
119+-- The second stream is evaluated multiple times. If the stream is a
120+-- consume-once stream then the caller should cache it in an 'Data.Array.Array'
121+-- before calling this function. Caching may also improve performance if the
122+-- stream is expensive to evaluate.
128123--
129−-- >>> rightJoin eq = flip (Stream.leftJoin eq)
124+-- >>> joinRightGeneric eq = flip (Stream.joinLeftGeneric eq)
130125--
131126-- Space: O(n) assuming the second stream is cached in memory.
132127--
133128-- Time: O(m x n)
134129--
135130-- /Unimplemented/
136−{-# INLINE leftJoin #-}
137−leftJoin :: Monad m =>
131+{-# INLINE joinLeftGeneric #-}
132+joinLeftGeneric :: Monad m =>
138133 (a -> b -> Bool) -> Stream m a -> Stream m b -> Stream m (a, Maybe b)
139−leftJoin eq s1 s2 = Stream.evalStateT (return False) $ unCross $ do
134+joinLeftGeneric eq s1 s2 = Stream.evalStateT (return False) $ unCross $ do
140135 a <- mkCross (Stream.liftInner s1)
141136 -- XXX should we use StreamD monad here?
142137 -- XXX Is there a better way to perform some action at the end of a loop
@@ -157,17 +152,19 @@ leftJoin eq s1 s2 = Stream.evalStateT (return False) $ unCross $ do
157152 else mkCross Stream.nil
158153 Nothing -> return (a, Nothing)
159154
160−-- | 'leftJoin' specialized to 'Ord' types for better performance.
155+-- XXX rename to joinLeftOrd?
156+
157+-- | A more efficient 'joinLeft' using a hashmap for efficiency.
161158--
162159-- Space: O(n)
163160--
164161-- Time: O(m + n)
165162--
166163-- /Pre-release/
167−{-# INLINE leftOrdJoin #-}
168−leftOrdJoin :: (Ord k, Monad m) =>
164+{-# INLINE joinLeft #-}
165+joinLeft :: (Ord k, Monad m) =>
169166 Stream m (k, a) -> Stream m (k, b) -> Stream m (k, a, Maybe b)
170−leftOrdJoin s1 s2 =
167+joinLeft s1 s2 =
171168 Stream.concatEffect $ do
172169 km <- toMap s2
173170 return $ fmap (joinAB km) s1
@@ -180,19 +177,14 @@ leftOrdJoin s1 s2 =
180177 Nothing -> (k, a, Nothing)
181178
182179-- XXX We can do this concurrently.
183−-- XXX Check performance of StreamD vs StreamK cross operation.
184180
185−-- | Like 'leftJoin' but emits a @(Just a, Just b)@. Like 'leftJoin', for those
181+-- XXX Check performance of StreamD vs StreamK
182+
183+-- | Like 'joinLeft' but emits a @(Just a, Just b)@. Like 'joinLeft', for those
186184-- @a@'s that are not equal to any @b@ emit @(Just a, Nothing)@, but
187185-- additionally, for those @b@'s that are not equal to any @a@ emit @(Nothing,
188186-- Just b)@.
189187--
190−-- This is a generalization of left join to include all the elements from the
191−-- right stream as well, in other words it is a combination of left and right
192−-- joins. This is a commutative operation. The order of stream arguments can be
193−-- changed without affecting results, except for the ordering of elements in
194−-- the resulting tuple.
195−--
196188-- For space efficiency use the smaller stream as the second stream.
197189--
198190-- Space: O(n)
@@ -200,15 +192,15 @@ leftOrdJoin s1 s2 =
200192-- Time: O(m x n)
201193--
202194-- /Pre-release/
203−{-# INLINE outerJoin #-}
204−outerJoin :: MonadIO m =>
195+{-# INLINE joinOuterGeneric #-}
196+joinOuterGeneric :: MonadIO m =>
205197 (a -> b -> Bool)
206198 -> Stream m a
207199 -> Stream m b
208200 -> Stream m (Maybe a, Maybe b)
209−outerJoin eq s1 s2 =
201+joinOuterGeneric eq s1 s =
210202 Stream.concatEffect $ do
211− inputArr <- Array.fromStream s2
203+ inputArr <- Array.fromStream s
212204 let len = Array.length inputArr
213205 foundArr <-
214206 Stream.fold
@@ -262,18 +254,18 @@ outerJoin eq s1 s2 =
262254-- a flag. At the end go through @Stream m b@ and find those that are not in that
263255-- hash to return (Nothing, b).
264256
265−-- | 'outerJoin' specialized to 'Ord' types for better performance.
257+-- | Like 'joinOuter' but uses a 'Map' for efficiency.
266258--
267259-- Space: O(m + n)
268260--
269261-- Time: O(m + n)
270262--
271263-- /Pre-release/
272−{-# INLINE outerOrdJoin #-}
273−outerOrdJoin ::
264+{-# INLINE joinOuter #-}
265+joinOuter ::
274266 (Ord k, MonadIO m) =>
275267 Stream m (k, a) -> Stream m (k, b) -> Stream m (k, Maybe a, Maybe b)
276−outerOrdJoin s1 s2 =
268+joinOuter s1 s2 =
277269 Stream.concatEffect $ do
278270 km1 <- kvFold s1
279271 km2 <- kvFold s2
core/src/Streamly/Internal/Data/Stream/Eliminate.hs+23−3
@@ -13,19 +13,32 @@
1313--
1414module Streamly.Internal.Data.Stream.Eliminate
1515 (
16− -- * Running a Parser
17− parse
16+ -- * Running a 'Fold'
17+ fold
18+
19+ -- -- * Running a 'Parser'
20+ , parse
1821 , parseD
1922 , parseBreak
2023 , parseBreakD
2124
22− -- * Deconstruction
25+ -- * Stream Deconstruction
2326 , uncons
2427
2528 -- * Right Folds
29+ , foldrM
30+ , foldr
31+ , foldrMx
2632 , foldr1
2733
34+ -- * Left Folds
35+ , foldlM'
36+ , foldl'
37+ , foldlMx'
38+ , foldlx'
39+
2840 -- * Specific Fold Functions
41+ , drain
2942 , mapM_ -- Map and Fold
3043 , null
3144 , head
@@ -48,9 +61,16 @@ module Streamly.Internal.Data.Stream.Eliminate
4861 , the
4962
5063 -- * To containers
64+ , toList
5165 , toListRev
5266
5367 -- * Multi-Stream Folds
68+ -- ** Comparisons
69+ -- | These should probably be expressed using zipping operations.
70+ , eqBy
71+ , cmpBy
72+
73+ -- ** Substreams
5474 -- | These should probably be expressed using parsers.
5575 , isPrefixOf
5676 , isInfixOf
core/src/Streamly/Internal/Data/Stream/Exception.hs+6−9
@@ -9,20 +9,17 @@
99
1010module Streamly.Internal.Data.Stream.Exception
1111 (
12− -- * Resources
13− before
14− , afterIO
15− , afterUnsafe
16− , finallyIO
17− , finallyUnsafe
18− , gbracket_
12+ gbracket_
1913 , gbracket
14+ , before
15+ , afterUnsafe
16+ , afterIO
2017 , bracketUnsafe
2118 , bracketIO3
2219 , bracketIO
23−
24− -- * Exceptions
2520 , onException
21+ , finallyUnsafe
22+ , finallyIO
2623 , ghandle
2724 , handle
2825 )
core/src/Streamly/Internal/Data/Stream/Generate.hs+12
@@ -16,13 +16,20 @@ module Streamly.Internal.Data.Stream.Generate
1616 (
1717 -- * Primitives
1818 nil
19+ , nilM
1920 , cons
21+ , consM
22+
23+ -- * From 'Unfold'
24+ , unfold
2025
2126 -- * Unfolding
2227 , unfoldr
2328 , unfoldrM
2429
2530 -- * From Values
31+ , fromPure
32+ , fromEffect
2633 , repeat
2734 , repeatM
2835 , replicate
@@ -88,6 +95,7 @@ module Streamly.Internal.Data.Stream.Generate
8895 -- * From Containers
8996 -- | Transform an input structure into a stream.
9097
98+ , fromList
9199 , fromListM
92100 , fromFoldable
93101 , fromFoldableM
@@ -96,6 +104,10 @@ module Streamly.Internal.Data.Stream.Generate
96104 , fromPtr
97105 , fromPtrN
98106 , fromByteStr#
107+
108+ -- * Conversions
109+ , fromStreamK
110+ , toStreamK
99111 )
100112where
101113
core/src/Streamly/Internal/Data/Stream/Nesting.hs+64−10
@@ -55,6 +55,11 @@ module Streamly.Internal.Data.Stream.Nesting
5555 -- yields an element. A special case of unfoldRoundRobin.
5656 , roundRobin -- interleaveFair?/ParallelFair
5757
58+ -- *** Zipping
59+ -- | Zip corresponding elements of two streams.
60+ , zipWith
61+ , zipWithM
62+
5863 -- *** Merging
5964 -- | Interleave elements from two streams based on a condition.
6065 , mergeBy
@@ -71,10 +76,17 @@ module Streamly.Internal.Data.Stream.Nesting
7176 -- unfoldMany: Unfold m a b -> t m a -> t m b
7277 -- @
7378
79+ -- *** ConcatMap
80+ -- | Generate streams by mapping a stream generator on each element of an
81+ -- input stream, append the resulting streams and flatten.
82+ , concatMap
83+ , concatMapM
84+
7485 -- *** ConcatUnfold
7586 -- | Generate streams by using an unfold on each element of an input
7687 -- stream, append the resulting streams and flatten. A special case of
7788 -- gintercalate.
89+ , unfoldMany
7890 , ConcatUnfoldInterleaveState (..)
7991 , unfoldInterleave
8092 , unfoldRoundRobin
@@ -106,8 +118,11 @@ module Streamly.Internal.Data.Stream.Nesting
106118
107119 -- ** Folding
108120 -- | Apply folds on a stream.
121+ , foldMany
122+ , refoldMany
109123 , foldSequence
110124 , foldIterateM
125+ , refoldIterateM
111126
112127 -- ** Parsing
113128 -- | Parsing is opposite to flattening. 'parseMany' is dual to concatMap or
@@ -124,6 +139,7 @@ module Streamly.Internal.Data.Stream.Nesting
124139
125140 -- ** Grouping
126141 -- | Group segments of a stream and fold. Special case of parsing.
142+ , groupsOf
127143 , groupsBy
128144 , groupsWhile
129145 , groupsRollingBy
@@ -131,8 +147,8 @@ module Streamly.Internal.Data.Stream.Nesting
131147 -- ** Splitting
132148 -- | A special case of parsing.
133149 , wordsBy
134− , splitOnSeq -- XXX splitOnSeg
135− , splitOnSuffixSeq -- XXX splitOnSegSuffix, splitOnTrailer
150+ , splitOnSeq
151+ , splitOnSuffixSeq
136152
137153 -- XXX Implement these as folds or parsers instead.
138154 , splitOnSuffixSeqAny
@@ -141,8 +157,9 @@ module Streamly.Internal.Data.Stream.Nesting
141157
142158 -- * Transform (Nested Containers)
143159 -- | Opposite to compact in ArrayStream
144− , splitInnerBy -- XXX innerSplitOn
145− , splitInnerBySuffix -- XXX innerSplitOnSuffix
160+ , splitInnerBy
161+ , splitInnerBySuffix
162+ , intersectBySorted
146163
147164 -- * Reduce By Streams
148165 , dropPrefix
@@ -592,6 +609,49 @@ mergeFstBy :: -- Monad m =>
592609mergeFstBy _f _m1 _m2 = undefined
593610 -- fromStreamK $ D.mergeFstBy f (toStreamD m1) (toStreamD m2)
594611
612+-------------------------------------------------------------------------------
613+-- Intersection of sorted streams
614+-------------------------------------------------------------------------------
615+
616+-- Assuming the streams are sorted in ascending order
617+{-# INLINE_NORMAL intersectBySorted #-}
618+intersectBySorted :: Monad m
619+ => (a -> a -> Ordering) -> Stream m a -> Stream m a -> Stream m a
620+intersectBySorted cmp (Stream stepa ta) (Stream stepb tb) =
621+ Stream step
622+ ( ta -- left stream state
623+ , tb -- right stream state
624+ , Nothing -- left value
625+ , Nothing -- right value
626+ )
627+
628+ where
629+
630+ {-# INLINE_LATE step #-}
631+ -- step 1, fetch the first value
632+ step gst (sa, sb, Nothing, b) = do
633+ r <- stepa gst sa
634+ return $ case r of
635+ Yield a sa' -> Skip (sa', sb, Just a, b) -- step 2/3
636+ Skip sa' -> Skip (sa', sb, Nothing, b)
637+ Stop -> Stop
638+
639+ -- step 2, fetch the second value
640+ step gst (sa, sb, a@(Just _), Nothing) = do
641+ r <- stepb gst sb
642+ return $ case r of
643+ Yield b sb' -> Skip (sa, sb', a, Just b) -- step 3
644+ Skip sb' -> Skip (sa, sb', a, Nothing)
645+ Stop -> Stop
646+
647+ -- step 3, compare the two values
648+ step _ (sa, sb, Just a, Just b) = do
649+ let res = cmp a b
650+ return $ case res of
651+ GT -> Skip (sa, sb, Just a, Nothing) -- step 2
652+ LT -> Skip (sa, sb, Nothing, Just b) -- step 1
653+ EQ -> Yield a (sa, sb, Nothing, Just b) -- step 1
654+
595655------------------------------------------------------------------------------
596656-- Combine N Streams - unfoldMany
597657------------------------------------------------------------------------------
@@ -2986,8 +3046,6 @@ splitInnerBySuffix isEmpty splitter joiner (Stream step1 state1) =
29863046--
29873047-- Space: @O(1)@
29883048--
2989−-- See also stripPrefix.
2990−--
29913049-- /Unimplemented/
29923050{-# INLINE dropPrefix #-}
29933051dropPrefix ::
@@ -3000,8 +3058,6 @@ dropPrefix = error "Not implemented yet!"
30003058--
30013059-- Space: @O(n)@ where n is the length of the infix.
30023060--
3003−-- See also stripInfix.
3004−--
30053061-- /Unimplemented/
30063062{-# INLINE dropInfix #-}
30073063dropInfix ::
@@ -3014,8 +3070,6 @@ dropInfix = error "Not implemented yet!"
30143070--
30153071-- Space: @O(n)@ where n is the length of the suffix.
30163072--
3017−-- See also stripSuffix.
3018−--
30193073-- /Unimplemented/
30203074{-# INLINE dropSuffix #-}
30213075dropSuffix ::
core/src/Streamly/Internal/Data/Stream/Top.hs+115−243
@@ -8,73 +8,38 @@
88-- Portability : GHC
99--
1010-- Top level module that can depend on all other lower level Stream modules.
11−--
12−-- Design notes:
13−--
14−-- The order of arguments in the join operations should ideally be opposite. It
15−-- should be such that the infinite stream is the last one. The transformation
16−-- should be on the last argument, so if you curry the functions with all other
17−-- arguments we get a @Stream -> Stream@ function. The first stream argument
18−-- may be considered as a config or modifier for the operation.
19−--
20−-- Benefit of changing the order is that we get a more intuitive Stream ->
21−-- Stream transformation after currying all other arguments. The inner loop
22−-- streams become arguments for the transformation, more like local modifiers
23−-- for the global outer stream as the last argument. Thus we can continue using
24−-- transformations on the outer stream in a composed pipeline. Otherwise we can
25−-- use flip to flip the order.
26−--
27−-- The fact that the inner stream can be used in the loop multiple times also
28−-- tells that this is not the real effectful stream, it is more like a pure
29−-- stream or an array. In fact we may consider using an Identity streams as
30−-- inner streams in which case these functions will not look nice.
31−--
32−-- Downsides:
33−--
34−-- * Maybe less intuitive to think about, because we usually think the first
35−-- stream as the outer loop and second as the inner.
36−-- * Zip and merge operations will continue using the opposite order.
37−-- * Need to change the order of cross, crossWith operations as well
38−-- * It will be inconsistent with Data.List. The functions cannot be used as
39−-- intuitive operators.
40−--
41−-- The choice is similar to concatMap vs bind. concatMap is pipeline
42−-- composition friendly but bind is user intuition friendly. Another option is
43−-- to have other functions with a different argument order e.g. flippedCross
44−-- instead of cross.
45−--
46−-- If we change the order we have to make sure that we have a consistent
47−-- convention for set-like and the cross join operations.
4811
4912module Streamly.Internal.Data.Stream.Top
5013 (
51− -- * Sampling
14+ -- * Transformation
15+ -- ** Sampling
5216 -- | Value agnostic filtering.
5317 strideFromThen
5418
55− -- * Straight Joins
56− -- | These are set-like operations but not exactly set operations because
57− -- streams are not necessarily sets, they may have duplicated elements.
58− -- These operations are generic i.e. they work on streams of unconstrained
59− -- types, therefore, they have quadratic performance characterstics. For
60− -- better performance using Set or Map structures see the
61− -- Streamly.Internal.Data.Stream.Container module.
62− , intersectBy
63− , deleteFirstsBy
64− , unionBy
65−
66− -- Set like operations on sorted streams
67− , sortedIntersectBy
68− , sortedDeleteFirstsBy
69− , sortedUnionBy
70−
71− -- * Cross Joins
72− , innerJoin
73−
74− -- Joins on sorted stream
75− , innerSortedJoin
76− , leftSortedJoin
77− , outerSortedJoin
19+ -- * Nesting
20+ -- ** Set like operations
21+ -- | These are not exactly set operations because streams are not
22+ -- necessarily sets, they may have duplicated elements. These operations
23+ -- are generic i.e. they work on streams of unconstrained types, therefore,
24+ -- they have quadratic performance characterstics. For better performance
25+ -- using Set structures see the Streamly.Internal.Data.Stream.Container
26+ -- module.
27+ , filterInStreamGenericBy
28+ , deleteInStreamGenericBy
29+ , unionWithStreamGenericBy
30+
31+ -- ** Set like operations on sorted streams
32+ , filterInStreamAscBy
33+ , deleteInStreamAscBy
34+ , unionWithStreamAscBy
35+
36+ -- ** Join operations
37+ , joinInnerGeneric
38+
39+ -- * Joins on sorted stream
40+ , joinInnerAscBy
41+ , joinLeftAscBy
42+ , joinOuterAscBy
7843 )
7944where
8045
@@ -83,7 +48,7 @@ where
8348import Control.Monad.IO.Class (MonadIO(..))
8449import Data.IORef (newIORef, readIORef, modifyIORef')
8550import Streamly.Internal.Data.Fold.Type (Fold)
86−import Streamly.Internal.Data.Stream.Type (Stream(..), Step(..), cross)
51+import Streamly.Internal.Data.Stream.Type (Stream, cross)
8752
8853import qualified Data.List as List
8954import qualified Streamly.Internal.Data.Fold as Fold
@@ -145,40 +110,29 @@ strideFromThen offset stride =
145110-- binary search if we have an Ord instance or Ordering returning function. The
146111-- time complexity would then become (m x log n).
147112
148−-- | Like 'cross' but emits only those tuples where @a == b@ using the supplied
149−-- equality predicate. This is essentially a @cross intersection@ of two
150−-- streams.
113+-- | Like 'cross' but emits only those tuples where @a == b@ using the
114+-- supplied equality predicate.
151115--
152116-- Definition:
153117--
154−-- >>> innerJoin eq s1 s2 = Stream.filter (\(a, b) -> a `eq` b) $ Stream.cross s1 s2
155−--
156−-- The second (inner) stream must be finite. Moreover, it must be either pure
157−-- or capable of multiple evaluations. If not then the caller should cache it
158−-- in an 'Data.Array.Array', if the type does not have an 'Unbox' instance then
159−-- use the Generic 'Data.Array.Generic.Array'. Convert the array to stream
160−-- before calling this function. Caching may also improve performance if the
161−-- stream is expensive to evaluate.
118+-- >>> joinInnerGeneric eq s1 s2 = Stream.filter (\(a, b) -> a `eq` b) $ Stream.cross s1 s2
162119--
163−-- If you care about performance this function should be your last choice among
164−-- all inner joins. 'Streamly.Internal.Data.Unfold.innerJoin' is a much faster
165−-- fused alternative. 'innerSortedJoin' is a faster alternative when streams
166−-- are sorted. 'innerOrdJoin' is an order of magnitude faster alternative when
167−-- the type has an 'Ord' instance.
120+-- You should almost always prefer @joinInnerOrd@ over 'joinInnerGeneric' if
121+-- possible. @joinInnerOrd@ is an order of magnitude faster but may take more
122+-- space for caching the second stream.
168123--
169−-- Note: Conceptually, this is a commutative operation. Result includes all the
170−-- elements from the left and the right stream. The order of streams can be
171−-- changed without affecting results, except for the ordering within the tuple.
124+-- See 'Streamly.Internal.Data.Unfold.joinInnerGeneric' for a much faster fused
125+-- alternative.
172126--
173127-- Time: O(m x n)
174128--
175129-- /Pre-release/
176−{-# INLINE innerJoin #-}
177−innerJoin :: Monad m =>
130+{-# INLINE joinInnerGeneric #-}
131+joinInnerGeneric :: Monad m =>
178132 (a -> b -> Bool) -> Stream m a -> Stream m b -> Stream m (a, b)
179−innerJoin eq s1 s2 = Stream.filter (\(a, b) -> a `eq` b) $ cross s1 s2
133+joinInnerGeneric eq s1 s2 = Stream.filter (\(a, b) -> a `eq` b) $ cross s1 s2
180134{-
181−innerJoin eq s1 s2 = do
135+joinInnerGeneric eq s1 s2 = do
182136 -- ConcatMap works faster than bind
183137 Stream.concatMap (\a ->
184138 Stream.concatMap (\b ->
@@ -189,44 +143,44 @@ innerJoin eq s1 s2 = do
189143 ) s1
190144-}
191145
192−-- | A more efficient 'innerJoin' for sorted streams.
146+-- | A more efficient 'joinInner' for sorted streams.
193147--
194148-- Space: O(1)
195149--
196150-- Time: O(m + n)
197151--
198152-- /Unimplemented/
199−{-# INLINE innerSortedJoin #-}
200−innerSortedJoin ::
153+{-# INLINE joinInnerAscBy #-}
154+joinInnerAscBy ::
201155 (a -> b -> Ordering) -> Stream m a -> Stream m b -> Stream m (a, b)
202−innerSortedJoin = undefined
156+joinInnerAscBy = undefined
203157
204−-- | A more efficient 'leftJoin' for sorted streams.
158+-- | A more efficient 'joinLeft' for sorted streams.
205159--
206160-- Space: O(1)
207161--
208162-- Time: O(m + n)
209163--
210164-- /Unimplemented/
211−{-# INLINE leftSortedJoin #-}
212−leftSortedJoin :: -- Monad m =>
165+{-# INLINE joinLeftAscBy #-}
166+joinLeftAscBy :: -- Monad m =>
213167 (a -> b -> Ordering) -> Stream m a -> Stream m b -> Stream m (a, Maybe b)
214−leftSortedJoin _eq _s1 _s2 = undefined
168+joinLeftAscBy _eq _s1 _s2 = undefined
215169
216−-- | A more efficient 'outerJoin' for sorted streams.
170+-- | A more efficient 'joinOuter' for sorted streams.
217171--
218172-- Space: O(1)
219173--
220174-- Time: O(m + n)
221175--
222176-- /Unimplemented/
223−{-# INLINE outerSortedJoin #-}
224−outerSortedJoin :: -- Monad m =>
177+{-# INLINE joinOuterAscBy #-}
178+joinOuterAscBy :: -- Monad m =>
225179 (a -> b -> Ordering)
226180 -> Stream m a
227181 -> Stream m b
228182 -> Stream m (Maybe a, Maybe b)
229−outerSortedJoin _eq _s1 _s2 = undefined
183+joinOuterAscBy _eq _s1 _s2 = undefined
230184
231185------------------------------------------------------------------------------
232186-- Set operations (special joins)
@@ -236,8 +190,8 @@ outerSortedJoin _eq _s1 _s2 = undefined
236190-- the best would be to use an Array with linear search. If the second stream
237191-- is sorted we can also use a binary search, using Ord constraint.
238192
239−-- | Keep only those elements in the first stream that are present in the
240−-- second stream too. The second stream is folded to a container using the
193+-- | Keep only those elements in the second stream that are present in the
194+-- first stream too. The first stream is folded to a container using the
241195-- supplied fold and then the elements in the container are looked up using the
242196-- supplied lookup function.
243197--
@@ -252,30 +206,23 @@ filterStreamWith :: Monad m =>
252206filterStreamWith fld member s1 s2 =
253207 Stream.concatEffect
254208 $ do
255− xs <- Stream.fold fld s2
256− return $ Stream.filter (`member` xs) s1
257−
258−-- XXX instead of folding the second stream to a list we could use it directly.
259−-- If the user wants they can generate the stream from an array and also call
260−-- uniq or nub on it. We can provide a convenience Stream -> Stream to cache
261−-- a finite stream in an array and serve it from the cache. The user can decide
262−-- what is best based on the context. They can also choose to use a boxed or
263−-- unboxed array for caching. To force caching we can make the second stream
264−-- monad type Identity. But that may be less flexible. One option is to use
265−-- cachedIntersectBy etc for automatic caching.
209+ xs <- Stream.fold fld s1
210+ return $ Stream.filter (`member` xs) s2
266211
267−-- | 'intersectBy' returns a subsequence of the first stream which intersects
268−-- with the second stream. Note that this is not a commutative operation unlike
269−-- a set intersection, because of duplicate elements in the stream the order of
270−-- the streams matters. This is similar to 'Data.List.intersectBy'. Note that
271−-- intersectBy is a special case of 'innerJoin'.
212+-- | 'filterInStreamGenericBy' retains only those elements in the second stream that
213+-- are present in the first stream.
272214--
273−-- >>> f s1 s2 = Stream.fold Fold.toList $ Stream.intersectBy (==) (Stream.fromList s1) (Stream.fromList s2)
274−-- >>> f [1,3,4,4,5]) [2,3,4,5,5]
275−-- [3,4,4,5]
215+-- >>> Stream.fold Fold.toList $ Stream.filterInStreamGenericBy (==) (Stream.fromList [1,2,2,4]) (Stream.fromList [2,1,1,3])
216+-- [2,1,1]
276217--
277−-- First stream can be infinite, the second stream must be finite and must be
278−-- capable of multiple evaluations.
218+-- >>> Stream.fold Fold.toList $ Stream.filterInStreamGenericBy (==) (Stream.fromList [2,1,1,3]) (Stream.fromList [1,2,2,4])
219+-- [1,2,2]
220+--
221+-- Similar to the list intersectBy operation but with the stream argument order
222+-- flipped.
223+--
224+-- The first stream must be finite and must not block. Second stream is
225+-- processed only after the first stream is fully realized.
279226--
280227-- Space: O(n) where @n@ is the number of elements in the second stream.
281228--
@@ -283,87 +230,46 @@ filterStreamWith fld member s1 s2 =
283230-- @n@ is the number of elements in the second stream.
284231--
285232-- /Pre-release/
286−{-# INLINE intersectBy #-}
287−intersectBy :: Monad m =>
233+{-# INLINE filterInStreamGenericBy #-}
234+filterInStreamGenericBy :: Monad m =>
288235 (a -> a -> Bool) -> Stream m a -> Stream m a -> Stream m a
289−intersectBy eq =
236+filterInStreamGenericBy eq =
290237 -- XXX Use an (unboxed) array instead.
291238 filterStreamWith
292239 (Fold.scanMaybe (Fold.uniqBy eq) Fold.toListRev)
293240 (List.any . eq)
294241
295−-------------------------------------------------------------------------------
296−-- Intersection of sorted streams
297−-------------------------------------------------------------------------------
298−
299−-- XXX The sort order is not important as long both the streams have the same
300−-- sort order. We need to move only in one direction in each stream.
301−-- XXX Fix the argument order to use the same behavior as intersectBy.
302−
303−-- | Like 'intersectBy' but assumes that the input streams are sorted in
242+-- | Like 'filterInStreamGenericBy' but assumes that the input streams are sorted in
304243-- ascending order. To use it on streams sorted in descending order pass an
305244-- inverted comparison function returning GT for less than and LT for greater
306245-- than.
307246--
308−-- Both streams can be infinite.
309−--
310247-- Space: O(1)
311248--
312249-- Time: O(m+n)
313250--
314251-- /Pre-release/
315−{-# INLINE_NORMAL sortedIntersectBy #-}
316−sortedIntersectBy :: Monad m =>
252+{-# INLINE filterInStreamAscBy #-}
253+filterInStreamAscBy :: Monad m =>
317254 (a -> a -> Ordering) -> Stream m a -> Stream m a -> Stream m a
318−sortedIntersectBy cmp (Stream stepa ta) (Stream stepb tb) =
319− Stream step
320− ( ta -- left stream state
321− , tb -- right stream state
322− , Nothing -- left value
323− , Nothing -- right value
324− )
325−
326− where
255+filterInStreamAscBy eq s1 s2 = Stream.intersectBySorted eq s2 s1
327256
328− {-# INLINE_LATE step #-}
329− -- step 1, fetch the first value
330− step gst (sa, sb, Nothing, b) = do
331− r <- stepa gst sa
332− return $ case r of
333− Yield a sa' -> Skip (sa', sb, Just a, b) -- step 2/3
334− Skip sa' -> Skip (sa', sb, Nothing, b)
335− Stop -> Stop
336−
337− -- step 2, fetch the second value
338− step gst (sa, sb, a@(Just _), Nothing) = do
339− r <- stepb gst sb
340− return $ case r of
341− Yield b sb' -> Skip (sa, sb', a, Just b) -- step 3
342− Skip sb' -> Skip (sa, sb', a, Nothing)
343− Stop -> Stop
344−
345− -- step 3, compare the two values
346− step _ (sa, sb, Just a, Just b) = do
347− let res = cmp a b
348− return $ case res of
349− GT -> Skip (sa, sb, Just a, Nothing) -- step 2
350− LT -> Skip (sa, sb, Nothing, Just b) -- step 1
351− EQ -> Yield a (sa, sb, Nothing, Just b) -- step 1
352−
353−-- | Returns a subsequence of the first stream, deleting first occurrences of
354−-- those elements that are present in the second stream. Note that this is not
355−-- a commutative operation. This is similar to the 'Data.List.deleteFirstsBy'.
257+-- | Delete all elements of the first stream from the seconds stream. If an
258+-- element occurs multiple times in the first stream as many occurrences of it
259+-- are deleted from the second stream.
356260--
357−-- >>> f xs ys = Stream.fold Fold.toList $ Stream.deleteFirstsBy (==) (Stream.fromList xs) (Stream.fromList ys)
358−-- >>> f [1,2,2,3,3,5] [1,2,2,3,4]
359−-- [2,3,5]
261+-- >>> Stream.fold Fold.toList $ Stream.deleteInStreamGenericBy (==) (Stream.fromList [1,2,3]) (Stream.fromList [1,2,2])
262+-- [2]
360263--
361−-- The following holds:
264+-- The following laws hold:
362265--
363−-- > deleteFirstsBy (==) (Stream.nub s2 `append` s1) s2 === s1
364−-- > deleteFirstsBy (==) (Stream.nub s2 `interleave` s1) s2 === s1
266+-- > deleteInStreamGenericBy (==) s1 (s1 `append` s2) === s2
267+-- > deleteInStreamGenericBy (==) s1 (s1 `interleave` s2) === s2
365268--
366−-- First stream can be infinite, second stream must be finite.
269+-- Same as the list 'Data.List.//' operation but with argument order flipped.
270+--
271+-- The first stream must be finite and must not block. Second stream is
272+-- processed only after the first stream is fully realized.
367273--
368274-- Space: O(m) where @m@ is the number of elements in the first stream.
369275--
@@ -371,83 +277,58 @@ sortedIntersectBy cmp (Stream stepa ta) (Stream stepb tb) =
371277-- @n@ is the number of elements in the second stream.
372278--
373279-- /Pre-release/
374−{-# INLINE deleteFirstsBy #-}
375−deleteFirstsBy :: Monad m =>
280+{-# INLINE deleteInStreamGenericBy #-}
281+deleteInStreamGenericBy :: Monad m =>
376282 (a -> a -> Bool) -> Stream m a -> Stream m a -> Stream m a
377−deleteFirstsBy eq s2 s1 =
378− -- XXX s2 can be a sorted mutable array and we can use binary
379− -- search to find. Mark the element deleted, count the deletions
380− -- and reconsolidate the array when a min number of elements is
381− -- deleted.
382−
383− -- XXX Use StreamK or list as second argument instead of Stream to avoid
384− -- concatEffect?
385− Stream.concatEffect $ do
386− xs <- Stream.toList s1
387− -- It reverses the list but that is fine.
388− let del x =
389− List.foldl' (\(ys,res) y ->
390− if x `eq` y
391− then (ys, True)
392− else (x:ys, res)) ([], False)
393− g (ys,_) x =
394− let (ys1, deleted) = del x ys
395− in if deleted
396− then (ys1, Nothing)
397− else (ys1, Just x)
398− in return
399− $ Stream.catMaybes
400− $ fmap snd
401− $ Stream.postscanl' g (xs, Nothing) s2
283+deleteInStreamGenericBy eq s1 s2 =
284+ Stream.concatEffect
285+ $ do
286+ -- This may work well if s1 is small
287+ -- If s1 is big we can go through s1, deleting elements from s2 and
288+ -- not emitting an element if it was successfully deleted from s2.
289+ -- we will need a deleteBy that can return whether the element was
290+ -- deleted or not.
291+ xs <- Stream.fold Fold.toList s2
292+ let f = Fold.foldl' (flip (List.deleteBy eq)) xs
293+ fmap Stream.fromList $ Stream.fold f s1
402294
403−-- | A more efficient 'deleteFirstsBy' for streams sorted in ascending order.
404−--
405−-- Both streams can be infinite.
295+-- | A more efficient 'deleteInStreamGenericBy' for streams sorted in ascending order.
406296--
407297-- Space: O(1)
408298--
409299-- /Unimplemented/
410−{-# INLINE sortedDeleteFirstsBy #-}
411−sortedDeleteFirstsBy :: -- (Monad m) =>
300+{-# INLINE deleteInStreamAscBy #-}
301+deleteInStreamAscBy :: -- (Monad m) =>
412302 (a -> a -> Ordering) -> Stream m a -> Stream m a -> Stream m a
413−sortedDeleteFirstsBy _eq _s1 _s2 = undefined
303+deleteInStreamAscBy _eq _s1 _s2 = undefined
414304
415305-- XXX Remove the MonadIO constraint. We can just cache one stream and then
416306-- implement using differenceEqBy.
417307
418−-- | Returns the first stream appended with those unique elements from the
419−-- second stream that are not already present in the first stream. Note that
420−-- this is not a commutative operation unlike a set union, argument order
421−-- matters. The behavior is similar to the 'Data.List.unionBy'.
308+-- | This essentially appends to the second stream all the occurrences of
309+-- elements in the first stream that are not already present in the second
310+-- stream.
422311--
423312-- Equivalent to the following except that @s2@ is evaluated only once:
424313--
425−-- >>> unionBy eq s1 s2 = s1 `Stream.append` Stream.deleteFirstsBy eq s1 (Stream.nub s2)
314+-- >>> unionWithStreamGenericBy eq s1 s2 = s2 `Stream.append` (Stream.deleteInStreamGenericBy eq s2 s1)
426315--
427316-- Example:
428317--
429−-- >>> f s1 s2 = Stream.fold Fold.toList $ Stream.unionBy (==) (Stream.fromList s1) (Stream.fromList s2)
430−-- >>> f [1,2,2,4] [1,1,2,3,3]
318+-- >>> Stream.fold Fold.toList $ Stream.unionWithStreamGenericBy (==) (Stream.fromList [1,1,2,3]) (Stream.fromList [1,2,2,4])
431319-- [1,2,2,4,3]
432320--
433−-- First stream can be infinite, but second stream must be finite. Note that if
434−-- the first stream is infinite the union means just the first stream. Thus
435−-- union is useful only when both streams are finite. See 'sortedUnionBy' where
436−-- union can work on infinite streams if they are sorted.
437−--
438321-- Space: O(n)
439322--
440323-- Time: O(m x n)
441324--
442325-- /Pre-release/
443−{-# INLINE unionBy #-}
444−unionBy :: MonadIO m =>
326+{-# INLINE unionWithStreamGenericBy #-}
327+unionWithStreamGenericBy :: MonadIO m =>
445328 (a -> a -> Bool) -> Stream m a -> Stream m a -> Stream m a
446−unionBy eq s2 s1 =
329+unionWithStreamGenericBy eq s1 s2 =
447330 Stream.concatEffect
448331 $ do
449− -- XXX use a rewrite rule such that if a list converted to stream
450− -- is passed to unionBy then this becomes an identity operation.
451332 xs <- Stream.fold Fold.toList s1
452333 -- XXX we can use postscanlMAfter' instead of IORef
453334 ref <- liftIO $ newIORef $! List.nubBy eq xs
@@ -460,21 +341,12 @@ unionBy eq s2 s1 =
460341 return $ Stream.fromList xs1
461342 return $ Stream.mapM f s2 `Stream.append` s3
462343
463−-- | A more efficient 'unionBy' for sorted streams.
464−--
465−-- Note that the behavior is different from 'unionBy'. In 'unionBy' we append
466−-- the unique elements from second stream only after exhausting the first one
467−-- whereas in sorted streams we can determine unique elements early even when
468−-- we are going through the first stream. Thus the result is an interleaving of
469−-- the two streams, merging those elements from the second stream that are not
470−-- present in the first.
344+-- | A more efficient 'unionWithStreamGenericBy' for sorted streams.
471345--
472346-- Space: O(1)
473347--
474−-- Both streams can be infinite.
475−--
476348-- /Unimplemented/
477−{-# INLINE sortedUnionBy #-}
478−sortedUnionBy :: -- (Monad m) =>
349+{-# INLINE unionWithStreamAscBy #-}
350+unionWithStreamAscBy :: -- (Monad m) =>
479351 (a -> a -> Ordering) -> Stream m a -> Stream m a -> Stream m a
480−sortedUnionBy _eq _s1 _s2 = undefined
352+unionWithStreamAscBy _eq _s1 _s2 = undefined
core/src/Streamly/Internal/Data/Stream/Transform.hs+26−1
@@ -21,6 +21,8 @@ module Streamly.Internal.Data.Stream.Transform
2121
2222 -- * Mapping
2323 -- | Stateless one-to-one maps.
24+ , map
25+ , mapM
2426 , sequence
2527
2628 -- * Mapping Effects
@@ -73,7 +75,7 @@ module Streamly.Internal.Data.Stream.Transform
7375 , scanMaybe
7476 , filter
7577 , filterM
76− , deleteBy -- deleteOnceBy?
78+ , deleteBy
7779 , uniqBy
7880 , uniq
7981 , prune
@@ -83,6 +85,9 @@ module Streamly.Internal.Data.Stream.Transform
8385 -- | Produce a subset of the stream trimmed at ends.
8486 , initNonEmpty
8587 , tailNonEmpty
88+ , take
89+ , takeWhile
90+ , takeWhileM
8691 , takeWhileLast
8792 , takeWhileAround
8893 , drop
@@ -129,6 +134,7 @@ module Streamly.Internal.Data.Stream.Transform
129134 -- * Searching
130135 , findIndices
131136 , elemIndices
137+ , slicesBy
132138
133139 -- * Rolling map
134140 -- | Map using the previous element.
@@ -1754,6 +1760,25 @@ findIndices p (Stream step state) = Stream step' (state, 0)
17541760elemIndices :: (Monad m, Eq a) => a -> Stream m a -> Stream m Int
17551761elemIndices a = findIndices (== a)
17561762
1763+{-# INLINE_NORMAL slicesBy #-}
1764+slicesBy :: Monad m => (a -> Bool) -> Stream m a -> Stream m (Int, Int)
1765+slicesBy p (Stream step1 state1) = Stream step (Just (state1, 0, 0))
1766+
1767+ where
1768+
1769+ {-# INLINE_LATE step #-}
1770+ step gst (Just (st, i, len)) = i `seq` len `seq` do
1771+ r <- step1 (adaptState gst) st
1772+ return
1773+ $ case r of
1774+ Yield x s ->
1775+ if p x
1776+ then Yield (i, len + 1) (Just (s, i + len + 1, 0))
1777+ else Skip (Just (s, i, len + 1))
1778+ Skip s -> Skip (Just (s, i, len))
1779+ Stop -> if len == 0 then Stop else Yield (i, len) Nothing
1780+ step _ Nothing = return Stop
1781+
17571782------------------------------------------------------------------------------
17581783-- Rolling map
17591784------------------------------------------------------------------------------
core/src/Streamly/Internal/Data/Stream/Transformer.hs+1−2
@@ -11,11 +11,10 @@
1111
1212module Streamly.Internal.Data.Stream.Transformer
1313 (
14− -- * Fold to Transformer Monad
1514 foldlT
1615 , foldrT
1716
18− -- * Inner Monad Operations
17+ -- * Transform Inner Monad
1918 , liftInner
2019 , runReaderT
2120 , usingReaderT
core/src/Streamly/Internal/Data/Stream/Type.hs+3−31
@@ -22,18 +22,18 @@
2222
2323module Streamly.Internal.Data.Stream.Type
2424 (
25− -- * Type
25+ -- * The stream type
2626 Step (..)
2727 -- XXX UnStream is exported to avoid a performance issue in some
2828 -- combinators if we use the pattern synonym "Stream".
2929 , Stream (Stream, UnStream)
3030
31− -- * CrossStream
31+ -- * CrossStream type wrapper
3232 , CrossStream
3333 , unCross
3434 , mkCross
3535
36− -- * To StreamK
36+ -- * Conversion to StreamK
3737 , fromStreamK
3838 , toStreamK
3939
@@ -91,7 +91,6 @@ module Streamly.Internal.Data.Stream.Type
9191
9292 -- * Combining Two Streams
9393 -- ** Zipping
94− -- | Zip corresponding elements of two streams.
9594 , zipWithM
9695 , zipWith
9796
@@ -107,8 +106,6 @@ module Streamly.Internal.Data.Stream.Type
107106 , unfoldMany
108107
109108 -- * Concat
110− -- | Generate streams by mapping a stream generator on each element of an
111− -- input stream, append the resulting streams and flatten.
112109 , concatEffect
113110 , concatMap
114111 , concatMapM
@@ -142,7 +139,6 @@ module Streamly.Internal.Data.Stream.Type
142139 , indexOnSuffix
143140
144141 -- * Multi-stream folds
145− -- | These should probably be expressed using zipping operations.
146142 , eqBy
147143 , cmpBy
148144
@@ -572,12 +568,8 @@ foldAdd f =
572568-- >>> Stream.foldrM step (return False) s
573569-- True
574570--
575−-- >>> import Control.Monad (join)
576−-- >>> foldrM f z = join $ Stream.foldr f z
577−--
578571{-# INLINE_NORMAL foldrM #-}
579572foldrM :: Monad m => (a -> m b -> m b) -> m b -> Stream m a -> m b
580−-- foldrM f z = join . Streamly.Internal.Data.Stream.StreamD.Type.foldr f z
581573foldrM f z (Stream step state) = go SPEC state
582574 where
583575 {-# INLINE_LATE go #-}
@@ -2076,26 +2068,6 @@ indexOnSuffix predicate =
20762068 (indexerBy (FL.takeEndBy_ predicate FL.length) 1)
20772069 (return (-1, 0))
20782070
2079−-- Alternate implementation
2080−{-# INLINE_NORMAL _indexOnSuffix #-}
2081−_indexOnSuffix :: Monad m => (a -> Bool) -> Stream m a -> Stream m (Int, Int)
2082−_indexOnSuffix p (Stream step1 state1) = Stream step (Just (state1, 0, 0))
2083−
2084− where
2085−
2086− {-# INLINE_LATE step #-}
2087− step gst (Just (st, i, len)) = i `seq` len `seq` do
2088− r <- step1 (adaptState gst) st
2089− return
2090− $ case r of
2091− Yield x s ->
2092− if p x
2093− then Yield (i, len + 1) (Just (s, i + len + 1, 0))
2094− else Skip (Just (s, i, len + 1))
2095− Skip s -> Skip (Just (s, i, len))
2096− Stop -> if len == 0 then Stop else Yield (i, len) Nothing
2097− step _ Nothing = return Stop
2098−
20992071{-# DEPRECATED sliceOnSuffix "Please use indexOnSuffix instead." #-}
21002072sliceOnSuffix :: Monad m => (a -> Bool) -> Stream m a -> Stream m (Int, Int)
21012073sliceOnSuffix = indexOnSuffix
core/src/Streamly/Internal/Data/Unfold.hs+4−6
@@ -82,7 +82,7 @@ module Streamly.Internal.Data.Unfold
8282 , dropWhileM
8383
8484 -- ** Cross product
85− , innerJoin
85+ , joinInnerGeneric
8686
8787 -- ** Resource Management
8888 -- | 'bracket' is the most general resource management operation, all other
@@ -677,12 +677,10 @@ dropWhileM f (Unfold step inject) = Unfold step' inject'
677677dropWhile :: Monad m => (b -> Bool) -> Unfold m a b -> Unfold m a b
678678dropWhile f = dropWhileM (return . f)
679679
680−-- | Cross intersection of two unfolds. See
681−-- 'Streamly.Internal.Data.Stream.innerJoin' for more details.
682−{-# INLINE_NORMAL innerJoin #-}
683−innerJoin :: Monad m =>
680+{-# INLINE_NORMAL joinInnerGeneric #-}
681+joinInnerGeneric :: Monad m =>
684682 (b -> c -> Bool) -> Unfold m a b -> Unfold m a c -> Unfold m a (b, c)
685−innerJoin eq s1 s2 = filter (\(a, b) -> a `eq` b) $ cross s1 s2
683+joinInnerGeneric eq s1 s2 = filter (\(a, b) -> a `eq` b) $ cross s1 s2
686684
687685------------------------------------------------------------------------------
688686-- Exceptions
core/src/Streamly/Internal/Unicode/Stream.hs+1−1
@@ -1057,7 +1057,7 @@ stripHead = Stream.dropWhile isSpace
10571057-- >>> Stream.fold Fold.toList $ Unicode.lines Fold.toList (Stream.fromList "lines\nthis\nstring\n\n\n")
10581058-- ["lines","this","string","",""]
10591059--
1060−-- >>> lines f = Stream.foldMany (Fold.takeEndBy_ (== '\n') f)
1060+-- > lines = Stream.splitOnSuffix (== '\n')
10611061--
10621062-- /Pre-release/
10631063{-# INLINE lines #-}
docs/Developer/optimization-guidelines.md−12
@@ -191,18 +191,6 @@ the stages of a loop, it is often not useful to inline the whole loop
191191itself, in fact we may have to occasionally use a `NOINLINE` so that the
192192compiler does not inline it.
193193
194−We need to be careful about NOINLINE on polymorphic functions. When
195−NOINLINE is used, the function cannot be specialized causing huge
196−performance degradation. To avoid that we can either make the
197−function monomorphic or sometimes using `NOINLINE [0]` can do the
198−trick. `NOINLINE [0]` blocks inlining in earlier phases but lets it
199−specialize in the last phase, inlining in the last phase is left to
200−the compiler. `INLINE [0]` may sound the same as `NOINLINE [0]`,
201−however, it behaves differently because we ask the compiler to INLINE
202−it compulsorily and it may not give us the desired results. The
203−Data.Fold.writeLastN benchmark is one such case where `NOINLINE [0]`
204−provides much better performance than `INLINE [0]`.
205−
206194### NoSpecConstr
207195
208196It is not always useful to specialize function calls for all constructors, some
src/Streamly/Internal/Data/Stream/IsStream/Top.hs+1−1
@@ -649,7 +649,7 @@ intersectBySorted :: (IsStream t, Monad m) =>
649649 (a -> a -> Ordering) -> t m a -> t m a -> t m a
650650intersectBySorted eq s1 =
651651 IsStream.fromStreamD
652− . StreamD.sortedIntersectBy eq (IsStream.toStreamD s1)
652+ . StreamD.intersectBySorted eq (IsStream.toStreamD s1)
653653 . IsStream.toStreamD
654654
655655-- Roughly joinLeft s1 s2 = s1 `difference` s2 + s1 `intersection` s2