on master help sign in
Implement many2, map2

* crossWith and many can be implemented in terms of many2 * rename mapmWithInput to mapM2 * add map2

commit9ed94a6a09eefb2ecd0c6830c8324e1e64178c75 authorHarendra Kumar <harendra@composewell.com> dateThu 4 Aug 2022, 22:42 (4 years ago) parent07eb303
7 files changed+254−107
benchmark/Streamly/Benchmark/Data/Unfold.hs+13−13 core/src/Streamly/Internal/Data/Array/Unboxed/Mut.hs+2−2 core/src/Streamly/Internal/Data/Unfold.hs+11−34 core/src/Streamly/Internal/Data/Unfold/Type.hs+109−2 src/Streamly/Data/Unfold.hs+114−51 src/Streamly/Internal/FileSystem/Dir.hs+1−1 test/Streamly/Test/Data/Unfold.hs+4−4
benchmark/Streamly/Benchmark/Data/Unfold.hs+13−13
@@ -38,7 +38,7 @@ import qualified Streamly.Internal.Data.Stream.StreamK as K
3838import qualified Streamly.Prelude as SP
3939
4040import Gauge hiding (env)
41−import Prelude hiding (concat, take, filter, zipWith, map, mapM, takeWhile)
41+import Prelude hiding (take, filter, zipWith, map, mapM, takeWhile)
4242import Streamly.Benchmark.Common
4343import Streamly.Benchmark.Common.Handle
4444
@@ -305,12 +305,12 @@ mapM :: Monad m => Int -> Int -> m ()
305305mapM size start =
306306 drainTransformationDefault (size + start) (UF.mapM (return . (+) 1)) start
307307
308−{-# INLINE mapMWithInput #-}
309−mapMWithInput :: Monad m => Int -> Int -> m ()
310−mapMWithInput size start =
308+{-# INLINE mapM2 #-}
309+mapM2 :: Monad m => Int -> Int -> m ()
310+mapM2 size start =
311311 drainTransformationDefault
312312 size
313− (UF.mapMWithInput (\a b -> return $ a + b))
313+ (UF.mapM2 (\a b -> return $ a + b))
314314 start
315315
316316-------------------------------------------------------------------------------
@@ -418,6 +418,9 @@ teeZipWith size start =
418418-- Applicative
419419-------------------------------------------------------------------------------
420420
421+nthRoot :: Double -> Int -> Int
422+nthRoot n value = round (fromIntegral value**(1/n))
423+
421424{-# INLINE toNullAp #-}
422425toNullAp :: Monad m => Int -> Int -> m ()
423426toNullAp value start =
@@ -438,9 +441,6 @@ _apDiscardSnd = undefined
438441-- Monad
439442-------------------------------------------------------------------------------
440443
441−nthRoot :: Double -> Int -> Int
442−nthRoot n value = round (fromIntegral value**(1/n))
443−
444444{-# INLINE concatMapM #-}
445445concatMapM :: Monad m => Int -> Int -> m ()
446446concatMapM value start =
@@ -601,9 +601,9 @@ concatCount :: Int -> Int
601601concatCount linearCount =
602602 round (((1 + 8 * fromIntegral linearCount)**(1/2::Double) - 1) / 2)
603603
604−{-# INLINE concat #-}
605−concat :: Monad m => Int -> Int -> m ()
606−concat linearCount start = do
604+{-# INLINE many #-}
605+many :: Monad m => Int -> Int -> m ()
606+many linearCount start = do
607607 let end = start + concatCount linearCount
608608 UF.fold FL.drain (UF.many (source end) (source end)) start
609609
@@ -668,7 +668,7 @@ o_1_space_transformation size =
668668 "transformation"
669669 [ benchIO "map" $ map size
670670 , benchIO "mapM" $ mapM size
671− , benchIO "mapMWithInput" $ mapMWithInput size
671+ , benchIO "mapM2" $ mapM2 size
672672 , benchIO "postscan" $ postscan size
673673 ]
674674 ]
@@ -728,7 +728,7 @@ o_1_space_nested env size =
728728 , benchIO "filterAllIn" $ filterAllIn size
729729 , benchIO "filterSome" $ filterSome size
730730
731− , benchIO "concat" $ concat size
731+ , benchIO "many" $ many size
732732 , mkBench "foldMany (Fold.takeEndBy_ (== lf) Fold.drain)" env
733733 $ \inh _ -> foldManySepBy inh
734734 ]
core/src/Streamly/Internal/Data/Array/Unboxed/Mut.hs+2−2
@@ -71,7 +71,7 @@ genSlicesFromLen from len =
7171 let fromThenTo n = (from, from + len, n - 1)
7272 mkSlice n i = return (i, min len (n - i))
7373 in Unfold.lmap length
74− $ Unfold.mapMWithInput mkSlice
74+ $ Unfold.mapM2 mkSlice
7575 $ Unfold.lmap fromThenTo Unfold.enumerateFromThenTo
7676
7777-- | Generate a stream of slices of specified length from an array, starting
@@ -86,7 +86,7 @@ getSlicesFromLen :: forall m a. (Monad m, Unboxed a)
8686 -> Unfold m (Array a) (Array a)
8787getSlicesFromLen from len =
8888 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)
9090
9191-- | Create an 'Array' from a stream. This is useful when we want to create a
9292-- 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
196196 -- * Folding
197197 , fold
198198
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+
199203 -- ** Mapping on Output
200204 , map
205+ , map2
201206 , mapM
202− , mapMWithInput
207+ , mapM2
203208
204209 , postscanlM'
205210 , postscan
@@ -234,6 +239,7 @@ module Streamly.Internal.Data.Unfold
234239 -- ** Nesting
235240 , ConcatState (..)
236241 , many
242+ , many2
237243 , concatMapM
238244 , bind
239245
@@ -419,39 +425,6 @@ foldMany (Fold fstep initial extract) (Unfold ustep inject1) =
419425 step (FoldManyYield b next) = return $ Yield b next
420426 step FoldManyDone = return Stop
421427
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−
455428-------------------------------------------------------------------------------
456429-- Either
457430-------------------------------------------------------------------------------
@@ -476,6 +449,8 @@ either (Unfold step1 inject1) = Unfold step inject
476449 Skip s -> Skip (s, f)
477450 Stop -> Stop) <$> step1 st
478451
452+-- postscan2 :: Monad m => Refold m a b c -> Unfold m a b -> Unfold m a c
453+
479454-- | Scan the output of an 'Unfold' to change it in a stateful manner.
480455--
481456-- /Pre-release/
@@ -552,6 +527,8 @@ scanWith restart (Fold fstep initial extract) (Unfold stepU injectU) =
552527scanMany :: Monad m => Fold m b c -> Unfold m a b -> Unfold m a c
553528scanMany = scanWith True
554529
530+-- scan2 :: Monad m => Refold m a b c -> Unfold m a b -> Unfold m a c
531+
555532-- | Scan the output of an 'Unfold' to change it in a stateful manner.
556533-- Once fold is done it will stop.
557534--
core/src/Streamly/Internal/Data/Unfold/Type.hs+109−2
@@ -35,6 +35,9 @@ module Streamly.Internal.Data.Unfold.Type
3535 , lmap
3636 , lmapM
3737 , map
38+ , map2
39+ , mapM
40+ , mapM2
3841 , both
3942 , first
4043 , second
@@ -47,7 +50,9 @@ module Streamly.Internal.Data.Unfold.Type
4750 -- * Nesting
4851 , ConcatState (..)
4952 , many
53+ , many2
5054 , manyInterleave
55+ -- , manyInterleave2
5156
5257 -- Applicative
5358 , apSequence
@@ -77,7 +82,7 @@ import Fusion.Plugin.Types (Fuse(..))
7782import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))
7883import Streamly.Internal.Data.Stream.StreamD.Step (Step(..))
7984
80−import Prelude hiding (map, concatMap, zipWith, takeWhile)
85+import Prelude hiding (map, mapM, concatMap, zipWith, takeWhile)
8186
8287-- $setup
8388-- >>> import qualified Streamly.Prelude as Stream
@@ -290,11 +295,70 @@ takeWhile f = takeWhileM (return . f)
290295-- Functor
291296------------------------------------------------------------------------------
292297
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+
293354-- | Map a function on the output of the unfold (the type @b@).
294355--
356+-- >>> map f = Unfold.map2 (const f)
357+--
295358-- /Pre-release/
296359{-# INLINE_NORMAL map #-}
297360map :: Functor m => (b -> c) -> Unfold m a b -> Unfold m a c
361+-- map f = map2 (const f)
298362map f (Unfold ustep uinject) = Unfold step uinject
299363
300364 where
@@ -373,15 +437,49 @@ apDiscardSnd :: -- Monad m =>
373437 Unfold m a b -> Unfold m a c -> Unfold m a b
374438apDiscardSnd (Unfold _step1 _inject1) (Unfold _step2 _inject2) = undefined
375439
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+
376470data Cross a s1 b s2 = CrossOuter a s1 | CrossInner a s1 b s2
377471
378472-- | Create a cross product (vector product or cartesian product) of the
379473-- output streams of two unfolds using a monadic combining function.
380474--
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+--
381478-- /Pre-release/
382479{-# INLINE_NORMAL crossWithM #-}
383480crossWithM :: Monad m =>
384481 (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
385483crossWithM f (Unfold step1 inject1) (Unfold step2 inject2) = Unfold step inject
386484
387485 where
@@ -443,6 +541,9 @@ cross = crossWith (,)
443541apply :: Monad m => Unfold m a (b -> c) -> Unfold m a b -> Unfold m a c
444542apply u1 u2 = fmap (\(a, b) -> a b) (cross u1 u2)
445543
544+-- XXX Applicative makes sense for unfolds, but monad does not. Use streams for
545+-- monad.
546+
446547{-
447548-- | Example:
448549--
@@ -473,6 +574,9 @@ data ConcatMapState m b s1 x =
473574 ConcatMapOuter x s1
474575 | forall s2. ConcatMapInner x s1 s2 (s2 -> m (Step s2 b))
475576
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+
476580-- | Map an unfold generating action to each element of an unfold and
477581-- flatten the results into a single stream.
478582--
@@ -593,12 +697,15 @@ data ConcatState s1 s2 = ConcatOuter s1 | ConcatInner s1 s2
593697-- | Apply the first unfold to each output element of the second unfold and
594698-- flatten the output in a single stream.
595699--
700+-- >>> many u = Unfold.many2 (Unfold.lmap snd u)
701+--
596702-- /Since: 0.9.0 (signature changed)/
597703--
598704-- /Since: 0.8.0/
599705--
600706{-# INLINE_NORMAL many #-}
601707many :: Monad m => Unfold m b c -> Unfold m a b -> Unfold m a c
708+-- many u1 = many2 (lmap snd u1)
602709many (Unfold step2 inject2) (Unfold step1 inject1) = Unfold step inject
603710
604711 where
@@ -635,7 +742,7 @@ instance Monad m => Category (Unfold m) where
635742 id = identity
636743
637744 {-# INLINE (.) #-}
638− (.) = flip many
745+ (.) = many
639746-}
640747
641748-------------------------------------------------------------------------------
src/Streamly/Data/Unfold.hs+114−51
@@ -13,83 +13,146 @@
1313--
1414-- >>> import qualified Streamly.Data.Fold as Fold
1515-- >>> import qualified Streamly.Data.Unfold as Unfold
16−-- >>> import qualified Streamly.Prelude as Stream
16+-- >>> import qualified Streamly.Internal.Data.Stream as Stream
1717--
1818-- For example, the 'fromList' Unfold generates a stream of values from a
19−-- supplied list. Unfolds can be converted to 'Streamly.Prelude.SerialT'
20−-- stream using the Stream.unfold operation.
19+-- supplied list. Unfolds can be converted to 'Streamly.Internal.Data.Stream'
20+-- using the 'Stream.unfold' operation.
2121--
2222-- >>> stream = Stream.unfold Unfold.fromList [1..100]
23−-- >>> Stream.sum stream
23+-- >>> Stream.fold Fold.sum stream
2424-- 5050
2525--
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':
4827--
4928-- >>> 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]
5130-- [2,3,4,5,6]
5231--
53−-- 'Unfold' streams can be transformed using transformation combinators. For
54−-- example, to retain only the first two elements of an unfold:
32+-- Output stream of an 'Unfold' can be transformed using transformation
33+-- combinators. For example, to retain only the first two elements of an
34+-- unfold:
5535--
5636-- >>> u = Unfold.take 2 Unfold.fromList
57−-- >>> Stream.toList $ Stream.unfold u [1..100]
37+-- >>> Stream.fold Fold.toList $ Stream.unfold u [1..100]
5838-- [1,2]
5939--
60−-- Multiple unfolds can be combined in several interesting ways. For example,
61−-- to generate nested looping as in imperative languages (also known as cross
62−-- product of the two streams):
40+-- Unfolds can be nested efficiently. For example, to implement nested looping:
6341--
6442-- >>> u1 = Unfold.lmap fst Unfold.fromList
6543-- >>> u2 = Unfold.lmap snd Unfold.fromList
6644-- >>> 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])
6846-- [(1,4),(1,5),(1,6),(2,4),(2,5),(2,6),(3,4),(3,5),(3,6)]
6947--
70−-- Nested loops using unfolds provide C like performance due to complete stream
71−-- fusion.
48+-- 'Unfold' @u1@ generates a stream from the first list in the input tuple,
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.
7257--
7358-- Please see "Streamly.Internal.Data.Unfold" for additional @Pre-release@
7459-- functions.
7560--
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+--
7698-- = Unfolds vs. Streams
7799--
78−-- Unfolds' raison d'etre is their efficiency in nested stream operations due
79−-- to complete stream fusion. 'Streamly.Prelude.concatMap' or the 'Monad'
80−-- instance of streams use stream generation operations of the shape @a -> t m
81−-- b@ and then flatten the resulting stream. This implementation is more
82−-- powerful but does not allow for complete stream fusion. Unfolds provide
83−-- less powerful but more efficient 'Streamly.Prelude.unfoldMany', 'many' and
84−-- 'crossWith' operations as an alternative to a subset of use cases of
85−-- 'concatMap' and 'Applicative' stream operations.
86−--
87−-- "Streamly.Prelude" exports polymorphic stream generation operations that
88−-- provide the same functionality as unfolds in this module. Since unfolds can
89−-- be easily converted to streams, several modules in streamly provide only
90−-- unfolds for serial stream generation. We cannot use unfolds exclusively for
91−-- stream generation as they do not support concurrency.
92−
100+-- The 'Unfold' abstraction for representing streams was introduced in Streamly
101+-- to provide C like performance for nested looping of streams. 'Unfold' and
102+-- 'Stream' abstractions are similar with the following differences:
103+--
104+-- * 'Stream' is less efficient than 'Unfold' for nesting.
105+-- * 'Stream' is more powerful than 'Unfold'.
106+-- * 'Stream' API is more convenient for programming
107+--
108+-- Unfolds can be easily converted to streams using 'Stream.unfold', however,
109+-- vice versa is not possible. To provide a familiar analogy, 'Unfold' is to
110+-- 'Stream' as 'Applicative' is to 'Monad'.
111+--
112+-- To demonstrate the efficiency of unfolds, the nested loop example in the
113+-- previous section can be implemented with concatMap or Monad instance of
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+--
93156module Streamly.Data.Unfold
94157 (
95158 -- * Unfold Type
src/Streamly/Internal/FileSystem/Dir.hs+1−1
@@ -223,7 +223,7 @@ read =
223223{-# INLINE readEither #-}
224224readEither :: MonadIO m => Unfold m String (Either String String)
225225readEither =
226− UF.mapMWithInput classify
226+ UF.mapM2 classify
227227 $ UF.filter (\x -> x /= "." && x /= "..")
228228 -- XXX use proper streaming read of the dir
229229 $ UF.lmapM (liftIO . Dir.getDirectoryContents) UF.fromList
test/Streamly/Test/Data/Unfold.hs+4−4
@@ -469,13 +469,13 @@ mapM =
469469 mList = Prelude.map fA list
470470 in testUnfoldMD unf list 0 (length list) mList
471471
472−mapMWithInput :: Property
473−mapMWithInput =
472+mapM2 :: Property
473+mapM2 =
474474 property
475475 $ \f list ->
476476 let fA = applyFun2 f :: [Int] -> Int -> Int
477477 fM x y = modify (+ 1) >> return (fA x y)
478− unf = UF.mapMWithInput fM UF.fromList
478+ unf = UF.mapM2 fM UF.fromList
479479 mList = Prelude.map (fA list) list
480480 in testUnfoldMD unf list 0 (length list) mList
481481
@@ -667,7 +667,7 @@ testTransformation =
667667 -- prop "map" map
668668 prop "postscan" postscan
669669 prop "mapM" mapM
670− prop "mapMWithInput" mapMWithInput
670+ prop "mapM2" mapM2
671671 prop "takeWhileM" takeWhileM
672672 -- prop "takeWhile" takeWhile
673673 prop "take" take