2-- Module : Streamly.Internal.Data.Stream.Async
3-- Copyright : (c) 2017 Composewell Technologies
4-- License : BSD-3-Clause
5-- Maintainer : streamly@composewell.com
6-- Stability : experimental
9-- Non-parallelizable stream combinators like unfoldrM, iterateM etc. can be
10-- evaluated concurrently with the stream consumer by using `eval`.
11-- Parallelizable combinators like repeatM, replicateM can generate the stream
12-- concurrently using 'concatMap'.
14-- Single effects related functionality can be moved to
15-- Data.Async/Control.Async.
16-- Common Channel functionality to Data.Channel.
17-- Stream channel to Data.Stream.Channel.
19module Streamly.Internal.Data.Stream.Async
42 -- | Stream combinators using Async channel
44 -- XXX Move to parallel module
53 -- XXX experimental binary ops, move to parallel?
67 , concatMapInterleaveWith
71import Streamly.Internal.Control.Concurrent (MonadAsync)
72import Streamly.Internal.Data.Stream.Type (Stream)
74import qualified Streamly.Internal.Data.Stream as Stream
76import Streamly.Internal.Data.Stream.Async.Channel
77import Prelude hiding (mapM, sequence, concat, concatMap)
81-- Imports for example snippets in this module.
84-- >>> import Control.Concurrent (threadDelay)
85-- >>> import qualified Streamly.Data.Array.Unboxed as Array
86-- >>> import qualified Streamly.Data.Fold as Fold
87-- >>> import qualified Streamly.Data.Parser as Parser
88-- >>> import qualified Streamly.Data.Stream as Stream
89-- >>> import qualified Streamly.Internal.Data.Stream.Async as Async
90-- >>> import Prelude hiding (concatMap, concat)
93-- threadDelay (n * 1000000) -- sleep for n seconds
94-- putStrLn (show n ++ " sec") -- print "n sec"
98-------------------------------------------------------------------------------
100-------------------------------------------------------------------------------
102-- | Like 'eval' but can specify a config modifier to change the concurrent
103-- channel parameters.
104{-# INLINE evalWith #-}
105evalWith :: MonadAsync m => (Config -> Config) -> Stream m a -> Stream m a
107 -- Stream.fromStreamD $ evalWithD cfg $ Stream.toStreamD stream
108 Stream.fromStreamK $ evalWithK cfg $ Stream.toStreamK stream
110-- | Evaluate a stream asynchronously using a channel and serve the consumer
111-- from the evaluation buffer.
113-- >>> eval = Async.evalWith id
116eval :: MonadAsync m => Stream m a -> Stream m a
119-------------------------------------------------------------------------------
120-- Concurrent combinators for "Stream" type
121-------------------------------------------------------------------------------
123-- | Like 'append' but with a Config modifier.
124{-# INLINE appendWith #-}
125appendWith :: MonadAsync m =>
126 (Config -> Config) -> Stream m a -> Stream m a -> Stream m a
127appendWith modifier stream1 stream2 =
130 modifier (Stream.toStreamK stream1) (Stream.toStreamK stream2)
132-- | Like 'interleave' but with a Config modifier.
133{-# INLINE interleaveWith #-}
134interleaveWith :: MonadAsync m =>
135 (Config -> Config) -> Stream m a -> Stream m a -> Stream m a
136interleaveWith modifier stream1 stream2 =
139 modifier (Stream.toStreamK stream1) (Stream.toStreamK stream2)
141-- | Binary operation to evaluate two streams concurrently prioritizing the
144-- If you want to combine more than two streams you almost always want the
145-- 'concat' operation instead. The performance of this operation degrades
146-- rapidly when more streams are combined as each operation adds one more
147-- concurrent channel. On the other hand, 'concat' uses a single channel for
148-- all streams. However, with this operation you can precisely control the
149-- scheduling by creating arbitrary shape expression trees.
151-- >>> append = Async.appendWith id
153-- The following code finishes in 4 seconds:
155-- >>> stream1 = Stream.fromEffect (delay 4)
156-- >>> stream2 = Stream.fromEffect (delay 2)
157-- >>> Stream.fold Fold.toList $ stream1 `Async.append` stream2
163append :: MonadAsync m => Stream m a -> Stream m a -> Stream m a
164append = appendWith id
166-- | Like 'append' but interleaves the streams fairly instead of prioritizing
167-- the left stream. This schedules all streams in a round robin fashion over
168-- limited number of threads.
170-- >>> interleave = Async.interleaveWith id
172{-# INLINE interleave #-}
173interleave :: MonadAsync m => Stream m a -> Stream m a -> Stream m a
174interleave = interleaveWith id
176-- concatMapWith modifier f stream = concatWith modifier $ fmap f stream
178-- | Like 'concatMap' but we can also specify the concurrent channel's
179-- configuration parameters using Config modifiers.
181{-# INLINE concatMapWith #-}
182concatMapWith :: MonadAsync m =>
183 (Config -> Config) -> (a -> Stream m b) -> Stream m a -> Stream m b
184concatMapWith modifier f stream =
187 modifier (Stream.toStreamK . f) (Stream.toStreamK stream)
189-- | Like 'concatMapInterleave' but we can also specify the concurrent
190-- channel's configuration parameters using Config modifiers.
192{-# INLINE concatMapInterleaveWith #-}
193concatMapInterleaveWith :: MonadAsync m =>
194 (Config -> Config) -> (a -> Stream m b) -> Stream m a -> Stream m b
195concatMapInterleaveWith modifier f stream =
197 $ concatMapInterleaveWithK
198 modifier (Stream.toStreamK . f) (Stream.toStreamK stream)
200-- | Map each element of the input to a stream and then concurrently evaluate
201-- and concatenate the resulting streams. Multiple streams may be evaluated
202-- concurrently but earlier streams are perferred. Output from the streams are
203-- used as they arrive.
205-- >>> concatMap = Async.concatMapWith id
207-- >>> f cfg xs = Stream.fold Fold.toList $ Async.concatMapWith cfg id $ Stream.fromList xs
209-- The following streams finish in 4 seconds:
211-- >>> stream1 = Stream.fromEffect (delay 4)
212-- >>> stream2 = Stream.fromEffect (delay 2)
213-- >>> stream3 = Stream.fromEffect (delay 1)
214-- >>> f id [stream1, stream2, stream3]
220-- Limiting threads to 2 schedules the third stream only after one of the first
221-- two has finished, releasing a thread:
223-- >>> f (Async.maxThreads 2) [stream1, stream2, stream3]
227-- When used with a Single thread it behaves like serial concatMap:
229-- >>> f (Async.maxThreads 1) [stream1, stream2, stream3]
233-- >>> stream1 = Stream.fromList [1,2,3]
234-- >>> stream2 = Stream.fromList [4,5,6]
235-- >>> f (Async.maxThreads 1) [stream1, stream2]
238{-# INLINE concatMap #-}
239concatMap :: MonadAsync m => (a -> Stream m b) -> Stream m a -> Stream m b
240concatMap = concatMapWith id
242-- | Map each element of the input to a stream and then concurrently evaluate
243-- and interleave the resulting streams. Unlike 'concatMap' which prefers to
244-- evaluate the earlier stream first, this schedules all streams in a round
245-- robin fashion over the available threads.
247-- >>> concatMapInterleave = Async.concatMapInterleaveWith id
249-- When used with a single thread it behaves like serial interleaving:
251-- >>> f cfg xs = Stream.fold Fold.toList $ Async.concatMapInterleaveWith cfg id $ Stream.fromList xs
253-- >>> stream1 = Stream.fromList [1,2,3]
254-- >>> stream2 = Stream.fromList [4,5,6]
255-- >>> f (Async.maxThreads 1) [stream1, stream2]
258-- /Works only on finite number of streams/
260{-# INLINE concatMapInterleave #-}
261concatMapInterleave :: MonadAsync m => (a -> Stream m b) -> Stream m a -> Stream m b
262concatMapInterleave = concatMapInterleaveWith id
264-- | Like 'concat' but we can also specify the concurrent channel's
265-- configuration parameters using Config modifiers.
267-- >>> concatWith modifier = Async.concatMapWith modifier id
269{-# INLINE concatWith #-}
270concatWith :: MonadAsync m =>
271 (Config -> Config) -> Stream m (Stream m a) -> Stream m a
272concatWith modifier = concatMapWith modifier id
274-- | Evaluate the streams in the input stream concurrently and combine them.
276-- >>> concat = Async.concatWith id
279concat :: MonadAsync m => Stream m (Stream m a) -> Stream m a
280concat = concatWith id
282-- | Like 'concat' but works on a list of streams.
284-- >>> concatList = Async.concat . Stream.fromList
286{-# INLINE concatList #-}
287concatList :: MonadAsync m => [Stream m a] -> Stream m a
288concatList = concat . Stream.fromList
290{-# INLINE applyWith #-}
291{-# SPECIALIZE applyWith ::
292 (Config -> Config) -> Stream IO (a -> b) -> Stream IO a -> Stream IO b #-}
293applyWith :: MonadAsync m =>
294 (Config -> Config) -> Stream m (a -> b) -> Stream m a -> Stream m b
295applyWith modifier stream1 stream2 =
298 (\g -> concatMapWith modifier (pure . g) stream2)
302{-# SPECIALIZE apply :: Stream IO (a -> b) -> Stream IO a -> Stream IO b #-}
303apply :: MonadAsync m => Stream m (a -> b) -> Stream m a -> Stream m b
307-- >>> mapMWith modifier f = Async.concatMapWith modifier (Stream.fromEffect . f)
309{-# INLINE mapMWith #-}
310mapMWith :: MonadAsync m =>
311 (Config -> Config) -> (a -> m b) -> Stream m a -> Stream m b
312mapMWith modifier f = concatMapWith modifier (Stream.fromEffect . f)
315-- >>> mapM = Async.mapMWith id
317mapM :: MonadAsync m => (a -> m b) -> Stream m a -> Stream m b
321-- >>> sequenceWith modifier = Async.mapMWith modifier id
323{-# INLINE sequenceWith #-}
324sequenceWith :: MonadAsync m =>
325 (Config -> Config) -> Stream m (m a) -> Stream m a
326sequenceWith modifier = mapMWith modifier id
329-- >>> sequence = Async.sequenceWith id
331{-# INLINE sequence #-}
332sequence :: MonadAsync m =>
333 Stream m (m a) -> Stream m a
334sequence = sequenceWith id
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list