on d29030f help sign in
231 lines 7.6 kB history raw
1{-# LANGUAGE CPP #-}
3-- |
4-- Module : Streamly.Data.Unfold
5-- Copyright : (c) 2019 Composewell Technologies
6-- License : BSD3
7-- Maintainer : streamly@composewell.com
8-- Stability : released
9-- Portability : GHC
10--
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.
26module 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
34 -- * Overview
35 -- $overview
37 -- * Unfold Type
38 Unfold
40 -- * Unfolds
41 -- One to one correspondence with
42 -- "Streamly.Internal.Data.Stream.Generate"
44 -- ** Basic Constructors
45 , unfoldrM
46 , unfoldr
47 , function
48 , functionM
50 -- ** Generators
51 -- | Generate a monadic stream from a seed.
52 , repeatM
53 , replicateM
54 , iterateM
56 -- ** Enumeration
57 , Enumerable (..)
59 -- ** From Containers
60 , fromList
61 , fromListM
62 , fromStream
64 -- * Combinators
65 -- ** Mapping on Input
66 , lmap
67 , lmapM
69 -- ** Mapping on Output
70 , mapM
72 -- ** Filtering
73 , takeWhileM
74 , takeWhile
75 , take
76 , filter
77 , filterM
78 , drop
79 , dropWhile
80 , dropWhileM
82 -- ** Zipping
83 , zipWith
85 -- ** Cross Product
86 , crossWith
88 -- ** Nesting
89 , many
91 )
92where
94import Prelude hiding
95 ( concat, map, mapM, takeWhile, take, filter, const, drop, dropWhile
96 , zipWith
97 )
98import Streamly.Internal.Data.Unfold
100#include "DocTestDataUnfold.hs"
102-- $overview
103--
104-- An 'Unfold' is a source or a producer of a stream of values. It takes a
105-- seed value as an input and unfolds it into a sequence of values.
106--
107-- For example, the 'fromList' Unfold generates a stream of values from a
108-- supplied list. Unfolds can be converted to 'Streamly.Internal.Data.Stream'
109-- using the 'Stream.unfold' operation.
110--
111-- >>> stream = Stream.unfold Unfold.fromList [1..100]
112-- >>> Stream.fold Fold.sum stream
113-- 5050
114--
115-- The input seed of an unfold can be transformed using 'lmap':
116--
117-- >>> u = Unfold.lmap (fmap (+1)) Unfold.fromList
118-- >>> Stream.fold Fold.toList $ Stream.unfold u [1..5]
119-- [2,3,4,5,6]
120--
121-- Output stream of an 'Unfold' can be transformed using transformation
122-- combinators. For example, to retain only the first two elements of an
123-- unfold:
124--
125-- >>> u = Unfold.take 2 Unfold.fromList
126-- >>> Stream.fold Fold.toList $ Stream.unfold u [1..100]
127-- [1,2]
128--
129-- Unfolds can be nested efficiently. For example, to implement nested looping:
130--
131-- >>> u1 = Unfold.lmap fst Unfold.fromList
132-- >>> u2 = Unfold.lmap snd Unfold.fromList
133-- >>> u = Unfold.crossWith (,) u1 u2
134-- >>> Stream.fold Fold.toList $ Stream.unfold u ([1,2,3], [4,5,6])
135-- [(1,4),(1,5),(1,6),(2,4),(2,5),(2,6),(3,4),(3,5),(3,6)]
136--
137-- 'Unfold' @u1@ generates a stream from the first list in the input tuple,
138-- @u2@ generates another stream from the second list. The combines 'Unfold'
139-- @u@ nests the two streams i.e. for each element in first stream, for each
140-- element in second stream apply the supplied function (i.e. @(,)@) to the
141-- pair of elements.
142--
143-- This is the equivalent of the nested looping construct from imperative
144-- languages, also known as the cross product of two streams in functional
145-- parlance.
146--
147-- Please see "Streamly.Internal.Data.Unfold" for additional @Pre-release@
148-- functions.
149--
150-- == Creating New Unfolds
151--
152-- There are many commonly used unfolds provided in this module. However, you
153-- can always create your own as well. An 'Unfold' is just a data
154-- representation of a stream generator function. It consists of an @inject@
155-- function which covnerts the supplied seed into an internal state of the
156-- unfold, and a @step@ function which takes the state and generates the next
157-- output in the stream. For those familiar with the list "Data.List.unfoldr"
158-- function, this is a data representation of the same.
159--
160-- Smart constructor functions are provided in this module for constructing new
161-- 'Unfolds'. For example, you can use the 'Unfold.unfoldr' constructor to
162-- create an 'Unfold' from a pure step function, unfoldr uses 'id' as the
163-- @inject@ function.
164--
165-- Let's define a simple pure step function:
166--
167-- >>> :{
168-- f [] = Nothing
169-- f (x:xs) = Just (x, xs)
170-- :}
171--
172-- Create an 'Unfold' from the step function:
173--
174-- >>> u = Unfold.unfoldr f
175--
176-- Run the 'Unfold':
177--
178-- >>> Stream.fold Fold.toList $ Stream.unfold u [1,2,3]
179-- [1,2,3]
180--
181-- The 'Unfold.unfoldr' smart constructor is essentially the same as the list
182-- "Data.List.unfoldr" function. We can use the same step function in both::
183--
184-- >>> Data.List.unfoldr f [1,2,3]
185-- [1,2,3]
186--
187-- == Unfolds vs. Streams
188--
189-- The 'Unfold' abstraction for representing streams was introduced in Streamly
190-- to provide C like performance for nested looping of streams. 'Unfold' and
191-- 'Stream' abstractions are similar with the following differences:
192--
193-- * 'Stream' is less efficient than 'Unfold' for nesting.
194-- * 'Stream' is more powerful than 'Unfold'.
195-- * 'Stream' API is more convenient for programming
196--
197-- Unfolds can be easily converted to streams using 'Stream.unfold', however,
198-- vice versa is not possible. To provide a familiar analogy, 'Unfold' is to
199-- 'Stream' as 'Applicative' is to 'Monad'.
200--
201-- To demonstrate the efficiency of unfolds, the nested loop example in the
202-- previous section can be implemented with concatMap or Monad instance of
203-- streams as follows:
204--
205-- @
206-- do
207-- x <- Stream.unfold Unfold.fromList [1,2,3]
208-- y <- Stream.unfold Unfold.fromList [4,5,6]
209-- return (x, y)
210-- @
211--
212-- As you can see, this is more convenient to write than using the 'crossWith'
213-- unfold combinator. However, this turns out to be many times slower than the
214-- unfold implementation. The Unfold version is equivalent in performance to
215-- the C implementation of the same nested loop. Similarly, unfolds can be
216-- nested with streams using the 'unfoldMany' combinator which is a much more
217-- efficient alternative to the 'concatMap' operation.
218--
219-- Streams use a hybrid implementation approach using direct style as well as
220-- CPS. Unfolds do not use CPS, therefore, lack the power that is afforded to
221-- streams by CPS. The CPS implementation allows infinitely scalable @cons@ and
222-- @append@ operations in streams. It is also used to implement concurrency in
223-- streams.
224--
225-- To summarize, unfolds are a high performance solution to the nesting
226-- problem. Since streams provide a more palatable API for programming, work
227-- with streams unless you need unfolds for better performance in nesting
228-- situations. There is little difference in the way in which unfolds and
229-- streams are written, it is easy to adapt a stream to an unfold. If you are
230-- writing an unfold you can convert it to stream for free using
231-- 'Stream.unfold'.