on da36a01 help sign in
663 lines 23 kB history raw
1-- |
2-- Module : Streamly.Internal.Data.Stream.Expand
3-- Copyright : (c) 2017 Composewell Technologies
4-- License : BSD-3-Clause
5-- Maintainer : streamly@composewell.com
6-- Stability : experimental
7-- Portability : GHC
8--
9-- Expand a stream by combining two or more streams or by combining streams
10-- with unfolds.
12module Streamly.Internal.Data.Stream.Expand
13 (
14 -- * Binary Combinators (Linear)
15 -- | Functions ending in the shape:
16 --
17 -- @Stream m a -> Stream m a -> Stream m a@.
18 --
19 -- The functions in this section have a linear or flat n-ary combining
20 -- characterstics. It means that when combined @n@ times (e.g. @a `serial`
21 -- b `serial` c ...@) the resulting expression will have an @O(n)@
22 -- complexity (instead O(n^2) for pair wise combinators described in the
23 -- next section. These functions can be used efficiently with
24 -- 'concatMapWith' et. al. combinators that combine streams in a linear
25 -- fashion (contrast with 'concatPairsWith' which combines streams as a
26 -- binary tree).
28 append
29 -- * Binary Combinators (Pair Wise)
30 -- | Like the functions in the section above these functions also combine
31 -- two streams into a single stream but when used @n@ times linearly they
32 -- exhibit O(n^2) complexity. They are best combined in a binary tree
33 -- fashion using 'concatPairsWith' giving a @n * log n@ complexity. Avoid
34 -- using these with 'concatMapWith' when combining a large or infinite
35 -- number of streams.
37 -- ** Append
38 , append2
40 -- ** Interleave
41 , interleave
42 , interleaveFst
43 , interleaveMin
45 -- ** Merge
46 , mergeBy
47 , mergeByM
48 , mergeByM2
49 , mergeMinBy
50 , mergeFstBy
52 -- ** Zip
53 , zipWith
54 , zipWithM
56 -- * Combine Streams and Unfolds
57 -- |
58 -- Expand a stream by repeatedly using an unfold and merging the resulting
59 -- streams. Functions generally ending in the shape:
60 --
61 -- @Unfold m a b -> Stream m a -> Stream m b@
63 -- ** Unfold and combine streams
64 -- | Unfold and flatten streams.
65 , unfoldMany -- XXX Rename to unfoldAppend
66 , unfoldInterleave
67 , unfoldRoundRobin
69 -- ** Interpose
70 -- | Insert effects between streams. Like unfoldMany but intersperses an
71 -- effect between the streams. A special case of gintercalate.
72 , interpose
73 , interposeSuffix
74 -- , interposeBy
76 -- ** Intercalate
77 -- | Insert Streams between Streams.
78 -- Like unfoldMany but intersperses streams from another source between
79 -- the streams from the first source.
80 , intercalate
81 , intercalateSuffix
82 , gintercalate
83 , gintercalateSuffix
85 -- * Combine Streams of Streams
86 -- | Map and serially append streams. 'concatMapM' is a generalization of
87 -- the binary append operation to append many streams.
88 , concatMapM
89 , concatMap
90 , concatM
91 , concat
93 -- * ConcatMapWith
94 -- | Map and flatten a stream like 'concatMap' but using a custom binary
95 -- stream merging combinator instead of just appending the streams. The
96 -- merging occurs sequentially, it works efficiently for 'serial', 'async',
97 -- 'ahead' like merge operations where we consume one stream before the
98 -- next or in case of 'wAsync' or 'parallel' where we consume all streams
99 -- simultaneously anyway.
100 --
101 -- However, in cases where the merging consumes streams in a round robin
102 -- fashion, a pair wise merging using 'concatPairsWith' would be more
103 -- efficient. These cases include operations like 'mergeBy' or 'zipWith'.
105 , concatMapWith
106 , bindWith
107 , concatSmapMWith
109 -- * ConcatPairsWith
110 -- | See the notes about suitable merge functions in the 'concatMapWith'
111 -- section.
112 , concatPairsWith
114 -- * IterateMap
115 -- | Map and flatten Trees of Streams
116 , iterateMapWith
117 , iterateSmapMWith
118 , iterateMapLeftsWith
119 , iterateUnfold
120 )
121where
123#include "inline.hs"
125import Streamly.Internal.Data.Stream.Bottom
126 ( concatM, concatMapM, concatMap, smapM, fromPure, fromEffect
127 , zipWith, zipWithM)
128import Streamly.Internal.Data.Stream.Type
129 ( Stream, fromStreamD, fromStreamK, toStreamD, toStreamK
130 , bindWith, concatMapWith, nil)
131import Streamly.Internal.Data.Unfold.Type (Unfold)
133import qualified Streamly.Internal.Data.Stream.StreamD as D
134import qualified Streamly.Internal.Data.Stream.StreamK as K (mergeBy, mergeByM)
135import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
137import Prelude hiding (concat, concatMap, zipWith)
139-- $setup
140-- >>> :m
141-- >>> import Data.Either (either)
142-- >>> import Data.IORef
143-- >>> import Prelude hiding (zipWith, concatMap, concat)
144-- >>> import qualified Streamly.Data.Array.Unboxed as Array
145-- >>> import qualified Streamly.Internal.Data.Fold as Fold
146-- >>> import qualified Streamly.Internal.Data.Stream as Stream
147-- >>> import qualified Streamly.Internal.Data.Unfold as Unfold
148-- >>> import qualified Streamly.Internal.Data.Parser as Parser
149-- >>> import qualified Streamly.Internal.FileSystem.Dir as Dir
150--
152------------------------------------------------------------------------------
153-- Appending
154------------------------------------------------------------------------------
156infixr 6 `append2`
158-- | This is fused version of 'append'. It could be up to 100x faster than
159-- 'append' when combining two fusible streams. However, it slows down
160-- quadratically with the number of streams being appended. Therefore, it is
161-- suitable for ad-hoc append of a few streams, and should not be used with
162-- 'concatMapWith' or 'concatPairsWith'.
163--
164-- /Fused/
165--
166-- @since 0.9.0
167{-# INLINE append2 #-}
168append2 ::Monad m => Stream m b -> Stream m b -> Stream m b
169append2 m1 m2 = fromStreamD $ D.append (toStreamD m1) (toStreamD m2)
171infixr 6 `append`
173-- | Appends two streams sequentially, yielding all elements from the first
174-- stream, and then all elements from the second stream.
175--
176-- >>> s1 = Stream.fromList [1,2]
177-- >>> s2 = Stream.fromList [3,4]
178-- >>> Stream.fold Fold.toList $ s1 `Stream.append` s2
179-- [1,2,3,4]
180--
181-- This has O(n) append performance where @n@ is the number of streams. It can
182-- be used to efficiently fold an infinite lazy container of streams
183-- 'concatMapWith' et. al.
184--
185-- /Not fused/
186--
187-- @since 0.9.0
188{-# INLINE append #-}
189append :: Stream m a -> Stream m a -> Stream m a
190append = (<>)
192------------------------------------------------------------------------------
193-- Interleaving
194------------------------------------------------------------------------------
196-- | Interleaves two streams, yielding one element from each stream
197-- alternately. When one stream stops the rest of the other stream is used in
198-- the output stream.
199--
200-- When joining many streams in a left associative manner earlier streams will
201-- get exponential priority than the ones joining later. Because of exponential
202-- weighting it can be used with 'concatMapWith' even on a large number of
203-- streams.
204--
205-- /Not fused/
206--
207-- @since 0.9.0
208{-# INLINE interleave #-}
209interleave :: Stream m a -> Stream m a -> Stream m a
210interleave s1 s2 = fromStreamK $ K.interleave (toStreamK s1) (toStreamK s2)
212-- | Like `interleave` but stops interleaving as soon as the first stream
213-- stops.
214--
215-- /Not fused/
216{-# INLINE interleaveFst #-}
217interleaveFst :: Stream m a -> Stream m a -> Stream m a
218interleaveFst s1 s2 =
219 fromStreamK $ K.interleaveFst (toStreamK s1) (toStreamK s2)
221-- | Like `interleave` but stops interleaving as soon as any of the two streams
222-- stops.
223--
224-- /Not fused/
225{-# INLINE interleaveMin #-}
226interleaveMin :: Stream m a -> Stream m a -> Stream m a
227interleaveMin s1 s2 =
228 fromStreamK $ K.interleaveMin (toStreamK s1) (toStreamK s2)
230------------------------------------------------------------------------------
231-- Merging (sorted streams)
232------------------------------------------------------------------------------
234-- | Merge two streams using a comparison function. The head elements of both
235-- the streams are compared and the smaller of the two elements is emitted, if
236-- both elements are equal then the element from the first stream is used
237-- first.
238--
239-- If the streams are sorted in ascending order, the resulting stream would
240-- also remain sorted in ascending order.
241--
242-- >>> s1 = Stream.fromList [1,3,5]
243-- >>> s2 = Stream.fromList [2,4,6,8]
244-- >>> Stream.fold Fold.toList $ Stream.mergeBy compare s1 s2
245-- [1,2,3,4,5,6,8]
246--
247-- See also: 'mergeByM2'
248--
249-- @since 0.9.0
250{-# INLINE mergeBy #-}
251mergeBy :: (a -> a -> Ordering) -> Stream m a -> Stream m a -> Stream m a
252mergeBy f m1 m2 = fromStreamK $ K.mergeBy f (toStreamK m1) (toStreamK m2)
254-- | Like 'mergeBy' but with a monadic comparison function.
255--
256-- Merge two streams randomly:
257--
258-- @
259-- > randomly _ _ = randomIO >>= \x -> return $ if x then LT else GT
260-- > Stream.toList $ Stream.mergeByM randomly (Stream.fromList [1,1,1,1]) (Stream.fromList [2,2,2,2])
261-- [2,1,2,2,2,1,1,1]
262-- @
263--
264-- Merge two streams in a proportion of 2:1:
265--
266-- >>> :{
267-- do
268-- let s1 = Stream.fromList [1,1,1,1,1,1]
269-- s2 = Stream.fromList [2,2,2]
270-- let proportionately m n = do
271-- ref <- newIORef $ cycle $ Prelude.concat [Prelude.replicate m LT, Prelude.replicate n GT]
272-- return $ \_ _ -> do
273-- r <- readIORef ref
274-- writeIORef ref $ Prelude.tail r
275-- return $ Prelude.head r
276-- f <- proportionately 2 1
277-- xs <- Stream.fold Fold.toList $ Stream.mergeByM f s1 s2
278-- print xs
279-- :}
280-- [1,1,2,1,1,2,1,1,2]
281--
282-- See also: 'mergeByM2'
283--
284-- @since 0.9.0
285{-# INLINE mergeByM #-}
286mergeByM
287 :: Monad m
288 => (a -> a -> m Ordering) -> Stream m a -> Stream m a -> Stream m a
289mergeByM f m1 m2 = fromStreamK $ K.mergeByM f (toStreamK m1) (toStreamK m2)
291-- | Like 'mergeByM' but much faster, works best when merging statically known
292-- number of streams. When merging more than two streams try to merge pairs and
293-- pair of pairs in a tree like structure.'mergeByM' works better with variable
294-- number of streams being merged using 'concatPairsWith'.
295--
296-- /Internal/
297{-# INLINE mergeByM2 #-}
298mergeByM2
299 :: Monad m
300 => (a -> a -> m Ordering) -> Stream m a -> Stream m a -> Stream m a
301mergeByM2 f m1 m2 =
302 fromStreamD $ D.mergeByM f (toStreamD m1) (toStreamD m2)
304-- | Like 'mergeByM' but stops merging as soon as any of the two streams stops.
305--
306-- /Unimplemented/
307{-# INLINABLE mergeMinBy #-}
308mergeMinBy :: -- Monad m =>
309 (a -> a -> m Ordering) -> Stream m a -> Stream m a -> Stream m a
310mergeMinBy _f _m1 _m2 = undefined
311 -- fromStreamD $ D.mergeMinBy f (toStreamD m1) (toStreamD m2)
313-- | Like 'mergeByM' but stops merging as soon as the first stream stops.
314--
315-- /Unimplemented/
316{-# INLINABLE mergeFstBy #-}
317mergeFstBy :: -- Monad m =>
318 (a -> a -> m Ordering) -> Stream m a -> Stream m a -> Stream m a
319mergeFstBy _f _m1 _m2 = undefined
320 -- fromStreamK $ D.mergeFstBy f (toStreamD m1) (toStreamD m2)
322------------------------------------------------------------------------------
323-- Combine N Streams - unfoldMany
324------------------------------------------------------------------------------
326-- | Like 'concatMap' but uses an 'Unfold' for stream generation. Unlike
327-- 'concatMap' this can fuse the 'Unfold' code with the inner loop and
328-- therefore provide many times better performance.
329--
330-- @since 0.9.0
331{-# INLINE unfoldMany #-}
332unfoldMany ::Monad m => Unfold m a b -> Stream m a -> Stream m b
333unfoldMany u m = fromStreamD $ D.unfoldMany u (toStreamD m)
335-- | This does not pair streams like concatPairsWith, instead, it goes through
336-- each stream one by one and yields one element from each stream. After it
337-- goes to the last stream it reverses the traversal to come back to the first
338-- stream yielding elements from each stream on its way back to the first
339-- stream and so on.
340--
341-- >>> lists = Stream.fromList [[1,1],[2,2],[3,3],[4,4],[5,5]]
342-- >>> interleaved = Stream.unfoldInterleave Unfold.fromList lists
343-- >>> Stream.fold Fold.toList interleaved
344-- [1,2,3,4,5,5,4,3,2,1]
345--
346-- Note that this is order of magnitude more efficient than "concatPairsWith
347-- wSerial" because of fusion.
348--
349-- /Fused/
350{-# INLINE unfoldInterleave #-}
351unfoldInterleave ::Monad m => Unfold m a b -> Stream m a -> Stream m b
352unfoldInterleave u m =
353 fromStreamD $ D.unfoldManyInterleave u (toStreamD m)
355-- | 'unfoldInterleave' switches to the next stream whenever a value from a
356-- stream is yielded, it does not switch on a 'Skip'. So if a stream keeps
357-- skipping for long time other streams won't get a chance to run.
358-- 'unfoldRoundRobin' switches on Skip as well. So it basically schedules each
359-- stream fairly irrespective of whether it produces a value or not.
360--
361{-# INLINE unfoldRoundRobin #-}
362unfoldRoundRobin ::Monad m => Unfold m a b -> Stream m a -> Stream m b
363unfoldRoundRobin u m =
364 fromStreamD $ D.unfoldManyInterleave u (toStreamD m)
366------------------------------------------------------------------------------
367-- Combine N Streams - interpose
368------------------------------------------------------------------------------
370-- > interpose x unf str = gintercalate unf str UF.identity (repeat x)
372-- | Unfold the elements of a stream, intersperse the given element between the
373-- unfolded streams and then concat them into a single stream.
374--
375-- >>> unwords = Stream.interpose ' '
376--
377-- /Pre-release/
378{-# INLINE interpose #-}
379interpose :: Monad m
380 => c -> Unfold m b c -> Stream m b -> Stream m c
381interpose x unf str =
382 fromStreamD $ D.interpose (return x) unf (toStreamD str)
384-- interposeSuffix x unf str = gintercalateSuffix unf str UF.identity (repeat x)
386-- | Unfold the elements of a stream, append the given element after each
387-- unfolded stream and then concat them into a single stream.
388--
389-- >>> unlines = Stream.interposeSuffix '\n'
390--
391-- /Pre-release/
392{-# INLINE interposeSuffix #-}
393interposeSuffix :: Monad m
394 => c -> Unfold m b c -> Stream m b -> Stream m c
395interposeSuffix x unf str =
396 fromStreamD $ D.interposeSuffix (return x) unf (toStreamD str)
398------------------------------------------------------------------------------
399-- Combine N Streams - intercalate
400------------------------------------------------------------------------------
402-- XXX we can swap the order of arguments to gintercalate so that the
403-- definition of unfoldMany becomes simpler? The first stream should be
404-- infixed inside the second one. However, if we change the order in
405-- "interleave" as well similarly, then that will make it a bit unintuitive.
406--
407-- > unfoldMany unf str =
408-- > gintercalate unf str (UF.nilM (\_ -> return ())) (repeat ())
410-- | 'interleaveInfix' followed by unfold and concat.
411--
412-- /Pre-release/
413{-# INLINE gintercalate #-}
414gintercalate
415 :: Monad m
416 => Unfold m a c -> Stream m a -> Unfold m b c -> Stream m b -> Stream m c
417gintercalate unf1 str1 unf2 str2 =
418 fromStreamD $ D.gintercalate
419 unf1 (toStreamD str1)
420 unf2 (toStreamD str2)
422-- > intercalate unf seed str = gintercalate unf str unf (repeatM seed)
424-- | 'intersperse' followed by unfold and concat.
425--
426-- >>> intercalate u a = Stream.unfoldMany u . Stream.intersperse a
427-- >>> intersperse = Stream.intercalate Unfold.identity
428-- >>> unwords = Stream.intercalate Unfold.fromList " "
429--
430-- >>> input = Stream.fromList ["abc", "def", "ghi"]
431-- >>> Stream.fold Fold.toList $ Stream.intercalate Unfold.fromList " " input
432-- "abc def ghi"
433--
434-- @since 0.9.0
435{-# INLINE intercalate #-}
436intercalate :: Monad m
437 => Unfold m b c -> b -> Stream m b -> Stream m c
438intercalate unf seed str = fromStreamD $
439 D.unfoldMany unf $ D.intersperse seed (toStreamD str)
441-- | 'interleaveSuffix' followed by unfold and concat.
442--
443-- /Pre-release/
444{-# INLINE gintercalateSuffix #-}
445gintercalateSuffix
446 :: Monad m
447 => Unfold m a c -> Stream m a -> Unfold m b c -> Stream m b -> Stream m c
448gintercalateSuffix unf1 str1 unf2 str2 =
449 fromStreamD $ D.gintercalateSuffix
450 unf1 (toStreamD str1)
451 unf2 (toStreamD str2)
453-- > intercalateSuffix unf seed str = gintercalateSuffix unf str unf (repeatM seed)
455-- | 'intersperseSuffix' followed by unfold and concat.
456--
457-- >>> intercalateSuffix u a = Stream.unfoldMany u . Stream.intersperseSuffix a
458-- >>> intersperseSuffix = Stream.intercalateSuffix Unfold.identity
459-- >>> unlines = Stream.intercalateSuffix Unfold.fromList "\n"
460--
461-- >>> input = Stream.fromList ["abc", "def", "ghi"]
462-- >>> Stream.fold Fold.toList $ Stream.intercalateSuffix Unfold.fromList "\n" input
463-- "abc\ndef\nghi\n"
464--
465-- @since 0.9.0
466{-# INLINE intercalateSuffix #-}
467intercalateSuffix :: Monad m
468 => Unfold m b c -> b -> Stream m b -> Stream m c
469intercalateSuffix unf seed str = fromStreamD $ D.unfoldMany unf
470 $ D.intersperseSuffix (return seed) (toStreamD str)
472------------------------------------------------------------------------------
473-- Combine N Streams - concatMap
474------------------------------------------------------------------------------
476-- | Flatten a stream of streams to a single stream.
477--
478-- >>> concat = Stream.concatMap id
479--
480-- /Pre-release/
481{-# INLINE concat #-}
482concat :: Monad m => Stream m (Stream m a) -> Stream m a
483concat = concatMap id
485------------------------------------------------------------------------------
486-- Combine N Streams - concatMap
487------------------------------------------------------------------------------
489-- | Like 'concatMapWith' but carries a state which can be used to share
490-- information across multiple steps of concat.
491--
492-- >>> concatSmapMWith combine f initial = Stream.concatMapWith combine id . Stream.smapM f initial
493--
494-- /Pre-release/
495--
496{-# INLINE concatSmapMWith #-}
497concatSmapMWith
498 :: Monad m
499 => (Stream m b -> Stream m b -> Stream m b)
500 -> (s -> a -> m (s, Stream m b))
501 -> m s
502 -> Stream m a
503 -> Stream m b
504concatSmapMWith combine f initial =
505 concatMapWith combine id . smapM f initial
507-- XXX Implement a StreamD version for fusion.
509-- | Combine streams in pairs using a binary stream combinator, then combine
510-- the resulting streams in pairs recursively until we get to a single combined
511-- stream.
512--
513-- For example, you can sort a stream using merge sort like this:
514--
515-- >>> s = Stream.fromList [5,1,7,9,2]
516-- >>> generate = Stream.fromPure
517-- >>> combine = Stream.mergeBy compare
518-- >>> Stream.fold Fold.toList $ Stream.concatPairsWith combine generate s
519-- [1,2,5,7,9]
520--
521-- /Caution: the stream of streams must be finite/
522--
523-- /Pre-release/
524--
525{-# INLINE concatPairsWith #-}
526concatPairsWith ::
527 (Stream m b -> Stream m b -> Stream m b)
528 -> (a -> Stream m b)
529 -> Stream m a
530 -> Stream m b
531concatPairsWith par f m =
532 fromStreamK
533 $ K.concatPairsWith
534 (\s1 s2 -> toStreamK $ fromStreamK s1 `par` fromStreamK s2)
535 (toStreamK . f)
536 (toStreamK m)
538------------------------------------------------------------------------------
539-- IterateMap - Map and flatten Trees of Streams
540------------------------------------------------------------------------------
542-- | Like 'iterateM' but iterates after mapping a stream generator on the
543-- output.
544--
545-- Yield an input element in the output stream, map a stream generator on it
546-- and then do the same on the resulting stream. This can be used for a depth
547-- first traversal of a tree like structure.
548--
549-- Note that 'iterateM' is a special case of 'iterateMapWith':
550--
551-- >>> iterateM f = Stream.iterateMapWith Stream.append (Stream.fromEffect . f) . Stream.fromEffect
552--
553-- It can be used to traverse a tree structure. For example, to list a
554-- directory tree:
555--
556-- >> input = Stream.fromPure (Left ".")
557-- >> f = either Dir.toEither (const Stream.nil)
558-- >> ls = Stream.iterateMapWith Stream.append f input
559--
560-- /Pre-release/
561--
562{-# INLINE iterateMapWith #-}
563iterateMapWith ::
564 (Stream m a -> Stream m a -> Stream m a)
565 -> (a -> Stream m a)
566 -> Stream m a
567 -> Stream m a
568iterateMapWith combine f = concatMapWith combine go
569 where
570 go x = fromPure x `combine` concatMapWith combine go (f x)
572-- | Same as @iterateMapWith Stream.serial@ but more efficient due to stream
573-- fusion.
574--
575-- /Unimplemented/
576{-# INLINE iterateUnfold #-}
577iterateUnfold :: -- (MonadAsync m) =>
578 Unfold m a a -> Stream m a -> Stream m a
579iterateUnfold = undefined
581------------------------------------------------------------------------------
582-- Flattening Graphs
583------------------------------------------------------------------------------
585-- To traverse graphs we need a state to be carried around in the traversal.
586-- For example, we can use a hashmap to store the visited status of nodes.
588-- | Like 'iterateMap' but carries a state in the stream generation function.
589-- This can be used to traverse graph like structures, we can remember the
590-- visited nodes in the state to avoid cycles.
591--
592-- Note that a combination of 'iterateMap' and 'usingState' can also be used to
593-- traverse graphs. However, this function provides a more localized state
594-- instead of using a global state.
595--
596-- See also: 'mfix'
597--
598-- /Pre-release/
599--
600{-# INLINE iterateSmapMWith #-}
601iterateSmapMWith
602 :: Monad m
603 => (Stream m a -> Stream m a -> Stream m a)
604 -> (b -> a -> m (b, Stream m a))
605 -> m b
606 -> Stream m a
607 -> Stream m a
608iterateSmapMWith combine f initial stream =
609 concatMap
610 (\b -> concatMapWith combine (go b) stream)
611 (fromEffect initial)
613 where
615 go b a = fromPure a `combine` feedback b a
617 feedback b a =
618 concatMap
619 (\(b1, s) -> concatMapWith combine (go b1) s)
620 (fromEffect $ f b a)
622------------------------------------------------------------------------------
623-- Either streams
624------------------------------------------------------------------------------
626-- Keep concating either streams as long as rights are generated, stop as soon
627-- as a left is generated and concat the left stream.
628--
629-- See also: 'handle'
630--
631-- /Unimplemented/
632--
633{-
634concatMapEitherWith
635 :: -- (MonadAsync m) =>
636 (forall x. t m x -> t m x -> t m x)
637 -> (a -> t m (Either (Stream m b) b))
638 -> Stream m a
639 -> Stream m b
640concatMapEitherWith = undefined
641-}
643-- | In an 'Either' stream iterate on 'Left's. This is a special case of
644-- 'iterateMapWith':
645--
646-- >>> iterateMapLeftsWith combine f = Stream.iterateMapWith combine (either f (const Stream.nil))
647--
648-- To traverse a directory tree:
649--
650-- >> input = fromPure (Left ".")
651-- >> ls = Stream.iterateMapLeftsWith Stream.append Dir.toEither input
652--
653-- /Pre-release/
654--
655{-# INLINE iterateMapLeftsWith #-}
656iterateMapLeftsWith
657 :: (b ~ Either a c)
658 => (Stream m b -> Stream m b -> Stream m b)
659 -> (a -> Stream m b)
660 -> Stream m b
661 -> Stream m b
662iterateMapLeftsWith combine f =
663 iterateMapWith combine (either f (const nil))