-- | -- Module : Streamly.Internal.Data.Stream.Async -- Copyright : (c) 2017 Composewell Technologies -- License : BSD-3-Clause -- Maintainer : streamly@composewell.com -- Stability : experimental -- Portability : GHC -- -- Non-parallelizable stream combinators like unfoldrM, iterateM etc. can be -- evaluated concurrently with the stream consumer by using `eval`. -- Parallelizable combinators like repeatM, replicateM can generate the stream -- concurrently using 'concatMap'. -- Single effects related functionality can be moved to -- Data.Async/Control.Async. -- Common Channel functionality to Data.Channel. -- Stream channel to Data.Stream.Channel. module Streamly.Internal.Data.Stream.Async ( MonadAsync -- * Configuration , Config , maxThreads , maxBuffer -- maxYields , Rate(..) , rate , avgRate , minRate , maxRate , constRate -- * Combinators -- | Stream combinators using Async channel -- XXX Move to parallel module , eval , evalWith , mapM , mapMWith , sequence , sequenceWith -- XXX experimental binary ops, move to parallel? , append , appendWith , apply , concatList , concat , concatWith , concatMap , concatMapWith ) where import Streamly.Internal.Control.Concurrent (MonadAsync) import Streamly.Internal.Data.Stream.Type (Stream) import qualified Streamly.Internal.Data.Stream as Stream import Streamly.Internal.Data.Stream.Async.Channel import Prelude hiding (mapM, sequence, concat, concatMap) -- $setup -- >>> :m -- >>> import qualified Streamly.Data.Stream as Stream -- >>> import qualified Streamly.Internal.Data.Stream.Async as Async ------------------------------------------------------------------------------- -- Useful operations ------------------------------------------------------------------------------- -- | Like 'eval' but can specify a config modifier to change the concurrent -- channel parameters. {-# INLINE evalWith #-} evalWith :: MonadAsync m => (Config -> Config) -> Stream m a -> Stream m a evalWith cfg stream = -- Stream.fromStreamD $ evalWithD cfg $ Stream.toStreamD stream Stream.fromStreamK $ evalWithK cfg $ Stream.toStreamK stream -- | Evaluate a stream asynchronously using a channel and serve the consumer -- from the evaluation buffer. -- -- >>> eval = Async.evalWith id -- {-# INLINE eval #-} eval :: MonadAsync m => Stream m a -> Stream m a eval = evalWith id ------------------------------------------------------------------------------- -- Concurrent combinators for "Stream" type ------------------------------------------------------------------------------- -- | Like 'append' but with a Config modifier. {-# INLINE appendWith #-} appendWith :: MonadAsync m => (Config -> Config) -> Stream m a -> Stream m a -> Stream m a appendWith modifier stream1 stream2 = Stream.fromStreamK $ appendWithK modifier (Stream.toStreamK stream1) (Stream.toStreamK stream2) -- | Binary operation to evaluate two streams concurrently prioritizing the -- left stream. -- -- If you want to combine more than two streams you almost always want the -- 'concat' operation instead. The performance of this operation degrades -- rapidly when more streams are combined as each operation adds one more -- concurrent channel. On the other hand, 'concat' uses a single channel for -- all streams. However, with this operation you can precisely control the -- scheduling by creating arbitrary shape expression trees. -- -- >>> append = Async.appendWith id -- {-# INLINE append #-} append :: MonadAsync m => Stream m a -> Stream m a -> Stream m a append = appendWith id -- concatMapWith modifier f stream = concatWith modifier $ fmap f stream -- | Like 'concatMap' but we can also specify the concurrent channel's -- configuration parameters using Config modifiers. -- {-# INLINE concatMapWith #-} concatMapWith :: MonadAsync m => (Config -> Config) -> (a -> Stream m b) -> Stream m a -> Stream m b concatMapWith modifier f stream = Stream.fromStreamK $ concatMapWithK modifier (Stream.toStreamK . f) (Stream.toStreamK stream) -- | Map each element of the input to a stream and then concurrently evaluate -- and concatenate the resulting streams. -- -- >>> concatMap = Async.concatMapWith id -- {-# INLINE concatMap #-} concatMap :: MonadAsync m => (a -> Stream m b) -> Stream m a -> Stream m b concatMap = concatMapWith id -- | Like 'concat' but we can also specify the concurrent channel's -- configuration parameters using Config modifiers. -- -- >>> concatWith modifier = Async.concatMapWith modifier id -- {-# INLINE concatWith #-} concatWith :: MonadAsync m => (Config -> Config) -> Stream m (Stream m a) -> Stream m a concatWith modifier = concatMapWith modifier id -- | Evaluate the streams in the input stream concurrently and combine them. -- -- >>> concat = Async.concatWith id -- {-# INLINE concat #-} concat :: MonadAsync m => Stream m (Stream m a) -> Stream m a concat = concatWith id -- | Like 'concat' but works on a list of streams. -- -- >>> concatList = Async.concat . Stream.fromList -- {-# INLINE concatList #-} concatList :: MonadAsync m => [Stream m a] -> Stream m a concatList = concat . Stream.fromList {-# INLINE apply #-} {-# SPECIALIZE apply :: Stream IO (a -> b) -> Stream IO a -> Stream IO b #-} apply :: MonadAsync m => Stream m (a -> b) -> Stream m a -> Stream m b apply stream1 stream2 = concatMap (\g -> concatMap (pure . g) stream2) stream1 -- | -- >>> mapMWith modifier f = Async.concatMapWith modifier (Stream.fromEffect . f) -- {-# INLINE mapMWith #-} mapMWith :: MonadAsync m => (Config -> Config) -> (a -> m b) -> Stream m a -> Stream m b mapMWith modifier f = concatMapWith modifier (Stream.fromEffect . f) -- | -- >>> mapM = Async.mapMWith id {-# INLINE mapM #-} mapM :: MonadAsync m => (a -> m b) -> Stream m a -> Stream m b mapM = mapMWith id -- | -- >>> sequenceWith modifier = Async.mapMWith modifier id -- {-# INLINE sequenceWith #-} sequenceWith :: MonadAsync m => (Config -> Config) -> Stream m (m a) -> Stream m a sequenceWith modifier = mapMWith modifier id -- | -- >>> sequence = Async.sequenceWith id -- {-# INLINE sequence #-} sequence :: MonadAsync m => Stream m (m a) -> Stream m a sequence = sequenceWith id