on master help sign in
Update Unfold docs, expose doctest setup
commit019d0c977628eb3061dae7b9b94420c942039ef5 authorHarendra Kumar <harendra@composewell.com> dateSun 12 Mar 2023, 19:10 (4 years ago) parente694d88
5 files changed+244−211
core/src/DocTestDataUnfold.hs+12 core/src/Streamly/Data/Unfold.hs+93−85 core/src/Streamly/Internal/Data/Unfold.hs+8−107 core/src/Streamly/Internal/Data/Unfold/Type.hs+130−19 core/streamly-core.cabal+1
core/src/DocTestDataUnfold.hsnew file+12
@@ -0,0 +1,12 @@
1+{- $setup
2+
3+>>> :m
4+>>> import Streamly.Data.Unfold (Unfold)
5+>>> import qualified Streamly.Data.Fold as Fold
6+>>> import qualified Streamly.Data.Stream as Stream
7+>>> import qualified Streamly.Data.Unfold as Unfold
8+
9+For APIs that have not been released yet.
10+
11+>>> import qualified Streamly.Internal.Data.Unfold as Unfold
12+-}
core/src/Streamly/Data/Unfold.hs+93−85
@@ -1,4 +1,4 @@
1−#include "inline.hs"
1+{-# LANGUAGE CPP #-}
22
33-- |
44-- Module : Streamly.Data.Unfold
@@ -8,13 +8,99 @@
88-- Stability : released
99-- Portability : GHC
1010--
11+-- Fast, composable stream producers with ability to terminate, supporting
12+-- nested stream fusion. Nested stream operations like
13+-- 'Streamly.Data.Stream.concatMap' in the "Streamly.Data.Stream" module do not
14+-- fuse, however, the 'Streamly.Data.Stream.unfoldMany' operation, using the
15+-- 'Unfold' type, is a fully fusible alternative to
16+-- 'Streamly.Data.Stream.concatMap'.
17+--
18+-- Please refer to "Streamly.Internal.Data.Unfold" for more functions that have
19+-- not yet been released.
20+--
21+-- Exception combinators are not exposed, we would like to encourage the use of
22+-- 'Stream' type instead whenever exception handling is required. We can
23+-- consider exposing the unfold exception functions if there is a compelling
24+-- use case to use unfolds instead of stream.
25+
26+module Streamly.Data.Unfold
27+ (
28+ -- * Setup
29+ -- | To execute the code examples provided in this module in ghci, please
30+ -- run the following commands first.
31+ --
32+ -- $setup
33+
34+ -- * Overview
35+ -- $overview
36+
37+ -- * Unfold Type
38+ Unfold
39+
40+ -- * Unfolds
41+ -- One to one correspondence with
42+ -- "Streamly.Internal.Data.Stream.Generate"
43+
44+ -- ** Basic Constructors
45+ , unfoldrM
46+ , unfoldr
47+ , function
48+ , functionM
49+
50+ -- ** Generators
51+ -- | Generate a monadic stream from a seed.
52+ , repeatM
53+ , replicateM
54+ , iterateM
55+
56+ -- ** From Containers
57+ , fromList
58+ , fromListM
59+ , fromStream
60+
61+ -- * Combinators
62+ -- ** Mapping on Input
63+ , lmap
64+ , lmapM
65+
66+ -- ** Mapping on Output
67+ , mapM
68+
69+ -- ** Filtering
70+ , takeWhileM
71+ , takeWhile
72+ , take
73+ , filter
74+ , filterM
75+ , drop
76+ , dropWhile
77+ , dropWhileM
78+
79+ -- ** Zipping
80+ , zipWith
81+
82+ -- ** Cross Product
83+ , crossWith
84+
85+ -- ** Nesting
86+ , many
87+
88+ )
89+where
90+
91+import Prelude hiding
92+ ( concat, map, mapM, takeWhile, take, filter, const, drop, dropWhile
93+ , zipWith
94+ )
95+import Streamly.Internal.Data.Unfold
96+
97+#include "DocTestDataUnfold.hs"
98+
99+-- $overview
100+--
11101-- An 'Unfold' is a source or a producer of a stream of values. It takes a
12102-- seed value as an input and unfolds it into a sequence of values.
13103--
14−-- >>> import qualified Streamly.Data.Fold as Fold
15−-- >>> import qualified Streamly.Data.Unfold as Unfold
16−-- >>> import qualified Streamly.Internal.Data.Stream as Stream
17−--
18104-- For example, the 'fromList' Unfold generates a stream of values from a
19105-- supplied list. Unfolds can be converted to 'Streamly.Internal.Data.Stream'
20106-- using the 'Stream.unfold' operation.
@@ -58,7 +144,7 @@
58144-- Please see "Streamly.Internal.Data.Unfold" for additional @Pre-release@
59145-- functions.
60146--
61−-- = Creating New Unfolds
147+-- == Creating New Unfolds
62148--
63149-- There are many commonly used unfolds provided in this module. However, you
64150-- can always create your own as well. An 'Unfold' is just a data
@@ -95,7 +181,7 @@
95181-- >>> Data.List.unfoldr f [1,2,3]
96182-- [1,2,3]
97183--
98−-- = Unfolds vs. Streams
184+-- == Unfolds vs. Streams
99185--
100186-- The 'Unfold' abstraction for representing streams was introduced in Streamly
101187-- to provide C like performance for nested looping of streams. 'Unfold' and
@@ -140,81 +226,3 @@
140226-- streams are written, it is easy to adapt a stream to an unfold. If you are
141227-- writing an unfold you can convert it to stream for free using
142228-- '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−--
156−module Streamly.Data.Unfold
157− (
158− -- * Unfold Type
159− Unfold
160−
161− -- * Unfolds
162− -- One to one correspondence with
163− -- "Streamly.Internal.Data.Stream.Generate"
164−
165− -- ** Basic Constructors
166− , unfoldrM
167− , unfoldr
168− , function
169− , functionM
170−
171− -- ** Generators
172− -- | Generate a monadic stream from a seed.
173− , repeatM
174− , replicateM
175− , iterateM
176−
177− -- ** From Containers
178− , fromList
179− , fromListM
180− , fromStream
181−
182− -- * Combinators
183− -- ** Mapping on Input
184− , lmap
185− , lmapM
186−
187− -- ** Mapping on Output
188− , mapM
189−
190− -- ** Filtering
191− , takeWhileM
192− , takeWhile
193− , take
194− , filter
195− , filterM
196− , drop
197− , dropWhile
198− , dropWhileM
199−
200− -- ** Zipping
201− , zipWith
202−
203− -- ** Cross Product
204− , crossWith
205−
206− -- ** Nesting
207− , many
208−
209− -- Exception combinators are not exposed, we would like to encourage the
210− -- use of Stream type instead where exception handling is required. We can
211− -- consider exposing the unfold exception functions if there is a
212− -- compelling use case to use unfolds instead of stream.
213− )
214−where
215−
216−import Prelude hiding
217− ( concat, map, mapM, takeWhile, take, filter, const, drop, dropWhile
218− , zipWith
219− )
220−import Streamly.Internal.Data.Unfold
core/src/Streamly/Internal/Data/Unfold.hs+8−107
@@ -1,3 +1,4 @@
1+{-# LANGUAGE CPP #-}
12-- |
23-- Module : Streamly.Internal.Data.Unfold
34-- Copyright : (c) 2019 Composewell Technologies
@@ -6,110 +7,14 @@
67-- Stability : experimental
78-- Portability : GHC
89--
9−-- To run the examples in this module:
10−--
11−-- >>> import qualified Streamly.Data.Fold as Fold
12−-- >>> import qualified Streamly.Internal.Data.Unfold as Unfold
13−--
14−-- = Unfolds and Streams
15−--
16−-- An 'Unfold' type is the same as the direct style 'Stream' type except that
17−-- it uses an inject function to determine the initial state of the stream
18−-- based on an input. A stream is a special case of Unfold when the static
19−-- input is unit or Void.
20−--
21−-- This allows an important optimization to occur in several cases, making the
22−-- 'Unfold' a more efficient abstraction. Consider the 'concatMap' and
23−-- 'unfoldMany' operations, the latter is more efficient. 'concatMap'
24−-- generates a new stream object from each element in the stream by applying
25−-- the supplied function to the element, the stream object includes the "step"
26−-- function as well as the initial "state" of the stream. Since the stream is
27−-- generated dynamically the compiler does not know the step function or the
28−-- state type statically at compile time, therefore, it cannot inline it. On
29−-- the other hand in case of 'unfoldMany' the compiler has visibility into
30−-- the unfold's state generation function, therefore, the compiler knows all
31−-- the types statically and it can inline the inject as well as the step
32−-- functions, generating efficient code. Essentially, the stream is not opaque
33−-- to the consumer in case of unfolds, the consumer knows how to generate the
34−-- stream from a seed using a known "inject" and "step" functions.
35−--
36−-- A Stream is like a data object whereas unfold is like a function. Being
37−-- function like, an Unfold is an instance of 'Category' and 'Arrow' type
38−-- classes.
39−--
40−-- = Unfolds and Folds
41−--
42−-- Streams forcing a closed control flow loop can be categorized under
43−-- two types, unfolds and folds, both of these are duals of each other.
44−--
45−-- Unfold streams are really generators of a sequence of elements, we can also
46−-- call them pull style streams. These are lazy producers of streams. On each
47−-- evaluation the producer generates the next element. A consumer can
48−-- therefore pull elements from the stream whenever it wants to. A stream
49−-- consumer can multiplex pull streams by pulling elements from the chosen
50−-- streams, therefore, pull streams allow merging or multiplexing. On the
51−-- other hand, with this representation we cannot split or demultiplex a
52−-- stream. So really these are stream sources that can be generated from a
53−-- seed and can be merged or zipped into a single stream.
54−--
55−-- The dual of Unfolds are Folds. Folds can also be called as push style
56−-- streams or reducers. These are strict consumers of streams. We keep pushing
57−-- elements to a fold and we can extract the result at any point. A driver can
58−-- choose which fold to push to and can also push the same element to multiple
59−-- folds. Therefore, folds allow splitting or demultiplexing a stream. On the
60−-- other hand, we cannot merge streams using this representation. So really
61−-- these are stream consumers that reduce the stream to a single value, these
62−-- consumers can be composed such that a stream can be split over multiple
63−-- consumers.
64−--
65−-- Performance:
66−--
67−-- Composing a tree or graph of computations with unfolds can be much more
68−-- efficient compared to composing with the Monad instance. The reason is that
69−-- unfolds allow the compiler to statically know the state and optimize it
70−-- using stream fusion whereas it is not possible with the monad bind because
71−-- the state is determined dynamically.
72−--
73−-- Reader:
74−--
75−-- An unfold acts as a reader (see 'Reader' monad). The input to an unfold acts
76−-- as the read-only environment. The environment can be extracted using the
77−-- 'identity' unfold (equivalent to 'ask') and transformed using 'lmap'.
78−
79−-- Open control flow style streams can also have two representations. StreamK
80−-- is a producer style representation. We can also have a consumer style
81−-- representation. We can use that for composable folds in StreamK
82−-- representation.
83−--
84−
85−-- = Performance Notes
86−--
87−-- 'Unfold' representation is more efficient than using streams when combining
88−-- streams. 'Unfold' type allows multiple unfold actions to be composed into a
89−-- single unfold function in an efficient manner by enabling the compiler to
90−-- perform stream fusion optimization.
91−-- @Unfold m a b@ can be considered roughly equivalent to an action @a -> t m
92−-- b@ (where @t@ is a stream type). Instead of using an 'Unfold' one could just
93−-- use a function of the shape @a -> t m b@. However, working with stream types
94−-- like t'Streamly.SerialT' does not allow the compiler to perform stream fusion
95−-- optimization when merging, appending or concatenating multiple streams.
96−-- Even though stream based combinator have excellent performance, they are
97−-- much less efficient when compared to combinators using 'Unfold'. For
98−-- example, the 'Streamly.Data.Stream.concatMap' combinator which uses @a -> t m b@
99−-- (where @t@ is a stream type) to generate streams is much less efficient
100−-- compared to 'Streamly.Data.Stream.unfoldMany'.
101−--
102−-- On the other hand, transformation operations on stream types are as
103−-- efficient as transformations on 'Unfold'.
104−--
105−-- We should note that in some cases working with stream types may be more
106−-- convenient compared to working with the 'Unfold' type. However, if extra
107−-- performance boost is important then 'Unfold' based composition should be
108−-- preferred compared to stream based composition when merging or concatenating
109−-- streams.
110−
11110module Streamly.Internal.Data.Unfold
11211 (
12+ -- * Setup
13+ -- | To execute the code examples provided in this module in ghci, please
14+ -- run the following commands first.
15+ --
16+ -- $setup
17+
11318 -- * Unfold Type
11419 Step(..)
11520 , Unfold
@@ -303,11 +208,7 @@ import Control.Monad.IO.Class (MonadIO (liftIO))
303208import Foreign (Storable, peek, sizeOf)
304209import Foreign.Ptr
305210
306−-- $setup
307−-- >>> import qualified Streamly.Data.Fold as Fold
308−-- >>> import qualified Streamly.Internal.Data.Unfold as Unfold
309−-- >>> import qualified Streamly.Data.Stream as Stream
310−
211+#include "DocTestDataUnfold.hs"
311212
312213-- | Convert an 'Unfold' into an unfold accepting a tuple as an argument,
313214-- using the argument of the original fold as the second element of tuple and
core/src/Streamly/Internal/Data/Unfold/Type.hs+130−19
@@ -1,3 +1,4 @@
1+{-# LANGUAGE CPP #-}
12-- |
23-- Module : Streamly.Internal.Data.Unfold.Type
34-- Copyright : (c) 2019 Composewell Technologies
@@ -6,14 +7,49 @@
67-- Stability : experimental
78-- Portability : GHC
89--
9−-- To run the examples in this module:
10−--
11−-- >>> import qualified Streamly.Data.Stream as Stream
12−-- >>> import qualified Streamly.Data.Fold as Fold
13−-- >>> import qualified Streamly.Internal.Data.Unfold as Unfold
10+-- An unfold is akin to a reader. It is the streaming equivalent of a reader.
11+-- The argument @a@ is the environment of the reader. That's the reason the
12+-- default unfolds in various modules are named "reader".
13+
14+-- = Performance Notes
15+--
16+-- 'Unfold' representation is more efficient than using streams when combining
17+-- streams. 'Unfold' type allows multiple unfold actions to be composed into a
18+-- single unfold function in an efficient manner by enabling the compiler to
19+-- perform stream fusion optimization.
20+-- @Unfold m a b@ can be considered roughly equivalent to an action @a -> t m
21+-- b@ (where @t@ is a stream type). Instead of using an 'Unfold' one could just
22+-- use a function of the shape @a -> t m b@. However, working with stream types
23+-- like t'Streamly.SerialT' does not allow the compiler to perform stream fusion
24+-- optimization when merging, appending or concatenating multiple streams.
25+-- Even though stream based combinator have excellent performance, they are
26+-- much less efficient when compared to combinators using 'Unfold'. For
27+-- example, the 'Streamly.Data.Stream.concatMap' combinator which uses @a -> t m b@
28+-- (where @t@ is a stream type) to generate streams is much less efficient
29+-- compared to 'Streamly.Data.Stream.unfoldMany'.
30+--
31+-- On the other hand, transformation operations on stream types are as
32+-- efficient as transformations on 'Unfold'.
33+--
34+-- We should note that in some cases working with stream types may be more
35+-- convenient compared to working with the 'Unfold' type. However, if extra
36+-- performance boost is important then 'Unfold' based composition should be
37+-- preferred compared to stream based composition when merging or concatenating
38+-- streams.
1439
1540module Streamly.Internal.Data.Unfold.Type
16− ( Unfold (..)
41+ (
42+ -- * Setup
43+ -- | To execute the code examples provided in this module in ghci, please
44+ -- run the following commands first.
45+ --
46+ -- $setup
47+
48+ -- * General Notes
49+ -- $notes
50+
51+ -- * Type
52+ Unfold (..)
1753
1854 -- * Basic Constructors
1955 , mkUnfoldM
@@ -84,22 +120,96 @@ import Streamly.Internal.Data.Stream.StreamD.Step (Step(..))
84120
85121import Prelude hiding (map, mapM, concatMap, zipWith, takeWhile)
86122
87−-- $setup
88−-- >>> import qualified Streamly.Data.Stream as Stream
89−-- >>> import qualified Streamly.Data.Fold as Fold
90−-- >>> import qualified Streamly.Internal.Data.Unfold as Unfold
123+#include "DocTestDataUnfold.hs"
124+
125+-- $notes
126+--
127+-- What makes streams less efficient is also what makes them more convenient to
128+-- use and powerful. The stream data type (Stream m a) bundles the state along
129+-- with the stream generator function making it opaque, whereas an unfold
130+-- exposes the state (Unfold m s a) to the user. This allows the Unfold to be
131+-- unfolded (inlined) inside a nested loop without having to bundle the state
132+-- and the generator together, the stream state can be saved and passed
133+-- independent of the generator function. On the other hand in a stream type we
134+-- have to bundle the stream state and the generator function together to save
135+-- the stream. This makes it inefficient because it requires boxing and
136+-- constructor allocation. However, this makes streams more convenient as we do
137+-- not need to pass around the state/seed separately.
138+--
139+-- Unfold Type:
140+--
141+-- The order of arguments allows 'Category' and 'Arrow' instances but precludes
142+-- contravariant and contra-applicative.
143+--
144+-- = Unfolds and Streams
145+--
146+-- An 'Unfold' type is the same as the direct style 'Stream' type except that
147+-- it uses an inject function to determine the initial state of the stream
148+-- based on an input. A stream is a special case of Unfold when the static
149+-- input is unit or Void.
150+--
151+-- This allows an important optimization to occur in several cases, making the
152+-- 'Unfold' a more efficient abstraction. Consider the 'concatMap' and
153+-- 'unfoldMany' operations, the latter is more efficient. 'concatMap'
154+-- generates a new stream object from each element in the stream by applying
155+-- the supplied function to the element, the stream object includes the "step"
156+-- function as well as the initial "state" of the stream. Since the stream is
157+-- generated dynamically the compiler does not know the step function or the
158+-- state type statically at compile time, therefore, it cannot inline it. On
159+-- the other hand in case of 'unfoldMany' the compiler has visibility into
160+-- the unfold's state generation function, therefore, the compiler knows all
161+-- the types statically and it can inline the inject as well as the step
162+-- functions, generating efficient code. Essentially, the stream is not opaque
163+-- to the consumer in case of unfolds, the consumer knows how to generate the
164+-- stream from a seed using a known "inject" and "step" functions.
165+--
166+-- A Stream is like a data object whereas unfold is like a function. Being
167+-- function like, an Unfold is an instance of 'Category' and 'Arrow' type
168+-- classes.
169+--
170+-- = Unfolds and Folds
171+--
172+-- Streams forcing a closed control flow loop can be categorized under
173+-- two types, unfolds and folds, both of these are duals of each other.
174+--
175+-- Unfold streams are really generators of a sequence of elements, we can also
176+-- call them pull style streams. These are lazy producers of streams. On each
177+-- evaluation the producer generates the next element. A consumer can
178+-- therefore pull elements from the stream whenever it wants to. A stream
179+-- consumer can multiplex pull streams by pulling elements from the chosen
180+-- streams, therefore, pull streams allow merging or multiplexing. On the
181+-- other hand, with this representation we cannot split or demultiplex a
182+-- stream. So really these are stream sources that can be generated from a
183+-- seed and can be merged or zipped into a single stream.
184+--
185+-- The dual of Unfolds are Folds. Folds can also be called as push style
186+-- streams or reducers. These are strict consumers of streams. We keep pushing
187+-- elements to a fold and we can extract the result at any point. A driver can
188+-- choose which fold to push to and can also push the same element to multiple
189+-- folds. Therefore, folds allow splitting or demultiplexing a stream. On the
190+-- other hand, we cannot merge streams using this representation. So really
191+-- these are stream consumers that reduce the stream to a single value, these
192+-- consumers can be composed such that a stream can be split over multiple
193+-- consumers.
194+--
195+-- Performance:
196+--
197+-- Composing a tree or graph of computations with unfolds can be much more
198+-- efficient compared to composing with the Monad instance. The reason is that
199+-- unfolds allow the compiler to statically know the state and optimize it
200+-- using stream fusion whereas it is not possible with the monad bind because
201+-- the state is determined dynamically.
202+--
203+-- Reader:
204+--
205+-- An unfold acts as a reader (see 'Reader' monad). The input to an unfold acts
206+-- as the read-only environment. The environment can be extracted using the
207+-- 'identity' unfold (equivalent to 'ask') and transformed using 'lmap'.
91208
92209------------------------------------------------------------------------------
93210-- Monadic Unfolds
94211------------------------------------------------------------------------------
95212
96−-- The order of arguments allows 'Category' and 'Arrow' instances but precludes
97−-- contravariant and contra-applicative.
98−--
99−-- An unfold is akin to a reader. It is the streaming equivalent of a reader.
100−-- The argument @a@ is the environment of the reader. That's the reason the
101−-- default unfolds in various modules are called "read".
102−--
103213-- | An @Unfold m a b@ is a generator of a stream of values of type @b@ from a
104214-- seed of type 'a' in 'Monad' @m@.
105215--
@@ -130,7 +240,7 @@ mkUnfoldrM step = Unfold step pure
130240-- The type 'Step' is isomorphic to 'Maybe'. Ideally unfoldrM should be the
131241-- same as mkUnfoldrM, this is for compatibility with traditional Maybe based
132242-- unfold step functions.
133−--
243+
134244-- | Build a stream by unfolding a /monadic/ step function starting from a seed.
135245-- The step function returns the next element in the stream and the next seed
136246-- value. When it is done it returns 'Nothing' and the stream ends.
@@ -369,6 +479,7 @@ instance Functor m => Functor (Unfold m a) where
369479------------------------------------------------------------------------------
370480
371481-- XXX Shouldn't this be Unfold m (m a) a ?
482+
372483-- | The unfold discards its input and generates a function stream using the
373484-- supplied monadic action.
374485--
@@ -386,7 +497,7 @@ fromEffect m = Unfold step inject
386497
387498-- XXX Shouldn't this be Unfold m a a ? Which is identity. Should this function
388499-- even exist for Unfolds. Should we have applicative/Monad for unfolds?
389−--
500+
390501-- | Discards the unfold input and always returns the argument of 'fromPure'.
391502--
392503-- > fromPure = fromEffect . pure
core/streamly-core.cabal+1
@@ -45,6 +45,7 @@ extra-source-files:
4545 src/DocTestDataParser.hs
4646 src/DocTestDataStream.hs
4747 src/DocTestDataStreamK.hs
48+ src/DocTestDataUnfold.hs
4849
4950 -- This is duplicated
5051 src/Streamly/Internal/Data/Array/ArrayMacros.h