on master help sign in
Implement many2, map2
9ed94a6Harendra Kumar
benchmark/Streamly/Benchmark/Data/Unfold.hs+13−13
@@ -38,7 +38,7 @@ import qualified Streamly.Internal.Data.Stream.StreamK as K
38import qualified Streamly.Prelude as SP
38import qualified Streamly.Prelude as SP
39
39
40import Gauge hiding (env)
40import Gauge hiding (env)
41−import Prelude hiding (concat, take, filter, zipWith, map, mapM, takeWhile)
41+import Prelude hiding (take, filter, zipWith, map, mapM, takeWhile)
42import Streamly.Benchmark.Common
42import Streamly.Benchmark.Common
43import Streamly.Benchmark.Common.Handle
43import Streamly.Benchmark.Common.Handle
44
44
@@ -305,12 +305,12 @@ mapM :: Monad m => Int -> Int -> m ()
305mapM size start =
305mapM size start =
306 drainTransformationDefault (size + start) (UF.mapM (return . (+) 1)) start
306 drainTransformationDefault (size + start) (UF.mapM (return . (+) 1)) start
307
307
308−{-# INLINE mapMWithInput #-}
308+{-# INLINE mapM2 #-}
309−mapMWithInput :: Monad m => Int -> Int -> m ()
309+mapM2 :: Monad m => Int -> Int -> m ()
310−mapMWithInput size start =
310+mapM2 size start =
311 drainTransformationDefault
311 drainTransformationDefault
312 size
312 size
313− (UF.mapMWithInput (\a b -> return $ a + b))
313+ (UF.mapM2 (\a b -> return $ a + b))
314 start
314 start
315
315
316-------------------------------------------------------------------------------
316-------------------------------------------------------------------------------
@@ -418,6 +418,9 @@ teeZipWith size start =
418-- Applicative
418-- Applicative
419-------------------------------------------------------------------------------
419-------------------------------------------------------------------------------
420
420
421+nthRoot :: Double -> Int -> Int
422+nthRoot n value = round (fromIntegral value**(1/n))
423+
421{-# INLINE toNullAp #-}
424{-# INLINE toNullAp #-}
422toNullAp :: Monad m => Int -> Int -> m ()
425toNullAp :: Monad m => Int -> Int -> m ()
423toNullAp value start =
426toNullAp value start =
@@ -438,9 +441,6 @@ _apDiscardSnd = undefined
438-- Monad
441-- Monad
439-------------------------------------------------------------------------------
442-------------------------------------------------------------------------------
440
443
441−nthRoot :: Double -> Int -> Int
442−nthRoot n value = round (fromIntegral value**(1/n))
443−
444{-# INLINE concatMapM #-}
444{-# INLINE concatMapM #-}
445concatMapM :: Monad m => Int -> Int -> m ()
445concatMapM :: Monad m => Int -> Int -> m ()
446concatMapM value start =
446concatMapM value start =
@@ -601,9 +601,9 @@ concatCount :: Int -> Int
601concatCount linearCount =
601concatCount linearCount =
602 round (((1 + 8 * fromIntegral linearCount)**(1/2::Double) - 1) / 2)
602 round (((1 + 8 * fromIntegral linearCount)**(1/2::Double) - 1) / 2)
603
603
604−{-# INLINE concat #-}
604+{-# INLINE many #-}
605−concat :: Monad m => Int -> Int -> m ()
605+many :: Monad m => Int -> Int -> m ()
606−concat linearCount start = do
606+many linearCount start = do
607 let end = start + concatCount linearCount
607 let end = start + concatCount linearCount
608 UF.fold FL.drain (UF.many (source end) (source end)) start
608 UF.fold FL.drain (UF.many (source end) (source end)) start
609
609
@@ -668,7 +668,7 @@ o_1_space_transformation size =
668 "transformation"
668 "transformation"
669 [ benchIO "map" $ map size
669 [ benchIO "map" $ map size
670 , benchIO "mapM" $ mapM size
670 , benchIO "mapM" $ mapM size
671− , benchIO "mapMWithInput" $ mapMWithInput size
671+ , benchIO "mapM2" $ mapM2 size
672 , benchIO "postscan" $ postscan size
672 , benchIO "postscan" $ postscan size
673 ]
673 ]
674 ]
674 ]
@@ -728,7 +728,7 @@ o_1_space_nested env size =
728 , benchIO "filterAllIn" $ filterAllIn size
728 , benchIO "filterAllIn" $ filterAllIn size
729 , benchIO "filterSome" $ filterSome size
729 , benchIO "filterSome" $ filterSome size
730
730
731− , benchIO "concat" $ concat size
731+ , benchIO "many" $ many size
732 , mkBench "foldMany (Fold.takeEndBy_ (== lf) Fold.drain)" env
732 , mkBench "foldMany (Fold.takeEndBy_ (== lf) Fold.drain)" env
733 $ \inh _ -> foldManySepBy inh
733 $ \inh _ -> foldManySepBy inh
734 ]
734 ]
core/src/Streamly/Internal/Data/Array/Unboxed/Mut.hs+2−2
@@ -71,7 +71,7 @@ genSlicesFromLen from len =
71 let fromThenTo n = (from, from + len, n - 1)
71 let fromThenTo n = (from, from + len, n - 1)
72 mkSlice n i = return (i, min len (n - i))
72 mkSlice n i = return (i, min len (n - i))
73 in Unfold.lmap length
73 in Unfold.lmap length
74− $ Unfold.mapMWithInput mkSlice
74+ $ Unfold.mapM2 mkSlice
75 $ Unfold.lmap fromThenTo Unfold.enumerateFromThenTo
75 $ Unfold.lmap fromThenTo Unfold.enumerateFromThenTo
76
76
77-- | Generate a stream of slices of specified length from an array, starting
77-- | Generate a stream of slices of specified length from an array, starting
@@ -86,7 +86,7 @@ getSlicesFromLen :: forall m a. (Monad m, Unboxed a)
86 -> Unfold m (Array a) (Array a)
86 -> Unfold m (Array a) (Array a)
87getSlicesFromLen from len =
87getSlicesFromLen from len =
88 let mkSlice arr (i, n) = return $ getSliceUnsafe i n arr
88 let mkSlice arr (i, n) = return $ getSliceUnsafe i n arr
89− in Unfold.mapMWithInput mkSlice (genSlicesFromLen from len)
89+ in Unfold.mapM2 mkSlice (genSlicesFromLen from len)
90
90
91-- | Create an 'Array' from a stream. This is useful when we want to create a
91-- | Create an 'Array' from a stream. This is useful when we want to create a
92-- single array from a stream of unknown size. 'writeN' is at least twice
92-- single array from a stream of unknown size. 'writeN' is at least twice
core/src/Streamly/Internal/Data/Unfold.hs+11−34
@@ -196,10 +196,15 @@ module Streamly.Internal.Data.Unfold
196 -- * Folding
196 -- * Folding
197 , fold
197 , fold
198
198
199+ -- XXX Add "WithInput" versions of all these, map2, postscan2, takeWhile2,
200+ -- filter2 etc. Alternatively, we can use the default operations with
201+ -- input, but that might make the common case more inconvenient.
202+
199 -- ** Mapping on Output
203 -- ** Mapping on Output
200 , map
204 , map
205+ , map2
201 , mapM
206 , mapM
202− , mapMWithInput
207+ , mapM2
203
208
204 , postscanlM'
209 , postscanlM'
205 , postscan
210 , postscan
@@ -234,6 +239,7 @@ module Streamly.Internal.Data.Unfold
234 -- ** Nesting
239 -- ** Nesting
235 , ConcatState (..)
240 , ConcatState (..)
236 , many
241 , many
242+ , many2
237 , concatMapM
243 , concatMapM
238 , bind
244 , bind
239
245
@@ -419,39 +425,6 @@ foldMany (Fold fstep initial extract) (Unfold ustep inject1) =
419 step (FoldManyYield b next) = return $ Yield b next
425 step (FoldManyYield b next) = return $ Yield b next
420 step FoldManyDone = return Stop
426 step FoldManyDone = return Stop
421
427
422−-- | Apply a monadic function to each element of the stream and replace it
423−-- with the output of the resulting action.
424−--
425−-- /Since: 0.8.0/
426−--
427−{-# INLINE_NORMAL mapM #-}
428−mapM :: Monad m => (b -> m c) -> Unfold m a b -> Unfold m a c
429−mapM f (Unfold ustep uinject) = Unfold step uinject
430− where
431− {-# INLINE_LATE step #-}
432− step st = do
433− r <- ustep st
434− case r of
435− Yield x s -> f x >>= \a -> return $ Yield a s
436− Skip s -> return $ Skip s
437− Stop -> return Stop
438−
439−{-# INLINE_NORMAL mapMWithInput #-}
440−mapMWithInput :: Monad m => (a -> b -> m c) -> Unfold m a b -> Unfold m a c
441−mapMWithInput f (Unfold ustep uinject) = Unfold step inject
442− where
443− inject a = do
444− r <- uinject a
445− return (a, r)
446−
447− {-# INLINE_LATE step #-}
448− step (inp, st) = do
449− r <- ustep st
450− case r of
451− Yield x s -> f inp x >>= \a -> return $ Yield a (inp, s)
452− Skip s -> return $ Skip (inp, s)
453− Stop -> return Stop
454−
455-------------------------------------------------------------------------------
428-------------------------------------------------------------------------------
456-- Either
429-- Either
457-------------------------------------------------------------------------------
430-------------------------------------------------------------------------------
@@ -476,6 +449,8 @@ either (Unfold step1 inject1) = Unfold step inject
476 Skip s -> Skip (s, f)
449 Skip s -> Skip (s, f)
477 Stop -> Stop) <$> step1 st
450 Stop -> Stop) <$> step1 st
478
451
452+-- postscan2 :: Monad m => Refold m a b c -> Unfold m a b -> Unfold m a c
453+
479-- | Scan the output of an 'Unfold' to change it in a stateful manner.
454-- | Scan the output of an 'Unfold' to change it in a stateful manner.
480--
455--
481-- /Pre-release/
456-- /Pre-release/
@@ -552,6 +527,8 @@ scanWith restart (Fold fstep initial extract) (Unfold stepU injectU) =
552scanMany :: Monad m => Fold m b c -> Unfold m a b -> Unfold m a c
527scanMany :: Monad m => Fold m b c -> Unfold m a b -> Unfold m a c
553scanMany = scanWith True
528scanMany = scanWith True
554
529
530+-- scan2 :: Monad m => Refold m a b c -> Unfold m a b -> Unfold m a c
531+
555-- | Scan the output of an 'Unfold' to change it in a stateful manner.
532-- | Scan the output of an 'Unfold' to change it in a stateful manner.
556-- Once fold is done it will stop.
533-- Once fold is done it will stop.
557--
534--
core/src/Streamly/Internal/Data/Unfold/Type.hs+109−2
@@ -35,6 +35,9 @@ module Streamly.Internal.Data.Unfold.Type
35 , lmap
35 , lmap
36 , lmapM
36 , lmapM
37 , map
37 , map
38+ , map2
39+ , mapM
40+ , mapM2
38 , both
41 , both
39 , first
42 , first
40 , second
43 , second
@@ -47,7 +50,9 @@ module Streamly.Internal.Data.Unfold.Type
47 -- * Nesting
50 -- * Nesting
48 , ConcatState (..)
51 , ConcatState (..)
49 , many
52 , many
53+ , many2
50 , manyInterleave
54 , manyInterleave
55+ -- , manyInterleave2
51
56
52 -- Applicative
57 -- Applicative
53 , apSequence
58 , apSequence
@@ -77,7 +82,7 @@ import Fusion.Plugin.Types (Fuse(..))
77import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))
82import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))
78import Streamly.Internal.Data.Stream.StreamD.Step (Step(..))
83import Streamly.Internal.Data.Stream.StreamD.Step (Step(..))
79
84
80−import Prelude hiding (map, concatMap, zipWith, takeWhile)
85+import Prelude hiding (map, mapM, concatMap, zipWith, takeWhile)
81
86
82-- $setup
87-- $setup
83-- >>> import qualified Streamly.Prelude as Stream
88-- >>> import qualified Streamly.Prelude as Stream
@@ -290,11 +295,70 @@ takeWhile f = takeWhileM (return . f)
290-- Functor
295-- Functor
291------------------------------------------------------------------------------
296------------------------------------------------------------------------------
292
297
298+{-# INLINE_NORMAL mapM2 #-}
299+mapM2 :: Monad m => (a -> b -> m c) -> Unfold m a b -> Unfold m a c
300+mapM2 f (Unfold ustep uinject) = Unfold step inject
301+ where
302+ inject a = do
303+ r <- uinject a
304+ return (a, r)
305+
306+ {-# INLINE_LATE step #-}
307+ step (inp, st) = do
308+ r <- ustep st
309+ case r of
310+ Yield x s -> f inp x >>= \a -> return $ Yield a (inp, s)
311+ Skip s -> return $ Skip (inp, s)
312+ Stop -> return Stop
313+
314+-- | Apply a monadic function to each element of the stream and replace it
315+-- with the output of the resulting action.
316+--
317+-- >>> mapM f = Unfold.mapM2 (const f)
318+--
319+-- /Since: 0.8.0/
320+--
321+{-# INLINE_NORMAL mapM #-}
322+mapM :: Monad m => (b -> m c) -> Unfold m a b -> Unfold m a c
323+-- mapM f = mapM2 (const f)
324+mapM f (Unfold ustep uinject) = Unfold step uinject
325+ where
326+ {-# INLINE_LATE step #-}
327+ step st = do
328+ r <- ustep st
329+ case r of
330+ Yield x s -> f x >>= \a -> return $ Yield a s
331+ Skip s -> return $ Skip s
332+ Stop -> return Stop
333+
334+-- |
335+--
336+-- >>> map2 f = Unfold.mapM2 (\a b -> pure (f a b))
337+--
338+{-# INLINE_NORMAL map2 #-}
339+map2 :: Functor m => (a -> b -> c) -> Unfold m a b -> Unfold m a c
340+-- map2 f = mapM2 (\a b -> pure (f a b))
341+map2 f (Unfold ustep uinject) = Unfold step (\a -> (a,) <$> uinject a)
342+
343+ where
344+
345+ func a r =
346+ case r of
347+ Yield x s -> Yield (f a x) (a, s)
348+ Skip s -> Skip (a, s)
349+ Stop -> Stop
350+
351+ {-# INLINE_LATE step #-}
352+ step (a, st) = fmap (func a) (ustep st)
353+
293-- | Map a function on the output of the unfold (the type @b@).
354-- | Map a function on the output of the unfold (the type @b@).
294--
355--
356+-- >>> map f = Unfold.map2 (const f)
357+--
295-- /Pre-release/
358-- /Pre-release/
296{-# INLINE_NORMAL map #-}
359{-# INLINE_NORMAL map #-}
297map :: Functor m => (b -> c) -> Unfold m a b -> Unfold m a c
360map :: Functor m => (b -> c) -> Unfold m a b -> Unfold m a c
361+-- map f = map2 (const f)
298map f (Unfold ustep uinject) = Unfold step uinject
362map f (Unfold ustep uinject) = Unfold step uinject
299
363
300 where
364 where
@@ -373,15 +437,49 @@ apDiscardSnd :: -- Monad m =>
373 Unfold m a b -> Unfold m a c -> Unfold m a b
437 Unfold m a b -> Unfold m a c -> Unfold m a b
374apDiscardSnd (Unfold _step1 _inject1) (Unfold _step2 _inject2) = undefined
438apDiscardSnd (Unfold _step1 _inject1) (Unfold _step2 _inject2) = undefined
375
439
440+{-# ANN type Many2State Fuse #-}
441+data Many2State x s1 s2 = Many2Outer x s1 | Many2Inner x s1 s2
442+
443+{-# INLINE_NORMAL many2 #-}
444+many2 :: Monad m => Unfold m (a, b) c -> Unfold m a b -> Unfold m a c
445+many2 (Unfold step2 inject2) (Unfold step1 inject1) = Unfold step inject
446+
447+ where
448+
449+ inject a = do
450+ s <- inject1 a
451+ return $ Many2Outer a s
452+
453+ {-# INLINE_LATE step #-}
454+ step (Many2Outer a st) = do
455+ r <- step1 st
456+ case r of
457+ Yield b s -> do
458+ innerSt <- inject2 (a, b)
459+ return $ Skip (Many2Inner a s innerSt)
460+ Skip s -> return $ Skip (Many2Outer a s)
461+ Stop -> return Stop
462+
463+ step (Many2Inner a ost ist) = do
464+ r <- step2 ist
465+ return $ case r of
466+ Yield x s -> Yield x (Many2Inner a ost s)
467+ Skip s -> Skip (Many2Inner a ost s)
468+ Stop -> Skip (Many2Outer a ost)
469+
376data Cross a s1 b s2 = CrossOuter a s1 | CrossInner a s1 b s2
470data Cross a s1 b s2 = CrossOuter a s1 | CrossInner a s1 b s2
377
471
378-- | Create a cross product (vector product or cartesian product) of the
472-- | Create a cross product (vector product or cartesian product) of the
379-- output streams of two unfolds using a monadic combining function.
473-- output streams of two unfolds using a monadic combining function.
380--
474--
475+-- >>> f1 f u = Unfold.mapM2 (\(_, c) b -> f b c) (Unfold.lmap fst u)
476+-- >>> crossWithM f u = Unfold.many2 (f1 f u)
477+--
381-- /Pre-release/
478-- /Pre-release/
382{-# INLINE_NORMAL crossWithM #-}
479{-# INLINE_NORMAL crossWithM #-}
383crossWithM :: Monad m =>
480crossWithM :: Monad m =>
384 (b -> c -> m d) -> Unfold m a b -> Unfold m a c -> Unfold m a d
481 (b -> c -> m d) -> Unfold m a b -> Unfold m a c -> Unfold m a d
482+-- crossWithM f u1 u2 = many2 (mapM2 (\(_, b) c -> f b c) (lmap fst u2)) u1
385crossWithM f (Unfold step1 inject1) (Unfold step2 inject2) = Unfold step inject
483crossWithM f (Unfold step1 inject1) (Unfold step2 inject2) = Unfold step inject
386
484
387 where
485 where
@@ -443,6 +541,9 @@ cross = crossWith (,)
443apply :: Monad m => Unfold m a (b -> c) -> Unfold m a b -> Unfold m a c
541apply :: Monad m => Unfold m a (b -> c) -> Unfold m a b -> Unfold m a c
444apply u1 u2 = fmap (\(a, b) -> a b) (cross u1 u2)
542apply u1 u2 = fmap (\(a, b) -> a b) (cross u1 u2)
445
543
544+-- XXX Applicative makes sense for unfolds, but monad does not. Use streams for
545+-- monad.
546+
446{-
547{-
447-- | Example:
548-- | Example:
448--
549--
@@ -473,6 +574,9 @@ data ConcatMapState m b s1 x =
473 ConcatMapOuter x s1
574 ConcatMapOuter x s1
474 | forall s2. ConcatMapInner x s1 s2 (s2 -> m (Step s2 b))
575 | forall s2. ConcatMapInner x s1 s2 (s2 -> m (Step s2 b))
475
576
577+-- XXX This is experimental. We should rather use streams if concatMap like
578+-- functionality is needed. This is no more efficient than streams.
579+
476-- | Map an unfold generating action to each element of an unfold and
580-- | Map an unfold generating action to each element of an unfold and
477-- flatten the results into a single stream.
581-- flatten the results into a single stream.
478--
582--
@@ -593,12 +697,15 @@ data ConcatState s1 s2 = ConcatOuter s1 | ConcatInner s1 s2
593-- | Apply the first unfold to each output element of the second unfold and
697-- | Apply the first unfold to each output element of the second unfold and
594-- flatten the output in a single stream.
698-- flatten the output in a single stream.
595--
699--
700+-- >>> many u = Unfold.many2 (Unfold.lmap snd u)
701+--
596-- /Since: 0.9.0 (signature changed)/
702-- /Since: 0.9.0 (signature changed)/
597--
703--
598-- /Since: 0.8.0/
704-- /Since: 0.8.0/
599--
705--
600{-# INLINE_NORMAL many #-}
706{-# INLINE_NORMAL many #-}
601many :: Monad m => Unfold m b c -> Unfold m a b -> Unfold m a c
707many :: Monad m => Unfold m b c -> Unfold m a b -> Unfold m a c
708+-- many u1 = many2 (lmap snd u1)
602many (Unfold step2 inject2) (Unfold step1 inject1) = Unfold step inject
709many (Unfold step2 inject2) (Unfold step1 inject1) = Unfold step inject
603
710
604 where
711 where
@@ -635,7 +742,7 @@ instance Monad m => Category (Unfold m) where
635 id = identity
742 id = identity
636
743
637 {-# INLINE (.) #-}
744 {-# INLINE (.) #-}
638− (.) = flip many
745+ (.) = many
639-}
746-}
640
747
641-------------------------------------------------------------------------------
748-------------------------------------------------------------------------------
src/Streamly/Data/Unfold.hs+114−51
@@ -13,83 +13,146 @@
13--
13--
14-- >>> import qualified Streamly.Data.Fold as Fold
14-- >>> import qualified Streamly.Data.Fold as Fold
15-- >>> import qualified Streamly.Data.Unfold as Unfold
15-- >>> import qualified Streamly.Data.Unfold as Unfold
16−-- >>> import qualified Streamly.Prelude as Stream
16+-- >>> import qualified Streamly.Internal.Data.Stream as Stream
17--
17--
18-- For example, the 'fromList' Unfold generates a stream of values from a
18-- For example, the 'fromList' Unfold generates a stream of values from a
19−-- supplied list. Unfolds can be converted to 'Streamly.Prelude.SerialT'
19+-- supplied list. Unfolds can be converted to 'Streamly.Internal.Data.Stream'
20−-- stream using the Stream.unfold operation.
20+-- using the 'Stream.unfold' operation.
21--
21--
22-- >>> stream = Stream.unfold Unfold.fromList [1..100]
22-- >>> stream = Stream.unfold Unfold.fromList [1..100]
23−-- >>> Stream.sum stream
23+-- >>> Stream.fold Fold.sum stream
24-- 5050
24-- 5050
25--
25--
26−-- All the serial stream generation operations in "Streamly.Prelude"
27−-- can be expressed using unfolds:
28−--
29−-- > Stream.fromList = Stream.unfold Unfold.fromList [1..100]
30−--
31−-- Conceptually, an 'Unfold' is just like "Data.List.unfoldr". Let us write a
32−-- step function to unfold a list using "Data.List.unfoldr":
33−--
34−-- >>> :{
35−-- f [] = Nothing
36−-- f (x:xs) = Just (x, xs)
37−-- :}
38−--
39−-- >>> Data.List.unfoldr f [1,2,3]
40−-- [1,2,3]
41−--
42−-- Unfold.unfoldr is just the same, it uses the same step function:
43−--
44−-- >>> Stream.toList $ Stream.unfold (Unfold.unfoldr f) [1,2,3]
45−-- [1,2,3]
46−--
47−-- The input of an unfold can be transformed using 'lmap':
26+-- The input seed of an unfold can be transformed using 'lmap':
48--
27--
49-- >>> u = Unfold.lmap (fmap (+1)) Unfold.fromList
28-- >>> u = Unfold.lmap (fmap (+1)) Unfold.fromList
50−-- >>> Stream.toList $ Stream.unfold u [1..5]
29+-- >>> Stream.fold Fold.toList $ Stream.unfold u [1..5]
51-- [2,3,4,5,6]
30-- [2,3,4,5,6]
52--
31--
53−-- 'Unfold' streams can be transformed using transformation combinators. For
32+-- Output stream of an 'Unfold' can be transformed using transformation
54−-- example, to retain only the first two elements of an unfold:
33+-- combinators. For example, to retain only the first two elements of an
34+-- unfold:
55--
35--
56-- >>> u = Unfold.take 2 Unfold.fromList
36-- >>> u = Unfold.take 2 Unfold.fromList
57−-- >>> Stream.toList $ Stream.unfold u [1..100]
37+-- >>> Stream.fold Fold.toList $ Stream.unfold u [1..100]
58-- [1,2]
38-- [1,2]
59--
39--
60−-- Multiple unfolds can be combined in several interesting ways. For example,
40+-- Unfolds can be nested efficiently. For example, to implement nested looping:
61−-- to generate nested looping as in imperative languages (also known as cross
62−-- product of the two streams):
63--
41--
64-- >>> u1 = Unfold.lmap fst Unfold.fromList
42-- >>> u1 = Unfold.lmap fst Unfold.fromList
65-- >>> u2 = Unfold.lmap snd Unfold.fromList
43-- >>> u2 = Unfold.lmap snd Unfold.fromList
66-- >>> u = Unfold.crossWith (,) u1 u2
44-- >>> u = Unfold.crossWith (,) u1 u2
67−-- >>> Stream.toList $ Stream.unfold u ([1,2,3], [4,5,6])
45+-- >>> Stream.fold Fold.toList $ Stream.unfold u ([1,2,3], [4,5,6])
68-- [(1,4),(1,5),(1,6),(2,4),(2,5),(2,6),(3,4),(3,5),(3,6)]
46-- [(1,4),(1,5),(1,6),(2,4),(2,5),(2,6),(3,4),(3,5),(3,6)]
69--
47--
70−-- Nested loops using unfolds provide C like performance due to complete stream
48+-- 'Unfold' @u1@ generates a stream from the first list in the input tuple,
71−-- fusion.
49+-- @u2@ generates another stream from the second list. The combines 'Unfold'
50+-- @u@ nests the two streams i.e. for each element in first stream, for each
51+-- element in second stream apply the supplied function (i.e. @(,)@) to the
52+-- pair of elements.
53+--
54+-- This is the equivalent of the nested looping construct from imperative
55+-- languages, also known as the cross product of two streams in functional
56+-- parlance.
72--
57--
73-- Please see "Streamly.Internal.Data.Unfold" for additional @Pre-release@
58-- Please see "Streamly.Internal.Data.Unfold" for additional @Pre-release@
74-- functions.
59-- functions.
75--
60--
61+-- = Creating New Unfolds
62+--
63+-- There are many commonly used unfolds provided in this module. However, you
64+-- can always create your own as well. An 'Unfold' is just a data
65+-- representation of a stream generator function. It consists of an @inject@
66+-- function which covnerts the supplied seed into an internal state of the
67+-- unfold, and a @step@ function which takes the state and generates the next
68+-- output in the stream. For those familiar with the list "Data.List.unfoldr"
69+-- function, this is a data representation of the same.
70+--
71+-- Smart constructor functions are provided in this module for constructing new
72+-- 'Unfolds'. For example, you can use the 'Unfold.unfoldr' constructor to
73+-- create an 'Unfold' from a pure step function, unfoldr uses 'id' as the
74+-- @inject@ function.
75+--
76+-- Let's define a simple pure step function:
77+--
78+-- >>> :{
79+-- f [] = Nothing
80+-- f (x:xs) = Just (x, xs)
81+-- :}
82+--
83+-- Create an 'Unfold' from the step function:
84+--
85+-- >>> u = Unfold.unfoldr f
86+--
87+-- Run the 'Unfold':
88+--
89+-- >>> Stream.fold Fold.toList $ Stream.unfold u [1,2,3]
90+-- [1,2,3]
91+--
92+-- The 'Unfold.unfoldr' smart constructor is essentially the same as the list
93+-- "Data.List.unfoldr" function. We can use the same step function in both::
94+--
95+-- >>> Data.List.unfoldr f [1,2,3]
96+-- [1,2,3]
97+--
76-- = Unfolds vs. Streams
98-- = Unfolds vs. Streams
77--
99--
78−-- Unfolds' raison d'etre is their efficiency in nested stream operations due
100+-- The 'Unfold' abstraction for representing streams was introduced in Streamly
79−-- to complete stream fusion. 'Streamly.Prelude.concatMap' or the 'Monad'
101+-- to provide C like performance for nested looping of streams. 'Unfold' and
80−-- instance of streams use stream generation operations of the shape @a -> t m
102+-- 'Stream' abstractions are similar with the following differences:
81−-- b@ and then flatten the resulting stream. This implementation is more
103+--
82−-- powerful but does not allow for complete stream fusion. Unfolds provide
104+-- * 'Stream' is less efficient than 'Unfold' for nesting.
83−-- less powerful but more efficient 'Streamly.Prelude.unfoldMany', 'many' and
105+-- * 'Stream' is more powerful than 'Unfold'.
84−-- 'crossWith' operations as an alternative to a subset of use cases of
106+-- * 'Stream' API is more convenient for programming
85−-- 'concatMap' and 'Applicative' stream operations.
107+--
86−--
108+-- Unfolds can be easily converted to streams using 'Stream.unfold', however,
87−-- "Streamly.Prelude" exports polymorphic stream generation operations that
109+-- vice versa is not possible. To provide a familiar analogy, 'Unfold' is to
88−-- provide the same functionality as unfolds in this module. Since unfolds can
110+-- 'Stream' as 'Applicative' is to 'Monad'.
89−-- be easily converted to streams, several modules in streamly provide only
111+--
90−-- unfolds for serial stream generation. We cannot use unfolds exclusively for
112+-- To demonstrate the efficiency of unfolds, the nested loop example in the
91−-- stream generation as they do not support concurrency.
113+-- previous section can be implemented with concatMap or Monad instance of
92−
114+-- streams as follows:
115+--
116+-- @
117+-- do
118+-- x <- Stream.unfold Unfold.fromList [1,2,3]
119+-- y <- Stream.unfold Unfold.fromList [4,5,6]
120+-- return (x, y)
121+-- @
122+--
123+-- As you can see, this is more convenient to write than using the 'crossWith'
124+-- unfold combinator. However, this turns out to be many times slower than the
125+-- unfold implementation. The Unfold version is equivalent in performance to
126+-- the C implementation of the same nested loop. Similarly, unfolds can be
127+-- nested with streams using the 'unfoldMany' combinator which is a much more
128+-- efficient alternative to the 'concatMap' operation.
129+--
130+-- Streams use a hybrid implementation approach using direct style as well as
131+-- CPS. Unfolds do not use CPS, therefore, lack the power that is afforded to
132+-- streams by CPS. The CPS implementation allows infinitely scalable @cons@ and
133+-- @append@ operations in streams. It is also used to implement concurrency in
134+-- streams.
135+--
136+-- To summarize, unfolds are a high performance solution to the nesting
137+-- problem. Since streams provide a more palatable API for programming, work
138+-- with streams unless you need unfolds for better performance in nesting
139+-- situations. There is little difference in the way in which unfolds and
140+-- streams are written, it is easy to adapt a stream to an unfold. If you are
141+-- writing an unfold you can convert it to stream for free using
142+-- 'Stream.unfold'.
143+
144+-- What makes streams less efficient is also what makes them more conveient to
145+-- use and powerful. The stream data type (Stream m a) bundles the state along
146+-- with the stream generator function making it opaque, whereas an unfold
147+-- exposes the state (Unfold m s a) to the user. This allows the Unfold to be
148+-- unfolded (inlined) inside a nested loop without having to bundle the state
149+-- and the generator together, the stream state can be saved and passed
150+-- independent of the generator function. On the other hand in a stream type we
151+-- have to bundle the stream state and the generator function together to save
152+-- the stream. This makes it inefficient because it requires boxing and
153+-- constructor allocation. However, this makes streams more convenient as we do
154+-- not need to pass around the state/seed separately.
155+--
93module Streamly.Data.Unfold
156module Streamly.Data.Unfold
94 (
157 (
95 -- * Unfold Type
158 -- * Unfold Type
src/Streamly/Internal/FileSystem/Dir.hs+1−1
@@ -223,7 +223,7 @@ read =
223{-# INLINE readEither #-}
223{-# INLINE readEither #-}
224readEither :: MonadIO m => Unfold m String (Either String String)
224readEither :: MonadIO m => Unfold m String (Either String String)
225readEither =
225readEither =
226− UF.mapMWithInput classify
226+ UF.mapM2 classify
227 $ UF.filter (\x -> x /= "." && x /= "..")
227 $ UF.filter (\x -> x /= "." && x /= "..")
228 -- XXX use proper streaming read of the dir
228 -- XXX use proper streaming read of the dir
229 $ UF.lmapM (liftIO . Dir.getDirectoryContents) UF.fromList
229 $ UF.lmapM (liftIO . Dir.getDirectoryContents) UF.fromList
test/Streamly/Test/Data/Unfold.hs+4−4
@@ -469,13 +469,13 @@ mapM =
469 mList = Prelude.map fA list
469 mList = Prelude.map fA list
470 in testUnfoldMD unf list 0 (length list) mList
470 in testUnfoldMD unf list 0 (length list) mList
471
471
472−mapMWithInput :: Property
472+mapM2 :: Property
473−mapMWithInput =
473+mapM2 =
474 property
474 property
475 $ \f list ->
475 $ \f list ->
476 let fA = applyFun2 f :: [Int] -> Int -> Int
476 let fA = applyFun2 f :: [Int] -> Int -> Int
477 fM x y = modify (+ 1) >> return (fA x y)
477 fM x y = modify (+ 1) >> return (fA x y)
478− unf = UF.mapMWithInput fM UF.fromList
478+ unf = UF.mapM2 fM UF.fromList
479 mList = Prelude.map (fA list) list
479 mList = Prelude.map (fA list) list
480 in testUnfoldMD unf list 0 (length list) mList
480 in testUnfoldMD unf list 0 (length list) mList
481
481
@@ -667,7 +667,7 @@ testTransformation =
667 -- prop "map" map
667 -- prop "map" map
668 prop "postscan" postscan
668 prop "postscan" postscan
669 prop "mapM" mapM
669 prop "mapM" mapM
670− prop "mapMWithInput" mapMWithInput
670+ prop "mapM2" mapM2
671 prop "takeWhileM" takeWhileM
671 prop "takeWhileM" takeWhileM
672 -- prop "takeWhile" takeWhile
672 -- prop "takeWhile" takeWhile
673 prop "take" take
673 prop "take" take