on master help sign in
Add overall docs for unfold module
7a1463dHarendra Kumar
Changelog.md+1
@@ -36,6 +36,7 @@
36* New encoding/decoding routines, `encodeUtf8'`, `encodeLatin1'`, `decodeUtf8'`,
36* New encoding/decoding routines, `encodeUtf8'`, `encodeLatin1'`, `decodeUtf8'`,
37 are added, these routines fail when they encounter any invalid characters.
37 are added, these routines fail when they encounter any invalid characters.
38* Several new functions added to `Streamly.Data.Fold`
38* Several new functions added to `Streamly.Data.Fold`
39+* Several Unfold routines and combinators added to `Streamly.Data.Unfold`.
39
40
40### Bug Fixes
41### Bug Fixes
41
42
src/Streamly/Data/Unfold.hs+81−53
@@ -8,57 +8,93 @@
8-- Stability : experimental
8-- Stability : experimental
9-- Portability : GHC
9-- Portability : GHC
10--
10--
11−-- 'Unfold' type represents an effectful action that generates a stream of
11+-- An 'Unfold' is a source or a producer of a stream of values. It takes a
12−-- values from a single starting value often called a seed value. Values can be
12+-- seed value as an input and unfolds it into a sequence of values.
13−-- generated and /pulled/ from the 'Unfold' one at a time. It can also be
13+--
14−-- called a producer or a source of stream. It is a data representation of the
14+-- >>> import qualified Streamly.Data.Fold as Fold
15−-- standard 'Streamly.Prelude.unfoldr' function. An 'Unfold' can be converted
15+-- >>> import qualified Streamly.Data.Unfold as Unfold
16−-- into a stream type using 'Streamly.Prelude.unfold' by supplying the seed.
16+-- >>> import qualified Streamly.Prelude as Stream
17−--
17+--
18−-- = Performance Notes
18+-- For example, the 'fromList' Unfold generates a stream of values from a
19−--
19+-- supplied list. Unfolds can be converted to 'Streamly.Prelude.SerialT'
20−-- 'Unfold' representation is more efficient than using streams when combining
20+-- stream using the Stream.unfold operation.
21−-- streams. 'Unfold' type allows multiple unfold actions to be composed into a
21+--
22−-- single unfold function in an efficient manner by enabling the compiler to
22+-- >>> stream = Stream.unfold Unfold.fromList [1..100]
23−-- perform stream fusion optimization.
23+-- >>> Stream.sum stream
24−-- @Unfold m a b@ can be considered roughly equivalent to an action @a -> t m
25−-- b@ (where @t@ is a stream type). Instead of using an 'Unfold' one could just
26−-- use a function of the shape @a -> t m b@. However, working with stream types
27−-- like t'Streamly.SerialT' does not allow the compiler to perform stream fusion
28−-- optimization when merging, appending or concatenating multiple streams.
29−-- Even though stream based combinator have excellent performance, they are
30−-- much less efficient when compared to combinators using 'Unfold'. For
31−-- example, the 'Streamly.Prelude.concatMap' combinator which uses @a -> t m b@
32−-- (where @t@ is a stream type) to generate streams is much less efficient
33−-- compared to 'Streamly.Prelude.unfoldMany'.
34−--
24+-- 5050
35−-- On the other hand, transformation operations on stream types are as
25+--
36−-- efficient as transformations on 'Unfold'.
26+-- All the serial stream generation operations in "Streamly.Prelude"
37−--
27+-- can be expressed using unfolds:
38−-- We should note that in some cases working with stream types may be more
39−-- convenient compared to working with the 'Unfold' type. However, if extra
40−-- performance boost is important then 'Unfold' based composition should be
41−-- preferred compared to stream based composition when merging or concatenating
42−-- streams.
43−--
44−-- = Programmer Notes
45−--
28+--
46−-- > import qualified Streamly.Data.Unfold as UF
29+-- > Stream.fromList = Stream.unfold Unfold.fromList [1..100]
47−--
30+--
48−-- More, not yet exposed, unfold combinators can be found in
31+-- Conceptually, an 'Unfold' is just like "Data.List.unfoldr". Let us write a
49−-- "Streamly.Internal.Data.Unfold".
32+-- step function to unfold a list using "Data.List.unfoldr":
50−
33+--
51−-- The stream types (e.g. t'Streamly.SerialT') can be considered as a special
34+-- >>> :{
52−-- case of 'Unfold' with no starting seed.
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':
48+--
49+-- >>> u = Unfold.lmap (fmap (+1)) Unfold.fromList
50+-- >>> Stream.toList $ Stream.unfold u [1..5]
51+-- [2,3,4,5,6]
53--
52--
53+-- 'Unfold' streams can be transformed using transformation combinators. For
54+-- example, to retain only the first two elements of an unfold:
55+--
56+-- >>> u = Unfold.take 2 Unfold.fromList
57+-- >>> Stream.toList $ Stream.unfold u [1..100]
58+-- [1,2]
59+--
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):
63+--
64+-- >>> u1 = Unfold.lmap fst Unfold.fromList
65+-- >>> u2 = Unfold.lmap snd Unfold.fromList
66+-- >>> u = Unfold.crossWith (,) u1 u2
67+-- >>> Stream.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)]
69+--
70+-- Nested loops using unfolds provide C like performance due to complete stream
71+-- fusion.
72+--
73+-- Please see "Streamly.Internal.Data.Unfold" for additional @Pre-release@
74+-- functions.
75+--
76+-- = Unfolds vs. Streams
77+--
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+
54module Streamly.Data.Unfold
93module Streamly.Data.Unfold
55 (
94 (
56 -- * Unfold Type
95 -- * Unfold Type
57 Unfold
96 Unfold
58
97
59− -- * Folding
60− , fold
61−
62 -- * Unfolds
98 -- * Unfolds
63 -- One to one correspondence with
99 -- One to one correspondence with
64 -- "Streamly.Internal.Data.Stream.IsStream.Generate"
100 -- "Streamly.Internal.Data.Stream.IsStream.Generate"
@@ -106,14 +142,6 @@ module Streamly.Data.Unfold
106
142
107 -- ** Nesting
143 -- ** Nesting
108 , many
144 , many
109−
110− -- ** Resource Management
111− , finally
112− , bracket
113−
114− -- ** Exceptions
115− , onException
116− , handle
117 )
145 )
118where
146where
119
147
src/Streamly/Internal/Data/Unfold.hs+47−5
@@ -8,6 +8,11 @@
8-- Stability : experimental
8-- Stability : experimental
9-- Portability : GHC
9-- Portability : GHC
10--
10--
11+-- To run the examples in this module:
12+--
13+-- >>> import qualified Streamly.Data.Fold as Fold
14+-- >>> import qualified Streamly.Internal.Data.Unfold as Unfold
15+--
11-- = Unfolds and Streams
16-- = Unfolds and Streams
12--
17--
13-- An 'Unfold' type is the same as the direct style 'Stream' type except that
18-- An 'Unfold' type is the same as the direct style 'Stream' type except that
@@ -72,6 +77,33 @@
72-- representation. We can use that for composable folds in StreamK
77-- representation. We can use that for composable folds in StreamK
73-- representation.
78-- representation.
74--
79--
80+
81+-- = Performance Notes
82+--
83+-- 'Unfold' representation is more efficient than using streams when combining
84+-- streams. 'Unfold' type allows multiple unfold actions to be composed into a
85+-- single unfold function in an efficient manner by enabling the compiler to
86+-- perform stream fusion optimization.
87+-- @Unfold m a b@ can be considered roughly equivalent to an action @a -> t m
88+-- b@ (where @t@ is a stream type). Instead of using an 'Unfold' one could just
89+-- use a function of the shape @a -> t m b@. However, working with stream types
90+-- like t'Streamly.SerialT' does not allow the compiler to perform stream fusion
91+-- optimization when merging, appending or concatenating multiple streams.
92+-- Even though stream based combinator have excellent performance, they are
93+-- much less efficient when compared to combinators using 'Unfold'. For
94+-- example, the 'Streamly.Prelude.concatMap' combinator which uses @a -> t m b@
95+-- (where @t@ is a stream type) to generate streams is much less efficient
96+-- compared to 'Streamly.Prelude.unfoldMany'.
97+--
98+-- On the other hand, transformation operations on stream types are as
99+-- efficient as transformations on 'Unfold'.
100+--
101+-- We should note that in some cases working with stream types may be more
102+-- convenient compared to working with the 'Unfold' type. However, if extra
103+-- performance boost is important then 'Unfold' based composition should be
104+-- preferred compared to stream based composition when merging or concatenating
105+-- streams.
106+
75module Streamly.Internal.Data.Unfold
107module Streamly.Internal.Data.Unfold
76 (
108 (
77 -- * Unfold Type
109 -- * Unfold Type
@@ -177,17 +209,19 @@ module Streamly.Internal.Data.Unfold
177 , concatMapM
209 , concatMapM
178 , bind
210 , bind
179
211
180− -- ** Exceptions
212+ -- ** Resource Management
181 , gbracket_
213 , gbracket_
182 , gbracket
214 , gbracket
183 , before
215 , before
184− , after_
185 , after
216 , after
186− , onException
217+ , after_
187− , finally_
188 , finally
218 , finally
189− , bracket_
219+ , finally_
190 , bracket
220 , bracket
221+ , bracket_
222+
223+ -- ** Exceptions
224+ , onException
191 , handle
225 , handle
192 )
226 )
193where
227where
@@ -326,6 +360,9 @@ swap = lmap Tuple.swap
326-- @Fold m b c@, returns a monadic action @a -> m c@ representing the
360-- @Fold m b c@, returns a monadic action @a -> m c@ representing the
327-- application of the fold on the unfolded stream.
361-- application of the fold on the unfolded stream.
328--
362--
363+-- >>> Unfold.fold Fold.sum Unfold.fromList [1..100]
364+-- 5050
365+--
329-- /Pre-release/
366-- /Pre-release/
330--
367--
331{-# INLINE_NORMAL fold #-}
368{-# INLINE_NORMAL fold #-}
@@ -544,6 +581,11 @@ fromIndicesM gen = Unfold step return
544-- Filtering
581-- Filtering
545-------------------------------------------------------------------------------
582-------------------------------------------------------------------------------
546
583
584+-- |
585+-- >>> u = Unfold.take 2 Unfold.fromList
586+-- >>> Unfold.fold Fold.toList u [1..100]
587+-- [1,2]
588+--
547{-# INLINE_NORMAL take #-}
589{-# INLINE_NORMAL take #-}
548take :: Monad m => Int -> Unfold m a b -> Unfold m a b
590take :: Monad m => Int -> Unfold m a b -> Unfold m a b
549take n (Unfold step1 inject1) = Unfold step inject
591take n (Unfold step1 inject1) = Unfold step inject
src/Streamly/Internal/Data/Unfold/Type.hs+25−7
@@ -5,6 +5,12 @@
5-- Maintainer : streamly@composewell.com
5-- Maintainer : streamly@composewell.com
6-- Stability : experimental
6-- Stability : experimental
7-- Portability : GHC
7-- Portability : GHC
8+--
9+-- To run the examples in this module:
10+--
11+-- >>> import qualified Streamly.Prelude as Stream
12+-- >>> import qualified Streamly.Data.Fold as Fold
13+-- >>> import qualified Streamly.Internal.Data.Unfold as Unfold
8
14
9module Streamly.Internal.Data.Unfold.Type
15module Streamly.Internal.Data.Unfold.Type
10 ( Unfold (..)
16 ( Unfold (..)
@@ -57,12 +63,6 @@ import Streamly.Internal.Data.Stream.StreamD.Step (Step(..))
57
63
58import Prelude hiding (const, map, concatMap, zipWith)
64import Prelude hiding (const, map, concatMap, zipWith)
59
65
60−-- $setup
61−-- >>> :m
62−-- >>> import qualified Streamly.Prelude as Stream
63−-- >>> import qualified Streamly.Data.Fold as Fold
64−-- >>> import qualified Streamly.Data.Unfold as Unfold
65−
66------------------------------------------------------------------------------
66------------------------------------------------------------------------------
67-- Monadic Unfolds
67-- Monadic Unfolds
68------------------------------------------------------------------------------
68------------------------------------------------------------------------------
@@ -121,6 +121,14 @@ unfoldrM next = Unfold step pure
121
121
122-- | Like 'unfoldrM' but uses a pure step function.
122-- | Like 'unfoldrM' but uses a pure step function.
123--
123--
124+-- >>> :{
125+-- f [] = Nothing
126+-- f (x:xs) = Just (x, xs)
127+-- :}
128+--
129+-- >>> Unfold.fold Fold.toList (Unfold.unfoldr f) [1,2,3]
130+-- [1,2,3]
131+--
124-- /Pre-release/
132-- /Pre-release/
125--
133--
126{-# INLINE unfoldr #-}
134{-# INLINE unfoldr #-}
@@ -133,6 +141,10 @@ unfoldr step = unfoldrM (pure . step)
133
141
134-- | Map a function on the input argument of the 'Unfold'.
142-- | Map a function on the input argument of the 'Unfold'.
135--
143--
144+-- >>> u = Unfold.lmap (fmap (+1)) Unfold.fromList
145+-- >>> Unfold.fold Fold.toList u [1..5]
146+-- [2,3,4,5,6]
147+--
136-- @
148-- @
137-- lmap f = Unfold.many (Unfold.function f)
149-- lmap f = Unfold.many (Unfold.function f)
138-- @
150-- @
@@ -246,6 +258,12 @@ crossWithM f (Unfold step1 inject1) (Unfold step2 inject2) = Unfold step inject
246--
258--
247-- > crossWith f = crossWithM (\b c -> return $ f b c)
259-- > crossWith f = crossWithM (\b c -> return $ f b c)
248--
260--
261+-- >>> u1 = Unfold.lmap fst Unfold.fromList
262+-- >>> u2 = Unfold.lmap snd Unfold.fromList
263+-- >>> u = Unfold.crossWith (,) u1 u2
264+-- >>> Unfold.fold Fold.toList u ([1,2,3], [4,5,6])
265+-- [(1,4),(1,5),(1,6),(2,4),(2,5),(2,6),(3,4),(3,5),(3,6)]
266+--
249-- /Pre-release/
267-- /Pre-release/
250{-# INLINE crossWith #-}
268{-# INLINE crossWith #-}
251crossWith :: Monad m =>
269crossWith :: Monad m =>
@@ -507,7 +525,7 @@ zipWithM f (Unfold step1 inject1) (Unfold step2 inject2) = Unfold step inject
507-- >>> square = fmap (\x -> x * x) Unfold.fromList
525-- >>> square = fmap (\x -> x * x) Unfold.fromList
508-- >>> cube = fmap (\x -> x * x * x) Unfold.fromList
526-- >>> cube = fmap (\x -> x * x * x) Unfold.fromList
509-- >>> u = Unfold.zipWith (,) square cube
527-- >>> u = Unfold.zipWith (,) square cube
510−-- >>> Unfold.fold u Fold.toList [1..5]
528+-- >>> Unfold.fold Fold.toList u [1..5]
511-- [(1,1),(4,8),(9,27),(16,64),(25,125)]
529-- [(1,1),(4,8),(9,27),(16,64),(25,125)]
512--
530--
513-- > zipWith f = zipWithM (\a b -> return $ f a b)
531-- > zipWith f = zipWithM (\a b -> return $ f a b)