@@ -86,12 +86,16 @@ module Streamly.Internal.Data.Stream.Concurrent
89+import Control.Monad.IO.Class (MonadIO(liftIO))
89−import Streamly.Internal.Control.Concurrent (MonadAsync)
90+import Streamly.Internal.Control.Concurrent (MonadAsync, askRunInIO)
91+import Streamly.Internal.Data.Stream.Channel.Types (concatMapDivK)
90import Streamly.Internal.Data.Stream.Type (Stream)
92import Streamly.Internal.Data.Stream.Type (Stream)
92import qualified Streamly.Internal.Data.Stream as Stream
94import qualified Streamly.Internal.Data.Stream as Stream
95+import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
94import Streamly.Internal.Data.Stream.Concurrent.Channel
97import Streamly.Internal.Data.Stream.Concurrent.Channel
95import Prelude hiding (mapM, sequence, concat, concatMap)
99import Prelude hiding (mapM, sequence, concat, concatMap)
@@ -114,16 +118,38 @@ import Prelude hiding (mapM, sequence, concat, concatMap)
116-------------------------------------------------------------------------------
120-------------------------------------------------------------------------------
117−-- Useful operations
121+-- Evaluating a stream
118-------------------------------------------------------------------------------
122-------------------------------------------------------------------------------
125+{-# INLINE_NORMAL evalWithD #-}
126+evalWithD :: MonadAsync m => (Config -> Config) -> D.Stream m a -> D.Stream m a
127+evalWithD modifier m = D.Stream step Nothing
131+ chan <- newChannel modifier
132+ sendFirstWorker chan (D.toStreamK m)
133+ -- XXX should use an unfold to make this efficient
134+ return $ D.Skip $ Just $ fromChannelD chan
136+ step gst (Just (D.UnStream step1 st)) = do
139+ D.Yield a s -> D.Yield a (Just $ D.Stream step1 s)
140+ D.Skip s -> D.Skip (Just $ D.Stream step1 s)
120-- | Like 'eval' but can specify a config modifier to change the concurrent
144-- | Like 'eval' but can specify a config modifier to change the concurrent
121-- channel parameters.
145-- channel parameters.
147+-- > evalWith = withChannel (const id)
122{-# INLINE evalWith #-}
149{-# INLINE evalWith #-}
123evalWith :: MonadAsync m => (Config -> Config) -> Stream m a -> Stream m a
150evalWith :: MonadAsync m => (Config -> Config) -> Stream m a -> Stream m a
124−evalWith cfg stream =
151+evalWith = withChannel (const id)
125 -- Stream.fromStreamD $ evalWithD cfg $ Stream.toStreamD stream
152 -- Stream.fromStreamD $ evalWithD cfg $ Stream.toStreamD stream
126− Stream.fromStreamK $ evalWithK cfg $ Stream.toStreamK stream
128-- | Evaluate a stream asynchronously using a channel and serve the consumer
154-- | Evaluate a stream asynchronously using a channel and serve the consumer
129-- from the evaluation buffer.
155-- from the evaluation buffer.
@@ -135,9 +161,65 @@ eval :: MonadAsync m => Stream m a -> Stream m a
135eval = evalWith id
161eval = evalWith id
137-------------------------------------------------------------------------------
163-------------------------------------------------------------------------------
138−-- Concurrent combinators for "Stream" type
164+-- appending two streams
139-------------------------------------------------------------------------------
165-------------------------------------------------------------------------------
167+{-# INLINE _appendGeneric #-}
168+_appendGeneric :: MonadAsync m =>
169+ ((Config -> Config) -> m (Channel m a))
170+ -> (Config -> Config)
174+_appendGeneric newChan modifier stream1 stream2 = K.concatEffect action
179+ chan <- newChan modifier
180+ let cfg = modifier defaultConfig
181+ done = K.nilM (stopChannel chan)
182+ case getStopWhen cfg of
184+ toChannelK chan stream2
185+ toChannelK chan stream1
187+ toChannelK chan stream2
188+ toChannelK chan (K.serial stream1 done)
190+ toChannelK chan (K.serial stream2 done)
191+ toChannelK chan (K.serial stream1 done)
192+ return $ Stream.toStreamK $ fromChannel chan
194+-- | Create a new channel and add both the streams to it for async evaluation.
195+-- The output stream is the result of the evaluation.
196+{-# INLINE appendWithK #-}
197+appendWithK :: MonadAsync m =>
198+ (Config -> Config) -> K.Stream m a -> K.Stream m a -> K.Stream m a
199+appendWithK modifier stream1 stream2 =
201+ if getOrdered (modifier defaultConfig)
202+ then concatMapWithK modifier id (stream1 `K.cons` K.fromPure stream2)
203+ else _appendGeneric Append.newChannel modifier stream1 stream2
205+ concatMapWithK modifier id (stream1 `K.cons` K.fromPure stream2)
207+-- | Evaluate the first stream in the current thread and add the second stream
208+-- to the supplied channel. This is to be used by a worker thread.
210+-- This can be used with concatMapWith:
213+-- concatMap = K.concatMapWith (_appendWithChanK chan) f stream
216+{-# INLINE _appendWithChanK #-}
217+_appendWithChanK :: MonadAsync m =>
218+ Channel m a -> K.Stream m a -> K.Stream m a -> K.Stream m a
219+_appendWithChanK chan stream1 stream2 =
220+ K.before (toChannelK chan stream2) stream1
141-- | Like 'append' but with a Config modifier.
223-- | Like 'append' but with a Config modifier.
142{-# INLINE combineWith #-}
224{-# INLINE combineWith #-}
143combineWith :: MonadAsync m =>
225combineWith :: MonadAsync m =>
@@ -217,6 +299,101 @@ parallelFst = combineWith (eagerEval . stopWhen FirstStops)
217parallelMin :: MonadAsync m => Stream m a -> Stream m a -> Stream m a
299parallelMin :: MonadAsync m => Stream m a -> Stream m a -> Stream m a
218parallelMin = combineWith (eagerEval . stopWhen AnyStops)
300parallelMin = combineWith (eagerEval . stopWhen AnyStops)
302+-------------------------------------------------------------------------------
304+-------------------------------------------------------------------------------
306+-- | A runner function takes a queuing function @q@ and a stream, it splits the
307+-- input stream, queuing the tail and using the head to generate a stream.
308+-- 'mkEnqueue' takes a runner function and generates the queuing function @q@.
309+-- Note that @q@ and the runner are mutually recursive, mkEnqueue ties the knot
311+{-# INLINE mkEnqueue #-}
312+mkEnqueue :: MonadAsync m =>
314+ -> ((K.Stream m a -> m ()) -> K.Stream m a -> K.Stream m b)
315+ -> m (K.Stream m a -> m ())
316+mkEnqueue chan runner = do
317+ runInIO <- askRunInIO
320+ -- Enqueue the outer loop
321+ liftIO $ enqueue chan False (runInIO, runner q stream)
322+ -- XXX In case of eager dispatch we can just directly dispatch
323+ -- a worker with the tail stream here rather than first queuing
324+ -- and then dispatching a worker which dequeues the work. The
325+ -- older implementation did a direct dispatch here and its perf
326+ -- characterstics looked much better.
330+-- XXX Can be renamed to concatMapWithK if we move concatMapWithK to higher
331+-- level module. We can keep only Channel based ops in this module.
333+-- | Takes the head element of the input stream and queues the tail of the
334+-- stream to the channel, then maps the supplied function on the head and
335+-- evaluates the resulting stream.
337+-- This function is designed to be used by worker threads on a channel to
338+-- concurrently map and evaluate a stream.
339+{-# INLINE concatMapWithChanK #-}
340+concatMapWithChanK :: MonadAsync m =>
341+ Channel m b -> (a -> K.Stream m b) -> K.Stream m a -> K.Stream m b
342+concatMapWithChanK chan f stream =
343+ let run q = concatMapDivK q f
344+ in K.concatMapEffect (`run` stream) (mkEnqueue chan run)
345+ -- K.concatMapWith (_appendWithChanK chan) f stream
347+{-# INLINE concatMapWithChanKAny #-}
348+concatMapWithChanKAny :: MonadAsync m =>
349+ Channel m b -> (a -> K.Stream m b) -> K.Stream m a -> K.Stream m b
350+concatMapWithChanKAny chan f stream =
351+ let done = K.nilM (stopChannel chan)
352+ run q = concatMapDivK q (\x -> K.serial (f x) done)
353+ in K.concatMapEffect (`run` stream) (mkEnqueue chan run)
355+{-# INLINE concatMapWithChanKFirst #-}
356+concatMapWithChanKFirst :: MonadAsync m =>
357+ Channel m b -> (a -> K.Stream m b) -> K.Stream m a -> K.Stream m b
358+concatMapWithChanKFirst chan f stream =
359+ let done = K.nilM (stopChannel chan)
360+ run q = concatMapDivK q f
361+ in K.concatEffect $ do
362+ res <- K.uncons stream
364+ Nothing -> return K.nil
366+ q <- mkEnqueue chan run
368+ return $ K.serial (f h) done
370+{-# INLINE concatMapWithChanKGeneric #-}
371+concatMapWithChanKGeneric :: MonadAsync m =>
374+ -> (a -> K.Stream m b)
377+concatMapWithChanKGeneric modifier chan f stream = do
378+ let cfg = modifier defaultConfig
379+ case getStopWhen cfg of
380+ AllStop -> concatMapWithChanK chan f stream
381+ FirstStops -> concatMapWithChanKFirst chan f stream
382+ AnyStops -> concatMapWithChanKAny chan f stream
384+-- XXX Add a deep evaluation variant that evaluates individual elements in the
385+-- generated streams in parallel.
387+-- | Allocate a channel and use it to concurrently evaluate the streams
388+-- generated by the mapped function.
390+{-# INLINE concatMapWithK #-}
391+concatMapWithK :: MonadAsync m =>
392+ (Config -> Config) -> (a -> K.Stream m b) -> K.Stream m a -> K.Stream m b
393+concatMapWithK modifier f =
394+ let g = concatMapWithChanKGeneric modifier
395+ in withChannelK (`g` f) modifier
220-- concatMapWith modifier f stream = concatWith modifier $ fmap f stream
397-- concatMapWith modifier f stream = concatWith modifier $ fmap f stream
222-- | Like 'concatMap' but we can also specify the concurrent channel's
399-- | Like 'concatMap' but we can also specify the concurrent channel's