HORIZON HASKELLDocslts/ghc-10.0.xfc4e5982026-10-11Search names, modules, packages, or :: a typeCtrl K

GHC 10.0.0.20260917 · lts/ghc-10.0.x · fc4e598 · 2026-10-11

Modulestreamly-0.11.1Haskell2010

Streamly.Internal.Data.Stream.Prelude

  • 5 types
  • 120 values
  • Packagestreamly-0.11.1
  • Exports125
  • LanguageHaskell2010
  • LicenceBSD-3-Clause
  • SourceChannel.hs

Concurrency Channels

60 declarations
valuechanConcatMapK
  1. :: MonadAsync m
  2. => Config -> Config
  3. -> Channel m b
  4. -> a -> StreamK m b
  5. -> StreamK m a
  6. -> StreamK m b
#

Make a concurrent stream evaluator from a stream, to be used in withChannelK or toChannelK. Maps a stream generation function on each element of the stream, the evaluation of the map on each element happens concurrently. All the generated streams are merged together in the output of the channel. The scheduling and termination behavior depends on the channel settings.

Note that if you queue a stream on the channel using toChannelK, it will be picked up by a worker and the worker would evaluate the entire stream serially and emit the results on the channel. However, if you transform the stream using parConcatMapChanK and queue it on the channel, it parallelizes the function map on each element of the stream. The simplest example is parConcatMapChanK id id which is equivalent to evaluating each element of the stream concurrently.

A channel worker evaluating this function would enqueue the tail on the channel's work queue and go on to evaluate the head generating an output stream. The tail is picked up by another worker which does the same and so on.

valuenewChannel :: MonadAsync m => (Config -> Config) -> m (Channel m a)
#

Create a new concurrent stream evaluation channel. The monad state used to run the stream actions is captured from the call site of newChannel.

valuenewAppendChannel :: MonadRunInIO m => (Config -> Config) -> m (Channel m a)
#

Create a new async style concurrent stream evaluation channel. The monad state used to run the stream actions is taken from the call site of newAppendChannel.

This is a low level API, use newChannel instead.

valuereadOutputQBounded :: MonadIO m => Bool -> Channel m a -> m [ChildEvent a]
#

Read the channel's output queue. When there is no output dispatches workers and waits for output (using sendWorkerWait). Always ensures that there is at least one outstanding worker.

To be used as readOutputQ function for the channel.

valuedispatchAllWait
  1. :: MonadIO m
  2. => Bool

    eager option is on

  3. -> (Channel m a -> IO ())

    delay function

  4. -> (Channel m a -> m Bool)

    dispatcher function

  5. -> Channel m a
  6. -> m ()
#

Dispatches as many workers as it can until output is seen in the event queue of the channel. If the dispatcher function returns False then no more dispatches can be done. If no more dispatches are possible blocks until output arrives in the event queue.

When this function returns we are sure that there is some output available.

Before we call this function we must ensure that there is either a pending worker or pending work, otherwise it might block forever. If there is pending work and no pending worker the dispatcher function must ensure that it dispatches a worker.

valuedispatchWorker
  1. :: MonadIO m
  2. => Count

    max yield limit for the worker

  3. -> Channel m a
  4. -> m Bool

    can dispatch more workers

#

Higher level API to dispatch a worker, it uses forkWorker to create a worker.

Dispatches a worker only if all of the following are true:

  • the channel has work to do

  • max thread count is not reached

  • max buffer limit is not reached

It is possible that no worker is dispatched even when there is no outstanding worker - only if any of the following is true:

  • maxBuffer limit is 0

  • maxThreads limit is set to 0

  • there is output pending in the output buffer

In all other cases a worker is guaranteed to be dispatched.

valuedispatchWorkerPaced
  1. :: MonadIO m
  2. => Channel m a
  3. -> m Bool

    True means can dispatch more

#

Like dispatchWorker but with rate control. The number of workers to be dispatched are decided based on the target rate. Uses dispatchWorker to actually dispatch when required. It may block wait until there is time to dispatch.

It guarantees that if there is no outstanding worker and there is work pending then it dispatches a worker though it may block for some time before it does that depending on the rate goal.

valueforkWorker
  1. :: MonadIO m
  2. => Count

    max yield limit for the worker

  3. -> Channel m a
  4. -> m ()
#

Low level API to create a worker. Forks a thread which executes the workLoop of the channel. It does not fork only if the channel is stopped.

valuestartChannel :: MonadIO m => Channel m a -> m ()
#

Start the evaluation of the channel's work queue by kicking off a worker. Note: Work queue must not be empty otherwise the worker will exit without doing anything.

valuefromChannelK
  1. :: MonadAsync m
  2. => Maybe (IO () -> IO ())
  3. -> Channel m a
  4. -> StreamK m a
#

Draw a stream from a concurrent channel. The stream consists of the evaluated values from the input streams that were enqueued on the channel using toChannelK.

This is the event processing loop for the channel which does two things, (1) dispatch workers, (2) process the events sent by the workers. Workers are dispatched based on the channel's configuration settings.

The stream stops and the channel is shutdown if any of the following occurs:

  • the work queue becomes empty

  • channel's max yield limit is reached

  • an exception is thrown by a worker

  • shutdown is called on the channel

Before the channel stops, all the workers are drained and no more workers are dispatched. When the channel is garbage collected a ThreadAbort exception is thrown to all pending workers. If inspect option is enabled then channel's stats are printed on stdout when the channel stops.

CAUTION! This API must not be called more than once on a channel.

valuetoChannelK :: MonadRunInIO m => Channel m a -> StreamK m a -> m ()
#

High level function to enqueue a work item on the channel. The fundamental unit of work is a stream. Each stream enqueued on the channel is picked up and evaluated by a worker thread. The worker evaluates the stream it picked up serially. When multiple streams are queued on the channel each stream can be evaluated concurrently by different workers.

Note that the items in each stream are not concurrently evaluated, streams are fundamentally serial, therefore, elements in one particular stream will be generated serially one after the other. Only two or more streams can be run concurrently with each other.

See chanConcatMapK for concurrent evaluation of each element of a stream. Alternatively, you can wrap each element of the original stream into a stream generating action and queue all those streams on the channel. Then all of them would be evaluated concurrently. However, that would not be streaming in nature, it would require buffering space for the entire original stream. Prefer chanConcatMapK for larger streams.

Items from each evaluated streams are queued to the same output queue of the channel which can be read using fromChannelK. toChannelK can be called multiple times to enqueue multiple streams on the channel.

valueavgRate :: Double -> Config -> Config
#

Same as rate (Just $ Rate (r/2) r (2*r) maxBound)

Specifies the average production rate of a stream in number of yields per second (i.e. Hertz). Concurrent production is ramped up or down automatically to achieve the specified average yield rate. The rate can go down to half of the specified rate on the lower side and double of the specified rate on the higher side.

valuecleanupChan :: Channel m a -> String -> IO ()
#

Never called from a worker thread. This can be called multiple times and only one will succeed others will block until it finishes.

valueconstRate :: Double -> Config -> Config
#

Same as rate (Just $ Rate r r r 0)

Specifies a constant yield rate. If for some reason the actual rate goes above or below the specified rate we do not try to recover it by increasing or decreasing the rate in future. This can be useful in applications like graphics frame refresh where we need to maintain a constant refresh rate.

valuedefaultConfig :: Config
#

The fields prefixed by an _ are not to be accessed or updated directly but via smart accessor APIs. Use get/set routines instead of directly accessing the Config fields

valueeager :: Bool -> Config -> Config
#

By default, processing of output from the worker threads is given priority over dispatching new workers. More workers are dispatched only when there is no output to process. When eager is set to True, workers are dispatched aggresively as long as there is more work to do irrespective of whether there is output pending to be processed by the stream consumer. However, dispatching may stop if maxThreads or maxBuffer is reached.

Note: This option has no effect when rate has been specified.

Note: Not supported with interleaved.

valueinspect :: Bool -> Config -> Config
#

Print debug information about the Channel when the stream ends. When the stream does not end normally, the channel debug information is printed when the channel is garbage collected. If you are expecting but not seeing the debug info try adding a performMajorGC before the program ends.

valueinterleaved :: Bool -> Config -> Config
#

Interleave the streams fairly instead of prioritizing the left stream. This schedules all streams in a round robin fashion over limited number of threads.

Note: Can only be used on finite number of streams.

Note: Not supported with ordered.

valuemaxBuffer :: Int -> Config -> Config
#

Specify the maximum size of the buffer for storing the results from concurrent computations. If the buffer becomes full we stop spawning more concurrent tasks until there is space in the buffer. A value of 0 resets the buffer size to default, a negative value means there is no limit. The default value is 1500.

CAUTION! using an unbounded maxBuffer value (i.e. a negative value) coupled with an unbounded maxThreads value is a recipe for disaster in presence of infinite streams, or very large streams. Especially, it must not be used when pure is used in ZipAsyncM streams as pure in applicative zip streams generates an infinite stream causing unbounded concurrent generation with no limit on the buffer or threads.

valuemaxRate :: Double -> Config -> Config
#

Same as rate (Just $ Rate (r/2) r r maxBound)

Specifies the maximum rate at which the stream should yield values. As far as possible the yield rate would never be allowed to go above the specified rate, even though it may possibly go below it at times, the lower limit is half of the specified rate. This can be useful in applications where certain resource usage must not be allowed to go beyond certain limits.

valuemaxThreads :: Int -> Config -> Config
#

Specify the maximum number of threads that can be spawned by the channel. A value of 0 resets the thread limit to default, a negative value means there is no limit. The default value is 1500.

When the actions in a stream are IO bound, having blocking IO calls, this option can be used to control the maximum number of in-flight IO requests. When the actions are CPU bound this option can be used to control the amount of CPU used by the stream.

valuemaxYields :: Maybe Int64 -> Config -> Config
#

The maximum number of yields that this channel would produce. The Channel automatically stops after that. This could be used to limit the speculative execution beyond the limit.

Nothing means there is no limit.

Keep in mind that checking this limit all the time has a performance overhead.

Known Bugs: currently this works only when rate is specified. Known Bugs: for ordered streams sometimes the actual count is less than expected.

valueminRate :: Double -> Config -> Config
#

Same as rate (Just $ Rate r r (2*r) maxBound)

Specifies the minimum rate at which the stream should yield values. As far as possible the yield rate would never be allowed to go below the specified rate, even though it may possibly go above it at times, the upper limit is double of the specified rate.

valueordered :: Bool -> Config -> Config
#

When enabled the streams may be evaluated cocnurrently but the results are produced in the same sequence as a serial evaluation would produce.

Note: Not supported with interleaved.

valuerate :: Maybe Rate -> Config -> Config
#

Specify the stream evaluation rate of a channel.

A Nothing value means there is no smart rate control, concurrent execution blocks only if maxThreads or maxBuffer is reached, or there are no more concurrent tasks to execute. This is the default.

When rate (throughput) is specified, concurrent production may be ramped up or down automatically to achieve the specified stream throughput. The specific behavior for different styles of Rate specifications is documented under Rate. The effective maximum production rate achieved by a channel is governed by:

  • The maxThreads limit

  • The maxBuffer limit

  • The maximum rate that the stream producer can achieve

  • The maximum rate that the stream consumer can achieve

Maximum production rate is given by:

rate = \frac{maxThreads}{latency}

If we know the average latency of the tasks we can set maxThreads accordingly.

valueshutdown :: MonadIO m => Channel m a -> m ()
#

Send a ChildStopChannel event to shutdown the channel. Upon receiving the event the event processing loop kills all the registered worker threads and stops the channel.

valuestopWith :: Maybe WorkerInfo -> Channel m a -> IO ()
#

Send a ChildStop event to the channel, used when the worker stops yielding and exits. The final update of the collected latency stats in WorkerInfo is pushed to the channel. Upon receiving the ChildStop event the channel would remove the worker from its set of registered workers.

A worker that uses this API must have been registered on the Channel prior to invoking this API. This is usually done by the dispatcher when the worker is dispatched.

valueuseAcquire :: AcquireIO -> Config -> Config
#

A concurrent stream allocates worker threads to evaluates actions in the stream concurrently. When an exception (sync or async) occurs in the code outside the scope of the stream generation code, these workers need to be stopped promptly. To enable that we can use an AcquireIO bracket from the surrounding scope. When AcquireIO scope ends the channel is automatically cleaned up.

Here is an example:

Example4 expressions
import Control.Monad (when)import Control.Concurrent (threadDelay)import Data.Function ((&))import System.IO (hClose, IOMode(..), openFile)
Example3 expressions
import qualified Streamly.Data.Fold as Foldimport qualified Streamly.Data.Stream.Prelude as Streamimport qualified Streamly.Control.Exception as Exception
Example1 expression
:{close x h = do putStrLn $ "closing: " ++ x hClose h:}
Example1 expression
:{action ref =     Stream.fromList ["file1", "file2"]   & Stream.parMapM (Stream.useAcquire ref)       (\x -> do           (h, release) <- Exception.acquire ref (openFile x ReadMode) (close x)           -- use h here           threadDelay 1000000           when (x == "file1") $ do               putStrLn $ "Manually releasing: " ++ x               release           return x       )   & Stream.trace print   & Stream.fold Fold.drain:}
Example1 expression
run = Exception.withAcquireIO action
valueyieldWith
  1. :: Maybe WorkerInfo

    Rate control info for the worker

  2. -> Channel m a
  3. -> a
  4. -> IO Bool

    True means the worker can continue otherwise stop.

#

Used by workers to send a value to the channel's output stream.

When a worker is dispatched, a WorkerInfo record is supplied to it by the dispatcher. This record contains the timestamp at the time of dispatch. Whenever the worker yields a value, the yield count in the WorkerInfo is incremented. If the channel has rate control enabled, the yield count and time duration is periodically (based on workerPollingInterval) pushed to the channel's workerPendingLatency stat. It is done only if the workerPollingInterval is non-zero.

Queues the event but returns False if:

  • the buffer limit is exceeding

  • channel yield rate is exceeding (when rate control is enabled and WorkerInfo is available)

This is a thread-safe API and can be called by anyone from anywhere. Even a thread that is not registered as a worker with the channel can use it but when rate control is enabled, it might confuse the rate control mechanism if we use workers beyond the knowledge of dispatcher.

datadata Rate
#

Specifies the stream yield rate in yields per second (Hertz). We keep accumulating yield credits at rateGoal. At any point of time we allow only as many yields as we have accumulated as per rateGoal since the start of time. If the consumer or the producer is slower or faster, the actual rate may fall behind or exceed rateGoal. We try to recover the gap between the two by increasing or decreasing the pull rate from the producer. However, if the yield count gap becomes more than rateBuffer (specified as a yield count) we try to recover only as much as rateBuffer.

rateLow puts a bound on how low the instantaneous rate can go when recovering the rate gap. In other words, it determines the maximum yield latency. Similarly, rateHigh puts a bound on how high the instantaneous rate can go when recovering the rate gap. In other words, it determines the minimum yield latency. We reduce the latency by increasing concurrency, therefore we can say that it puts an upper bound on concurrency.

If the rateGoal is 0 or negative the stream never yields a value. If the rateBuffer is 0 or negative we do not attempt to recover.

Constructors

datadata StopWhen
#

Specify when the Channel should stop.

Constructors

datadata Channel (m :: Type -> Type) a
#

A mutable channel to evaluate multiple streams concurrently and provide the combined results as output stream.

There are only two actors working on the channel data structure, the event processing loop (single thread), and the workers (multiple threads). Locking notes are provided below for concurrent access.

Constructors

  • Channel
    • svarMrun :: RunInIO m

      Runner for the monadic actions in the stream. Captures the monad state at the point where the channel was created and uses the same state to run all actions.

    • maxBufferLimit :: Limit

      Maximum size of the outputQueue. The actual worst case buffer could be double of this as the event loop may read the queue and the workers may fill it up even before the event loop has started consuming.

    • outputQueue :: IORef ([ChildEvent a], Int)

      (events, count): worker event queue of the channel. This is where the workers queue the results and other events.

      LOCKING

      Frequently locked. This is locked and updated by workers on each yield, and locked, updated by the event loop thread once in a while for reading. Workers' locking contention may be high if there are a large number of workers.

    • outputDoorBell :: MVar ()

      Door bell for workers to wakeup the event loop.

      LOCKING

      Infrequently locked. Used only when the

      outputQueue

      transitions from empty to non-empty, or a work item is queued by a worker to the work queue and

      doorBellOnWorkQ

      is set by the event loop.

      We also use this for workerCount decrement, we wait on this during cleanup. So any workerCount decrement must send a doorBell.

    • readOutputQ :: m [ChildEvent a]

      Function to read the output queue of the channel, depends on the rate control option.

    • postProcess :: m Bool

      Function to invoke after all the events read in a batch are processed i.e. before we go on to read the next batch, depends on the rate control option.

    • remainingWork :: Maybe (IORef Count)

      Tracks how many yields are remaining before the channel stops, used when maxYields option is enabled.

      LOCKING

      Read only access by event loop when dispatching a worker. Decremented by workers when picking work and undo decrement if the worker does not yield a value.

    • isWorkDone :: IO Bool

      Determine if there is no more work to do. When maxYields is set for the channel we may be done even if the work queue still has work.

    • yieldRateInfo :: Maybe YieldRateInfo

      Rate control information for the channel used when rate control is enabled,

    • doorBellOnWorkQ :: IORef Bool

      When set to True, ring outputDoorBell when a work item is queued on the work queue. This is set by the dispatcher before going to sleep. It wants to be woken up whenever the work queue got more work to do so that it can dispatch a worker.

    • eagerDispatch :: m ()

      This is a hook which is invoked whenever the tail of the stream is re-enqueued on the work queue. Normally, this is set to a noop. When eager option is enabled this is set to an unconditional worker dispatch function. This ensures that we eagerly send a worker as long as there is work to do.

      NOTE that this is called from a worker context, therefore we should consider appropriate locking semantics.

    • enqueue :: (RunInIO m, StreamK m a) -> IO ()

      Enqueue a stream for evaluation on the channel. The first element of the tuple is the runner function which is used to run the stream actions in a specific monadic context.

    • isQueueDone :: IO Bool

      Determine if the work queue is empty, therefore, there is no more work to do.

    • workLoop :: Maybe WorkerInfo -> m ()

      Worker function. It is implicitly aware of the work queue. It dequeues a work item from the queue and runs it. It keeps on doing this in a loop until it determines that it needs to stop.

      Normally, the worker stops when the work queue becomes empty or the work rate is higher than the target rate when rate control is enabled. It stops by sending a ChildStop event to the channel

      When rate control is enabled, the worker is dispatched with a WorkerInfo record which is used by the worker to maintain rate control information and communicate it to the channel.

    • channelStopping :: IORef Bool
    • channelStopped :: MVar Bool
    • maxWorkerLimit :: Limit

      This is capped to maxBufferLimit if set to more than that. Otherwise potentially each worker may yield one value to the buffer in the worst case exceeding the requested buffer size.

    • workerThreads :: IORef (Set ThreadId)

      Tracks all active worker threads. An entry is added by the dispatcher when a worker is dispatched, and removed whenever the event processing loop receives a ChildStop event.

      LOCKING

      Normally, this is updated only by the event loop thread, but in case of eager dispatch (done in worker context) it is updated by a worker. So reads from the event loop should be mindful of that. Updates to this must be async signal safe because we rely on it for cleanup and cleanup may leave unfinished threads if a thread is forked but this is not updated.

    • workerCount :: IORef Int

      Total number of active worker threads.

      LOCKING

      Updated locked, by the event loop thread when dispatching a worker and by a worker thread when the thread stops. This is read without lock at several places where we want to rely on an approximate value. Updates to this must be async signal safe because we rely on it for cleanup and cleanup may hang and leave unfinished threads if this is not correct.

    • accountThread :: ThreadId -> m ()
    • workerStopMVar :: MVar ()

      Used when ordered is enabled. This is a lock to stop the workers one at a time. Stopping one might affect whether the other should stop.

    • svarRef :: Maybe (IORef ())

      A weak IORef to call a cleanup function when the channel is garbage collected.

    • svarStats :: SVarStats

      Stats collection.

    • svarInspectMode :: Bool

      When inspect mode is enabled we report diagnostic data about the channel at certain points.

    • svarCreator :: ThreadId

      threadId of the thread that created the channel

datadata Config
#

An abstract type for specifying the configuration parameters of a Channel. Use Config -> Config modifier functions to modify the default configuration. See the individual modifier documentation for default values.

Concurrent Streams

31 declarations
valuefromCallback :: MonadAsync m => ((a -> m ()) -> m ()) -> Stream m a
#

fromCallback action runs action with a callback which is used by the action to send values that appear in the resulting stream. The action must be run in a separate thread independent of the one in which the stream is being evaluated. The action is supposed to be run forever in an infinite loop.

Example:

> import Control.Concurrent (threadDelay, forkIO)
> import Control.Monad (void, forever)
> import qualified Streamly.Data.Fold as Fold
> import qualified Streamly.Data.Stream.Prelude as Stream
>
> main = do
>     Stream.fold (Fold.drainMapM print)
>         $ Stream.fromCallback
>         $ \yield ->
>             void $ forkIO $ forever $ do
>                 yield "x" >> threadDelay 1000000

Pre-release

valuenewStreamAndCallback :: MonadAsync m => m (a -> m (), Stream m a)
#

Returns an entangled pair of a callback and a stream i.e. whenever the callback is called a value appears in the stream. The stream is infinite, there is no way for the callback to indicate that it is done now.

The callback queues a value to a concurrent channel associated with the stream. The stream can be evaluated safely in any thread.

Pre-release

valueparApply
  1. :: MonadAsync m
  2. => Config -> Config
  3. -> Stream m (a -> b)
  4. -> Stream m a
  5. -> Stream m b
#

Deprecated. Please use parCrossApply instead.

Apply an argument stream to a function stream concurrently. Uses a shared channel for all individual applications within a stream application.

valueparBuffered
  1. :: MonadAsync m
  2. => Config -> Config
  3. -> Stream m a
  4. -> Stream m a
#

parBuffered evaluates a stream as a whole asynchronously with respect to the consumer of the stream. A worker thread evaluates multiple elements of the stream ahead of time and buffers the results; the consumer of the stream runs in another thread consuming the elements from the buffer, thus decoupling the production and consumption of the stream. parBuffered can be used to run different stages of a pipeline concurrently.

It is important to note that parBuffered does not evaluate individual actions in the stream concurrently with respect to each other, it merely evaluates the stream serially but in a different thread than the consumer thread, thus the consumer and producer can run concurrently. See parMapM and parSequence to evaluate actions in the stream concurrently.

The evaluation requires only one thread as only one stream needs to be evaluated. Therefore, the concurrency options that are relevant to multiple streams do not apply here e.g. maxThreads, eager, interleaved, ordered, stopWhen options do not have any effect on parBuffered.

Useful idioms:

Example2 expressions
parUnfoldrM step = Stream.parBuffered id . Stream.unfoldrM stepparIterateM step = Stream.parBuffered id . Stream.iterateM step
valueparConcatMap
  1. :: MonadAsync m
  2. => Config -> Config
  3. -> a -> Stream m b
  4. -> Stream m a
  5. -> Stream m b
#

Map each element of the input to a stream and then concurrently evaluate and concatenate the resulting streams. Multiple streams may be evaluated concurrently but earlier streams are perferred. Output from the streams are used as they arrive.

Definition:

Example1 expression
parConcatMap modifier f stream = Stream.parConcat modifier $ fmap f stream

Examples:

Example1 expression
f cfg xs = Stream.fold Fold.toList $ Stream.parConcatMap cfg id $ Stream.fromList xs

The following streams finish in 4 seconds:

Example4 expressions
stream1 = Stream.fromEffect (delay 4)stream2 = Stream.fromEffect (delay 2)stream3 = Stream.fromEffect (delay 1)f id [stream1, stream2, stream3]1 sec2 sec4 sec[1,2,4]

Limiting threads to 2 schedules the third stream only after one of the first two has finished, releasing a thread:

Example1 expression
f (Stream.maxThreads 2) [stream1, stream2, stream3]...[2,1,4]

When used with a Single thread it behaves like serial concatMap:

Example1 expression
f (Stream.maxThreads 1) [stream1, stream2, stream3]...[4,2,1]
Example3 expressions
stream1 = Stream.fromList [1,2,3]stream2 = Stream.fromList [4,5,6]f (Stream.maxThreads 1) [stream1, stream2][1,2,3,4,5,6]

Schedule all streams in a round robin fashion over the available threads:

Example1 expression
f cfg xs = Stream.fold Fold.toList $ Stream.parConcatMap (Stream.interleaved True . cfg) id $ Stream.fromList xs
Example3 expressions
stream1 = Stream.fromList [1,2,3]stream2 = Stream.fromList [4,5,6]f (Stream.maxThreads 1) [stream1, stream2][1,4,2,5,3,6]
valueparEval :: MonadAsync m => (Config -> Config) -> Stream m a -> Stream m a
#

Deprecated. Please use parBuffered instead.

parBuffered evaluates a stream as a whole asynchronously with respect to the consumer of the stream. A worker thread evaluates multiple elements of the stream ahead of time and buffers the results; the consumer of the stream runs in another thread consuming the elements from the buffer, thus decoupling the production and consumption of the stream. parBuffered can be used to run different stages of a pipeline concurrently.

It is important to note that parBuffered does not evaluate individual actions in the stream concurrently with respect to each other, it merely evaluates the stream serially but in a different thread than the consumer thread, thus the consumer and producer can run concurrently. See parMapM and parSequence to evaluate actions in the stream concurrently.

The evaluation requires only one thread as only one stream needs to be evaluated. Therefore, the concurrency options that are relevant to multiple streams do not apply here e.g. maxThreads, eager, interleaved, ordered, stopWhen options do not have any effect on parBuffered.

Useful idioms:

Example2 expressions
parUnfoldrM step = Stream.parBuffered id . Stream.unfoldrM stepparIterateM step = Stream.parBuffered id . Stream.iterateM step
valueparListEagerFst :: MonadAsync m => [Stream m a] -> Stream m a
#

Like parListEager but stops the output as soon as the first stream stops.

Example1 expression
parListEagerFst = Stream.parList (Stream.eager True . Stream.stopWhen Stream.FirstStops)
valueparListEagerMin :: MonadAsync m => [Stream m a] -> Stream m a
#

Like parListEager but stops the output as soon as any of the two streams stops.

Definition:

Example1 expression
parListEagerMin = Stream.parList (Stream.eager True . Stream.stopWhen Stream.AnyStops)
valueparListInterleaved :: MonadAsync m => [Stream m a] -> Stream m a
#

Like parListLazy but interleaves the streams fairly instead of prioritizing the left stream. This schedules all streams in a round robin fashion over limited number of threads.

Example1 expression
parListInterleaved = Stream.parList (Stream.interleaved True)
valueparMapM
  1. :: MonadAsync m
  2. => Config -> Config
  3. -> a -> m b
  4. -> Stream m a
  5. -> Stream m b
#

Definition:

Example1 expression
parMapM modifier f = Stream.parConcatMap modifier (Stream.fromEffect . f)

For example, the following finishes in 3 seconds (as opposed to 6 seconds) because all actions run in parallel. Even though results are available out of order they are ordered due to the config option:

Example2 expressions
f x = delay x >> return xStream.fold Fold.toList $ Stream.parMapM (Stream.ordered True) f $ Stream.fromList [3,2,1]1 sec2 sec3 sec[3,2,1]
valueparMergeByM
  1. :: MonadAsync m
  2. => Config -> Config
  3. -> a -> a -> m Ordering
  4. -> Stream m a
  5. -> Stream m a
  6. -> Stream m a
#

Like mergeByM but evaluates both the streams concurrently.

Definition:

Example1 expression
parMergeByM cfg f m1 m2 = Stream.mergeByM f (Stream.parBuffered cfg m1) (Stream.parBuffered cfg m2)
valueparRepeatM :: MonadAsync m => (Config -> Config) -> m a -> Stream m a
#

Definition:

Example1 expression
parRepeatM cfg = Stream.parSequence cfg . Stream.repeat

Generate a stream by repeatedly executing a monadic action forever.

valueparReplicateM
  1. :: MonadAsync m
  2. => Config -> Config
  3. -> Int
  4. -> m a
  5. -> Stream m a
#

Generate a stream by concurrently performing a monadic action n times.

Definition:

Example1 expression
parReplicateM cfg n = Stream.parSequence cfg . Stream.replicate n

Example, parReplicateM in the following example executes all the replicated actions concurrently, thus taking only 1 second:

Example1 expression
Stream.fold Fold.drain $ Stream.parReplicateM id 10 $ delay 1...
valueparSequence
  1. :: MonadAsync m
  2. => Config -> Config
  3. -> Stream m (m a)
  4. -> Stream m a
#

Definition:

Example1 expression
parSequence modifier = Stream.parMapM modifier id

Useful idioms:

Example2 expressions
parFromListM = Stream.parSequence id . Stream.fromListparFromFoldableM = Stream.parSequence id . StreamK.toStream . StreamK.fromFoldable
valueparTapCount
  1. :: MonadAsync m
  2. => a -> Bool
  3. -> Stream m Int -> m b
  4. -> Stream m a
  5. -> Stream m a
#

parTapCount predicate fold stream taps the count of those elements in the stream that pass the predicate. The resulting count stream is sent to a fold running concurrently in another thread.

For example, to print the count of elements processed every second:

Example4 expressions
rate = Stream.rollingMap2 (flip (-)) . Stream.delayPost 1report = Stream.fold (Fold.drainMapM print) . ratetap = Stream.parTapCount (const True) reportgo = Stream.fold Fold.drain $ tap $ Stream.enumerateFrom 0

Note: This may not work correctly on 32-bit machines because of Int overflow.

Pre-release

valueparTwo
  1. :: MonadAsync m
  2. => Config -> Config
  3. -> Stream m a
  4. -> Stream m a
  5. -> Stream m a
#

Binary operation to evaluate two streams concurrently using a channel.

If you want to combine more than two streams you almost always want the parList or parConcat 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, parConcat uses a single channel for all streams. However, with this operation you can precisely control the scheduling by creating arbitrary shape expression trees.

Definition:

Example1 expression
parTwo cfg x y = Stream.parList cfg [x, y]

Example, the following code finishes in 4 seconds:

Example4 expressions
async = Stream.parTwo idstream1 = Stream.fromEffect (delay 4)stream2 = Stream.fromEffect (delay 2)Stream.fold Fold.toList $ stream1 `async` stream22 sec4 sec[2,4]
valueparYieldWith :: (Config -> Config) -> ((a -> m b) -> m c) -> Stream m a
#

An improved version of fromCallback.

  • Takes a channel config modifier

  • Evaluates the action in a parallel thread

  • The action is supplied with a yield function to yield values to the stream

  • Any exception generated is forwarded to the stream

  • Sends a Stop event when the action is done.

Unimplemented

valueparZipWith
  1. :: MonadAsync m
  2. => Config -> Config
  3. -> a -> b -> c
  4. -> Stream m a
  5. -> Stream m b
  6. -> Stream m c
#
Example1 expression
parZipWith cfg f = Stream.parZipWithM cfg (\a b -> return $ f a b)
Example3 expressions
m1 = Stream.fromList [1,2,3]m2 = Stream.fromList [4,5,6]Stream.fold Fold.toList $ Stream.parZipWith id (,) m1 m2[(1,4),(2,5),(3,6)]
valueparZipWithM
  1. :: MonadAsync m
  2. => Config -> Config
  3. -> a -> b -> m c
  4. -> Stream m a
  5. -> Stream m b
  6. -> Stream m c
#

Evaluates the streams being zipped in separate threads than the consumer. The zip function is evaluated in the consumer thread.

Example1 expression
parZipWithM cfg f m1 m2 = Stream.zipWithM f (Stream.parBuffered cfg m1) (Stream.parBuffered cfg m2)

Multi-stream concurrency options won't apply here, see the notes in parBuffered.

If you want to evaluate the zip function as well in a separate thread, you can use a parBuffered on parZipWithM.

Time

26 declarations
valueboundedIntervalsOf
  1. :: Int
  2. -> Double
  3. -> Int
  4. -> Fold m a b
  5. -> Stream m a
  6. -> Stream m b
#

Like intervalsOf but with an additional argument to limit the number of collected items to a max limit. If the limit is reached, the fold output is emitted and the next interval is started.

An alternative behavior would be to emit multiple elements in the same interval if the size is exceeded, keeping the intervals fixed. That can be achieved by using intervalsOf and using a fold that terminates on the limit.

Unimplemented

valuebufferLatest :: Stream m a -> Stream m (Maybe a)
#

Always produce the latest available element from the stream without any delay. The stream is continuously evaluated at the highest possible rate and only the latest element is retained for sampling.

Unimplemented

valuebufferLatestN :: Int -> Stream m a -> Stream m a
#

Evaluate the input stream continuously and keep only the latest n elements in a ring buffer, keep discarding the older ones to make space for the new ones. When the output stream is evaluated the buffer collected till now is streamed and it starts filling again.

Unimplemented

valuebufferOldestN :: Int -> Stream m a -> Stream m a
#

Evaluate the input stream continuously and keep only the oldest n elements in the buffer, discard the new ones when the buffer is full. When the output stream is evaluated the collected buffer is streamed and the buffer starts filling again.

Unimplemented

valueclassifySessionsBy
  1. :: (MonadAsync m, Ord k)
  2. => Double

    timer tick in seconds

  3. -> Bool

    reset the timer when an event is received

  4. -> (Int -> m Bool)

    predicate to eject sessions based on session count

  5. -> Double

    session timeout in seconds

  6. -> Fold m a b

    Fold to be applied to session data

  7. -> Stream m (AbsTime, (k, a))

    timestamp, (session key, session data)

  8. -> Stream m (k, b)

    session key, fold result

#

classifySessionsBy tick keepalive predicate timeout fold stream classifies an input event stream consisting of (timestamp, (key, value)) into sessions based on the key, folding all the values corresponding to the same key into a session using the supplied fold.

When the fold terminates or a timeout occurs, a tuple consisting of the session key and the folded value is emitted in the output stream. The timeout is measured from the first event in the session. If the keepalive option is set to True the timeout is reset to 0 whenever an event is received.

The timestamp in the input stream is an absolute time from some epoch, characterizing the time when the input event was generated. The notion of current time is maintained by a monotonic event time clock using the timestamps seen in the input stream. The latest timestamp seen till now is used as the base for the current time. When no new events are seen, a timer is started with a clock resolution of tick seconds. This timer is used to detect session timeouts in the absence of new events.

To ensure an upper bound on the memory used the number of sessions can be limited to an upper bound. If the ejection predicate returns True, the oldest session is ejected before inserting a new session.

When the stream ends any buffered sessions are ejected immediately.

If a session key is received even after a session has finished, another session is created for that key.

Example1 expression
:{Stream.fold (Fold.drainMapM print)    $ Stream.classifySessionsBy 1 False (const (return False)) 3 (Fold.take 3 Fold.toList)    $ Stream.timestamped    $ Stream.delay 0.1    $ Stream.fromList ((,) <$> [1,2,3] <*> ['a','b','c']):}(1,"abc")(2,"abc")(3,"abc")

Pre-release

valuedropInterval :: MonadAsync m => Double -> Stream m a -> Stream m a
#

dropInterval interval drops all the stream elements that are generated before the specified interval in seconds has passed.

The interval begins when the stream is evaluated for the first time.

valuedropLastInterval :: Int -> Stream m a -> Stream m a
#

Drop time interval i seconds at the end of the stream.

O(n) space, where n is the number elements dropped.

Unimplemented

valueinterject :: MonadAsync m => m a -> Double -> Stream m a -> Stream m a
#

Intersperse a monadic action into the input stream after every n seconds.

Definition:

Example1 expression
interject n f xs = Stream.parListEagerFst [xs, Stream.periodic f n]

Example:

Example3 expressions
s = Stream.fromList "hello"input = Stream.mapM (\x -> threadDelay 1000000 >> putChar x) sStream.fold Fold.drain $ Stream.interject (putChar ',') 1.05 inputh,e,l,l,o
valueintervalsOf
  1. :: MonadAsync m
  2. => Double
  3. -> Fold m a b
  4. -> Stream m a
  5. -> Stream m b
#

Group the input stream into windows of n second each and then fold each group using the provided fold function.

If the fold terminates before the interval is over, the next collection is started, thus multiple collection can happen in the same interval. If the fold does not terminate before the interval is over, the fold will be forced to terminate at the interval end.

Example:

Example3 expressions
twoPerSec = Stream.parBuffered (Stream.constRate 2) $ Stream.enumerateFrom 1intervals = Stream.intervalsOf 1 Fold.toList twoPerSecStream.fold Fold.toList $ Stream.take 2 intervals[...,...]
valueperiodic :: MonadIO m => m a -> Double -> Stream m a
#

Generate a stream by running an action periodically at the specified time interval.

valuesampleBurstEnd :: MonadAsync m => Double -> Stream m a -> Stream m a
#

Sample one event at the end of each burst of events. A burst is a group of events close together in time, it ends when an event is spaced by more than the specified time interval (in seconds) from the previous event.

This is known as debounce in some libraries.

The clock granularity is 10 ms.

valuesampleIntervalEnd :: MonadAsync m => Double -> Stream m a -> Stream m a
#

Continuously evaluate the input stream and sample the last event in each time window of n seconds.

This is also known as throttle in some libraries.

Example1 expression
sampleIntervalEnd n = Stream.catMaybes . Stream.intervalsOf n Fold.latest
valuesampleIntervalStart :: MonadAsync m => Double -> Stream m a -> Stream m a
#

Like sampleInterval but samples at the beginning of the time window.

Example1 expression
sampleIntervalStart n = Stream.catMaybes . Stream.intervalsOf n Fold.one
valuetakeInterval :: MonadAsync m => Double -> Stream m a -> Stream m a
#

takeInterval interval runs the stream only upto the specified time interval in seconds.

The interval starts when the stream is evaluated for the first time.

valuetakeLastInterval :: Double -> Stream m a -> Stream m a
#

Take time interval i seconds at the end of the stream.

O(n) space, where n is the number elements taken.

Unimplemented

valueticks :: MonadIO m => Double -> Stream m ()
#

Generate a tick stream consisting of () elements, each tick is generated after the specified time delay given in seconds.

Example1 expression
ticks = Stream.periodic (return ())
valueticksRate :: MonadAsync m => Rate -> Stream m ()
#

Generate a tick stream, ticks are generated at the specified Rate. The rate is adaptive, the tick generation speed can be increased or decreased at different times to achieve the specified rate. The specific behavior for different styles of Rate specifications is documented under Rate. The effective maximum rate achieved by a stream is governed by the processor speed.

Example2 expressions
tickStream = Stream.repeatM (return ())ticksRate r = Stream.parBuffered (Stream.rate (Just r)) tickStream
valuetimedChunksOf'
  1. :: (MonadAsync m, Unbox a)
  2. => Double
  3. -> Int
  4. -> Stream m a
  5. -> Stream m (Array a)
#

Like timedChunksOf but creates pinned arrays. If the chunks are smaller than LARGE_OBJECT_THRESHOLD then this routine may be useful for better performance if the arrays are to be sent for IO. This will avoid a copy for pinning by the IO routines.

valuetimedGroupsOf
  1. :: MonadAsync m
  2. => Double
  3. -> Int
  4. -> Fold m a b
  5. -> Stream m a
  6. -> Stream m b
#

Like groupsOf but if the group is not completed within the specified time interval then emit whatever we have collected till now. The group timeout is reset whenever a group is emitted. The granularity of the clock is 100 ms.

Note that it will not emit any output unless at least one item has been collected in the group. The time interval is only for timing out the already collected but not yet complete group.

Example2 expressions
s = Stream.delayPost 0.3 $ Stream.fromList [1..1000]f = Stream.fold (Fold.drainMapM print) $ Stream.timedGroupsOf 1 5 Fold.toList s

Pre-release

Lifted

8 declarations
valueafter
  1. :: (MonadIO m, MonadBaseControl IO m)
  2. => m b
  3. -> Stream m a
  4. -> Stream m a
#

Run the action m b whenever the stream Stream m a stops normally, or if it is garbage collected after a partial lazy evaluation.

The semantics of the action m b are similar to the semantics of cleanup action in bracket.

See also after_

valuebracket
  1. :: (MonadAsync m, MonadCatch m)
  2. => m b
  3. -> b -> m c
  4. -> b -> Stream m a
  5. -> Stream m a
#

Run the alloc action IO b with async exceptions disabled but keeping blocking operations interruptible (see mask). Use the output b of the IO action as input to the function b -> Stream m a to generate an output stream.

b is usually a resource under the IO monad, e.g. a file handle, that requires a cleanup after use. The cleanup action b -> m c, runs whenever (1) the stream ends normally, (2) due to a sync or async exception or, (3) if it gets garbage collected after a partial lazy evaluation. The exception is not caught, it is rethrown.

bracket only guarantees that the cleanup action runs, and it runs with async exceptions enabled. The action must ensure that it can successfully cleanup the resource in the face of sync or async exceptions.

When the stream ends normally or on a sync exception, cleanup action runs immediately in the current thread context, whereas in other cases it runs in the GC context, therefore, cleanup may be delayed until the GC gets to run.

See also: bracket_

Inhibits stream fusion

valuebracket3
  1. :: (MonadAsync m, MonadCatch m)
  2. => m b
  3. -> b -> m c
  4. -> b -> m d
  5. -> b -> m e
  6. -> b -> Stream m a
  7. -> Stream m a
#

Like bracket but can use 3 separate cleanup actions depending on the mode of termination:

  1. When the stream stops normally

  2. When the stream is garbage collected

  3. When the stream encounters an exception

bracket3 before onStop onGC onException action runs action using the result of before. If the stream stops, onStop action is executed, if the stream is abandoned onGC is executed, if the stream encounters an exception onException is executed.

The exception is not caught, it is rethrown.

Pre-release

valuefinally :: (MonadAsync m, MonadCatch m) => m b -> Stream m a -> Stream m a
#

Run the action m b whenever the stream Stream m a stops normally, aborts due to an exception or if it is garbage collected after a partial lazy evaluation.

The semantics of running the action m b are similar to the cleanup action semantics described in bracket.

Example1 expression
finally action xs = Stream.bracket (return ()) (const action) (const xs)

See also finally_

Inhibits stream fusion

valueretry
  1. :: (MonadCatch m, Exception e, Ord e)
  2. => Map e Int

    map from exception to retry count

  3. -> (e -> Stream m a)

    default handler for those exceptions that are not in the map

  4. -> Stream m a
  5. -> Stream m a
#

retry takes 3 arguments

  1. A map m whose keys are exceptions and values are the number of times to retry the action given that the exception occurs.

  2. A handler han that decides how to handle an exception when the exception cannot be retried.

  3. The stream itself that we want to run this mechanism on.

When evaluating a stream if an exception occurs,

  1. The stream evaluation aborts

  2. The exception is looked up in m

a. If the exception exists and the mapped value is > 0 then,

i. The value is decreased by 1.

ii. The stream is resumed from where the exception was called, retrying the action.

b. If the exception exists and the mapped value is == 0 then the stream evaluation stops.

c. If the exception does not exist then we handle the exception using han.

Internal