2{-# LANGUAGE ConstraintKinds #-}
3{-# LANGUAGE FlexibleContexts #-}
4{-# LANGUAGE FlexibleInstances #-}
5{-# LANGUAGE GeneralizedNewtypeDeriving#-}
6{-# LANGUAGE InstanceSigs #-}
7{-# LANGUAGE LambdaCase #-}
8{-# LANGUAGE MultiParamTypeClasses #-}
9{-# LANGUAGE ScopedTypeVariables #-}
10{-# LANGUAGE UndecidableInstances #-} -- XXX
13-- Module : Streamly.Streams.Async
14-- Copyright : (c) 2017 Harendra Kumar
17-- Maintainer : harendra.kumar@gmail.com
18-- Stability : experimental
22module Streamly.Streams.Async
39import Control.Concurrent (myThreadId)
40import Control.Monad (ap)
41import Control.Monad.Base (MonadBase(..), liftBaseDefault)
42import Control.Monad.Catch (MonadThrow, throwM)
43import Control.Concurrent.MVar (newEmptyMVar)
44-- import Control.Monad.Error.Class (MonadError(..))
45import Control.Monad.IO.Class (MonadIO(..))
46import Control.Monad.Reader.Class (MonadReader(..))
47import Control.Monad.State.Class (MonadState(..))
48import Control.Monad.Trans.Class (MonadTrans(lift))
49import Data.Concurrent.Queue.MichaelScott (LinkedQueue, newQ, nullQ, tryPopR)
50import Data.IORef (IORef, newIORef, readIORef)
51import Data.Maybe (fromJust)
52import Data.Semigroup (Semigroup(..))
54import Prelude hiding (map)
55import qualified Data.Set as S
57import Streamly.Atomics (atomicModifyIORefCAS)
58import Streamly.Streams.SVar (fromSVar)
59import Streamly.Streams.Serial (map)
61import Streamly.Streams.StreamK
62 (IsStream(..), Stream, mkStream, foldStream, adapt, foldStreamShared,
64import qualified Streamly.Streams.StreamK as K
66#include "Instances.hs"
68-------------------------------------------------------------------------------
70-------------------------------------------------------------------------------
72{-# INLINE workLoopLIFO #-}
80workLoopLIFO q st sv winfo = run
87 Nothing -> liftIO $ sendStop sv winfo
88 Just m -> foldStreamSVar sv st yieldk single run m
91 res <- liftIO $ sendYield sv winfo (ChildYield a)
92 if res then run else liftIO $ sendStop sv winfo
95 res <- liftIO $ sendYield sv winfo (ChildYield a)
97 then foldStreamSVar sv st yieldk single run r
102 dequeue = liftIO $ atomicModifyIORefCAS q $ \case
104 x : xs -> (xs, Just x)
106-- We duplicate workLoop for yield limit and no limit cases because it has
107-- around 40% performance overhead in the worst case.
109-- XXX we can pass yinfo directly as an argument here so that we do not have to
110-- make a check every time.
111{-# INLINE workLoopLIFOLimited #-}
114 => IORef [Stream m a]
119workLoopLIFOLimited q st sv winfo = run
126 Nothing -> liftIO $ sendStop sv winfo
128 -- XXX This is just a best effort minimization of concurrency
129 -- to the yield limit. If the stream is made of concurrent
130 -- streams we do not reserve the yield limit in the constituent
131 -- streams before executing the action. This can be done
132 -- though, by sharing the yield limit ref with downstream
133 -- actions via state passing. Just a todo.
134 yieldLimitOk <- liftIO $ decrementYieldLimit sv
137 let stop = liftIO (incrementYieldLimit sv) >> run
138 foldStreamSVar sv st yieldk single stop m
139 -- Avoid any side effects, undo the yield limit decrement if we
140 -- never yielded anything.
143 incrementYieldLimit sv
147 res <- liftIO $ sendYield sv winfo (ChildYield a)
148 if res then run else liftIO $ sendStop sv winfo
150 -- XXX can we pass on the yield limit downstream to limit the concurrency
151 -- of constituent streams.
153 res <- liftIO $ sendYield sv winfo (ChildYield a)
154 yieldLimitOk <- liftIO $ decrementYieldLimit sv
155 let stop = liftIO (incrementYieldLimit sv) >> run
156 if res && yieldLimitOk
157 then foldStreamSVar sv st yieldk single stop r
159 incrementYieldLimit sv
163 dequeue = liftIO $ atomicModifyIORefCAS q $ \case
165 x : xs -> (xs, Just x)
167-------------------------------------------------------------------------------
169-------------------------------------------------------------------------------
171-- XXX we can remove sv as it is derivable from st
173{-# INLINE workLoopFIFO #-}
176 => LinkedQueue (Stream m a)
181workLoopFIFO q st sv winfo = run
186 work <- liftIO $ tryPopR q
188 Nothing -> liftIO $ sendStop sv winfo
189 Just m -> foldStreamSVar sv st yieldk single run m
192 res <- liftIO $ sendYield sv winfo (ChildYield a)
193 if res then run else liftIO $ sendStop sv winfo
196 res <- liftIO $ sendYield sv winfo (ChildYield a)
198 then foldStreamSVar sv st yieldk single run r
203{-# INLINE workLoopFIFOLimited #-}
206 => LinkedQueue (Stream m a)
211workLoopFIFOLimited q st sv winfo = run
216 work <- liftIO $ tryPopR q
218 Nothing -> liftIO $ sendStop sv winfo
220 yieldLimitOk <- liftIO $ decrementYieldLimit sv
223 let stop = liftIO (incrementYieldLimit sv) >> run
224 foldStreamSVar sv st yieldk single stop m
227 incrementYieldLimit sv
231 res <- liftIO $ sendYield sv winfo (ChildYield a)
232 if res then run else liftIO $ sendStop sv winfo
235 res <- liftIO $ sendYield sv winfo (ChildYield a)
236 yieldLimitOk <- liftIO $ decrementYieldLimit sv
237 let stop = liftIO (incrementYieldLimit sv) >> run
238 if res && yieldLimitOk
239 then foldStreamSVar sv st yieldk single stop r
241 incrementYieldLimit sv
245-------------------------------------------------------------------------------
247-- This code belongs in SVar.hs but is kept here for perf reasons
248-------------------------------------------------------------------------------
250-- XXX we have this function in this file because passing runStreamLIFO as a
251-- function argument to this function results in a perf degradation of more
252-- than 10%. Need to investigate what the root cause is.
253-- Interestingly, the same thing does not make any difference for Ahead.
254getLifoSVar :: forall m a. MonadAsync m
255 => State Stream m a -> RunInIO m -> IO (SVar Stream m a)
256getLifoSVar st mrun = do
257 outQ <- newIORef ([], 0)
258 outQMv <- newEmptyMVar
260 wfw <- newIORef False
261 running <- newIORef S.empty
263 yl <- case getYieldLimit st of
264 Nothing -> return Nothing
265 Just x -> Just <$> newIORef x
266 rateInfo <- getYieldRateInfo st
268 stats <- newSVarStats
271 let isWorkFinished _ = null <$> readIORef q
273 let isWorkFinishedLimited sv = do
275 case remainingWork sv of
279 Nothing -> return False
280 qEmpty <- null <$> readIORef q
281 return $ qEmpty || yieldsDone
283 let getSVar :: SVar Stream m a
284 -> (SVar Stream m a -> m [ChildEvent a])
285 -> (SVar Stream m a -> m Bool)
286 -> (SVar Stream m a -> IO Bool)
287 -> (IORef [Stream m a]
293 getSVar sv readOutput postProc workDone wloop = SVar
296 , maxBufferLimit = getMaxBuffer st
297 , maxWorkerLimit = getMaxThreads st
298 , yieldRateInfo = rateInfo
299 , outputDoorBell = outQMv
300 , readOutputQ = readOutput sv
301 , postProcess = postProc sv
302 , workerThreads = running
303 , workLoop = wloop q st{streamVar = Just sv} sv
304 , enqueue = enqueueLIFO sv q
305 , isWorkDone = workDone sv
306 , isQueueDone = workDone sv
308 , svarStyle = AsyncVar
309 , svarStopStyle = StopNone
310 , svarStopBy = undefined
312 , workerCount = active
313 , accountThread = delThread sv
314 , workerStopMVar = undefined
316 , svarInspectMode = getInspectMode st
318 , aheadWorkQueue = undefined
319 , outputHeap = undefined
324 case getStreamRate st of
326 case getYieldLimit st of
327 Nothing -> getSVar sv readOutputQBounded
331 Just _ -> getSVar sv readOutputQBounded
333 isWorkFinishedLimited
336 case getYieldLimit st of
337 Nothing -> getSVar sv readOutputQPaced
341 Just _ -> getSVar sv readOutputQPaced
343 isWorkFinishedLimited
347getFifoSVar :: forall m a. MonadAsync m
348 => State Stream m a -> RunInIO m -> IO (SVar Stream m a)
349getFifoSVar st mrun = do
350 outQ <- newIORef ([], 0)
351 outQMv <- newEmptyMVar
353 wfw <- newIORef False
354 running <- newIORef S.empty
356 yl <- case getYieldLimit st of
357 Nothing -> return Nothing
358 Just x -> Just <$> newIORef x
359 rateInfo <- getYieldRateInfo st
361 stats <- newSVarStats
364 let isWorkFinished _ = nullQ q
365 let isWorkFinishedLimited sv = do
367 case remainingWork sv of
371 Nothing -> return False
373 return $ qEmpty || yieldsDone
375 let getSVar :: SVar Stream m a
376 -> (SVar Stream m a -> m [ChildEvent a])
377 -> (SVar Stream m a -> m Bool)
378 -> (SVar Stream m a -> IO Bool)
379 -> (LinkedQueue (Stream m a)
385 getSVar sv readOutput postProc workDone wloop = SVar
388 , maxBufferLimit = getMaxBuffer st
389 , maxWorkerLimit = getMaxThreads st
390 , yieldRateInfo = rateInfo
391 , outputDoorBell = outQMv
392 , readOutputQ = readOutput sv
393 , postProcess = postProc sv
394 , workerThreads = running
395 , workLoop = wloop q st{streamVar = Just sv} sv
396 , enqueue = enqueueFIFO sv q
397 , isWorkDone = workDone sv
398 , isQueueDone = workDone sv
400 , svarStyle = WAsyncVar
401 , svarStopStyle = StopNone
402 , svarStopBy = undefined
404 , workerCount = active
405 , accountThread = delThread sv
406 , workerStopMVar = undefined
408 , svarInspectMode = getInspectMode st
410 , aheadWorkQueue = undefined
411 , outputHeap = undefined
416 case getStreamRate st of
418 case getYieldLimit st of
419 Nothing -> getSVar sv readOutputQBounded
423 Just _ -> getSVar sv readOutputQBounded
425 isWorkFinishedLimited
428 case getYieldLimit st of
429 Nothing -> getSVar sv readOutputQPaced
433 Just _ -> getSVar sv readOutputQPaced
435 isWorkFinishedLimited
439{-# INLINABLE newAsyncVar #-}
440newAsyncVar :: MonadAsync m
441 => State Stream m a -> Stream m a -> m (SVar Stream m a)
443 mrun <- captureMonadState
444 sv <- liftIO $ getLifoSVar st mrun
447-- XXX Get rid of this?
448-- | Make a stream asynchronous, triggers the computation and returns a stream
449-- in the underlying monad representing the output generated by the original
450-- computation. The returned action is exhaustible and must be drained once. If
451-- not drained fully we may have a thread blocked forever and once exhausted it
452-- will always return 'empty'.
455{-# INLINABLE mkAsync #-}
456mkAsync :: (IsStream t, MonadAsync m) => t m a -> m (t m a)
457mkAsync m = fmap fromSVar (newAsyncVar defState (toStream m))
459{-# INLINABLE mkAsync' #-}
460mkAsync' :: (IsStream t, MonadAsync m) => State Stream m a -> t m a -> m (t m a)
461mkAsync' st m = fmap fromSVar (newAsyncVar st (toStream m))
463-- | Create a new SVar and enqueue one stream computation on it.
464{-# INLINABLE newWAsyncVar #-}
465newWAsyncVar :: MonadAsync m
466 => State Stream m a -> Stream m a -> m (SVar Stream m a)
467newWAsyncVar st m = do
468 mrun <- captureMonadState
469 sv <- liftIO $ getFifoSVar st mrun
472------------------------------------------------------------------------------
473-- Running streams concurrently
474------------------------------------------------------------------------------
476-- Concurrency rate control.
478-- Our objective is to create more threads on demand if the consumer is running
479-- faster than us. As soon as we encounter a concurrent composition we create a
480-- push pull pair of threads. We use an SVar for communication between the
481-- consumer, pulling from the SVar and the producer who is pushing to the SVar.
482-- The producer creates more threads if the SVar drains and becomes empty, that
483-- is the consumer is running faster.
485-- XXX Note 1: This mechanism can be problematic if the initial production
486-- latency is high, we may end up creating too many threads. So we need some
487-- way to monitor and use the latency as well. Having a limit on the dispatches
488-- (programmer controlled) may also help.
490-- TBD Note 2: We may want to run computations at the lower level of the
491-- composition tree serially even when they are composed using a parallel
492-- combinator. We can use 'serial' in place of 'async' and 'wSerial' in
493-- place of 'wAsync'. If we find that an SVar immediately above a computation
494-- gets drained empty we can switch to parallelizing the computation. For that
495-- we can use a state flag to fork the rest of the computation at any point of
496-- time inside the Monad bind operation if the consumer is running at a faster
499-- TBD Note 3: the binary operation ('parallel') composition allows us to
500-- dispatch a chunkSize of only 1. If we have to dispatch in arbitrary
501-- chunksizes we will need to compose the parallel actions using a data
502-- constructor (A Free container) instead so that we can divide it in chunks of
503-- arbitrary size before dispatching. If the stream is composed of
504-- hierarchically composed grains of different sizes then we can always switch
505-- to a desired granularity depending on the consumer speed.
507-- TBD Note 4: for pure work (when we are not in the IO monad) we can divide it
508-- into just the number of CPUs.
510-- | Join two computations on the currently running 'SVar' queue for concurrent
511-- execution. When we are using parallel composition, an SVar is passed around
512-- as a state variable. We try to schedule a new parallel computation on the
513-- SVar passed to us. The first time, when no SVar exists, a new SVar is
514-- created. Subsequently, 'joinStreamVarAsync' may get called when a computation
515-- already scheduled on the SVar is further evaluated. For example, when (a
516-- `parallel` b) is evaluated it calls a 'joinStreamVarAsync' to put 'a' and 'b' on
517-- the current scheduler queue.
519-- The 'SVarStyle' required by the current composition context is passed as one
520-- of the parameters. If the scheduling and composition style of the new
521-- computation being scheduled is different than the style of the current SVar,
522-- then we create a new SVar and schedule it on that. The newly created SVar
523-- joins as one of the computations on the current SVar queue.
525-- Cases when we need to switch to a new SVar:
527-- * (x `parallel` y) `parallel` (t `parallel` u) -- all of them get scheduled on the same SVar
528-- * (x `parallel` y) `parallel` (t `async` u) -- @t@ and @u@ get scheduled on a new child SVar
529-- because of the scheduling policy change.
530-- * if we 'adapt' a stream of type 'async' to a stream of type
531-- 'Parallel', we create a new SVar at the transitioning bind.
532-- * When the stream is switching from disjunctive composition to conjunctive
533-- composition and vice-versa we create a new SVar to isolate the scheduling
536forkSVarAsync :: (IsStream t, MonadAsync m)
537 => SVarStyle -> t m a -> t m a -> t m a
538forkSVarAsync style m1 m2 = mkStream $ \st stp sng yld -> do
540 AsyncVar -> newAsyncVar st (concurrently (toStream m1) (toStream m2))
541 WAsyncVar -> newWAsyncVar st (concurrently (toStream m1) (toStream m2))
542 _ -> error "illegal svar type"
543 foldStream st stp sng yld $ fromSVar sv
545 concurrently ma mb = mkStream $ \st stp sng yld -> do
546 liftIO $ enqueue (fromJust $ streamVar st) mb
547 foldStreamShared st stp sng yld ma
549{-# INLINE joinStreamVarAsync #-}
550joinStreamVarAsync :: (IsStream t, MonadAsync m)
551 => SVarStyle -> t m a -> t m a -> t m a
552joinStreamVarAsync style m1 m2 = mkStream $ \st stp sng yld ->
554 Just sv | svarStyle sv == style -> do
555 liftIO $ enqueue sv (toStream m2)
556 foldStreamShared st stp sng yld m1
557 _ -> foldStreamShared st stp sng yld (forkSVarAsync style m1 m2)
559------------------------------------------------------------------------------
560-- Semigroup and Monoid style compositions for parallel actions
561------------------------------------------------------------------------------
563-- | Polymorphic version of the 'Semigroup' operation '<>' of 'AsyncT'.
564-- Merges two streams possibly concurrently, preferring the
565-- elements from the left one when available.
569async :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a
570async = joinStreamVarAsync AsyncVar
575{-# DEPRECATED (<|) "Please use 'async' instead." #-}
577(<|) :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a
580-- IMPORTANT: using a monomorphically typed and SPECIALIZED consMAsync makes a
581-- huge difference in the performance of consM in IsStream instance even we
582-- have a SPECIALIZE in the instance.
584-- | XXX we can implement it more efficienty by directly implementing instead
585-- of combining streams using async.
586{-# INLINE consMAsync #-}
587{-# SPECIALIZE consMAsync :: IO a -> AsyncT IO a -> AsyncT IO a #-}
588consMAsync :: MonadAsync m => m a -> AsyncT m a -> AsyncT m a
589consMAsync m r = fromStream $ K.yieldM m `async` (toStream r)
591------------------------------------------------------------------------------
593------------------------------------------------------------------------------
595-- | Deep async composition or async composition with depth first traversal. In
596-- a left to right 'Semigroup' composition it tries to yield elements from the
597-- left stream as long as it can, but it can run the right stream in parallel
598-- if it needs to, based on demand. The right stream can be run if the left
599-- stream blocks on IO or cannot produce elements fast enough for the consumer.
602-- main = ('toList' . 'asyncly' $ (fromFoldable [1,2]) \<> (fromFoldable [3,4])) >>= print
608-- Any exceptions generated by a constituent stream are propagated to the
609-- output stream. The output and exceptions from a single stream are guaranteed
610-- to arrive in the same order in the resulting stream as they were generated
611-- in the input stream. However, the relative ordering of elements from
612-- different streams in the resulting stream can vary depending on scheduling
613-- and generation delays.
615-- Similarly, the monad instance of 'AsyncT' /may/ run each iteration
616-- concurrently based on demand. More concurrent iterations are started only
617-- if the previous iterations are not able to produce enough output for the
622-- import qualified "Streamly.Prelude" as S
623-- import Control.Concurrent
625-- main = 'drain' . 'asyncly' $ do
626-- n <- return 3 \<\> return 2 \<\> return 1
628-- threadDelay (n * 1000000)
629-- myThreadId >>= \\tid -> putStrLn (show tid ++ ": Delay " ++ show n)
632-- ThreadId 40: Delay 1
633-- ThreadId 39: Delay 2
634-- ThreadId 38: Delay 3
637-- All iterations may run in the same thread if they do not block.
639-- Note that async composition with depth first traversal can be used to
640-- combine infinite number of streams as it explores only a bounded number of
644newtype AsyncT m a = AsyncT {getAsyncT :: Stream m a}
645 deriving (MonadTrans)
647-- | A demand driven left biased parallely composing IO stream of elements of
648-- type @a@. See 'AsyncT' documentation for more details.
651type Async = AsyncT IO
653-- | Fix the type of a polymorphic stream as 'AsyncT'.
656asyncly :: IsStream t => AsyncT m a -> t m a
659instance IsStream AsyncT where
665------------------------------------------------------------------------------
667------------------------------------------------------------------------------
669-- Monomorphically typed version of "async" for better performance of Semigroup
671{-# INLINE mappendAsync #-}
672{-# SPECIALIZE mappendAsync :: AsyncT IO a -> AsyncT IO a -> AsyncT IO a #-}
673mappendAsync :: MonadAsync m => AsyncT m a -> AsyncT m a -> AsyncT m a
674mappendAsync m1 m2 = fromStream $ async (toStream m1) (toStream m2)
676instance MonadAsync m => Semigroup (AsyncT m a) where
679------------------------------------------------------------------------------
681------------------------------------------------------------------------------
683instance MonadAsync m => Monoid (AsyncT m a) where
687------------------------------------------------------------------------------
689------------------------------------------------------------------------------
691-- GHC: if we change the implementation of bindWith with arguments in a
692-- different order we see a significant performance degradation (~2x).
693{-# INLINE bindAsync #-}
694{-# SPECIALIZE bindAsync :: AsyncT IO a -> (a -> AsyncT IO b) -> AsyncT IO b #-}
695bindAsync :: MonadAsync m => AsyncT m a -> (a -> AsyncT m b) -> AsyncT m b
696bindAsync m f = fromStream $ K.bindWith async (adapt m) (\a -> adapt $ f a)
698-- GHC: if we specify arguments in the definition of (>>=) we see a significant
699-- performance degradation (~2x).
700instance MonadAsync m => Monad (AsyncT m) where
704{-# INLINE apAsync #-}
705{-# SPECIALIZE apAsync :: AsyncT IO (a -> b) -> AsyncT IO a -> AsyncT IO b #-}
706apAsync :: MonadAsync m => AsyncT m (a -> b) -> AsyncT m a -> AsyncT m b
707apAsync mf m = ap (adapt mf) (adapt m)
709instance (Monad m, MonadAsync m) => Applicative (AsyncT m) where
710 pure = AsyncT . K.yield
713------------------------------------------------------------------------------
715------------------------------------------------------------------------------
717MONAD_COMMON_INSTANCES(AsyncT, MONADPARALLEL)
719------------------------------------------------------------------------------
721------------------------------------------------------------------------------
723-- | XXX we can implement it more efficienty by directly implementing instead
724-- of combining streams using wAsync.
725{-# INLINE consMWAsync #-}
726{-# SPECIALIZE consMWAsync :: IO a -> WAsyncT IO a -> WAsyncT IO a #-}
727consMWAsync :: MonadAsync m => m a -> WAsyncT m a -> WAsyncT m a
728consMWAsync m r = fromStream $ K.yieldM m `wAsync` (toStream r)
730-- | Polymorphic version of the 'Semigroup' operation '<>' of 'WAsyncT'.
731-- Merges two streams concurrently choosing elements from both fairly.
735wAsync :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a
736wAsync = joinStreamVarAsync WAsyncVar
738-- | Wide async composition or async composition with breadth first traversal.
739-- The Semigroup instance of 'WAsyncT' concurrently /traverses/ the composed
740-- streams using a depth first travesal or in a round robin fashion, yielding
741-- elements from both streams alternately.
744-- main = ('toList' . 'wAsyncly' $ (fromFoldable [1,2]) \<> (fromFoldable [3,4])) >>= print
750-- Any exceptions generated by a constituent stream are propagated to the
751-- output stream. The output and exceptions from a single stream are guaranteed
752-- to arrive in the same order in the resulting stream as they were generated
753-- in the input stream. However, the relative ordering of elements from
754-- different streams in the resulting stream can vary depending on scheduling
755-- and generation delays.
757-- Similarly, the 'Monad' instance of 'WAsyncT' runs /all/ iterations fairly
758-- concurrently using a round robin scheduling.
762-- import qualified "Streamly.Prelude" as S
763-- import Control.Concurrent
765-- main = 'drain' . 'wAsyncly' $ do
766-- n <- return 3 \<\> return 2 \<\> return 1
768-- threadDelay (n * 1000000)
769-- myThreadId >>= \\tid -> putStrLn (show tid ++ ": Delay " ++ show n)
772-- ThreadId 40: Delay 1
773-- ThreadId 39: Delay 2
774-- ThreadId 38: Delay 3
777-- Unlike 'AsyncT' all iterations are guaranteed to run fairly
778-- concurrently, unconditionally.
780-- Note that async composition with breadth first traversal can only combine a
781-- finite number of streams as it needs to retain state for each unfinished
785newtype WAsyncT m a = WAsyncT {getWAsyncT :: Stream m a}
786 deriving (MonadTrans)
788-- | A round robin parallely composing IO stream of elements of type @a@.
789-- See 'WAsyncT' documentation for more details.
792type WAsync = WAsyncT IO
794-- | Fix the type of a polymorphic stream as 'WAsyncT'.
797wAsyncly :: IsStream t => WAsyncT m a -> t m a
800instance IsStream WAsyncT where
801 toStream = getWAsyncT
806------------------------------------------------------------------------------
808------------------------------------------------------------------------------
810{-# INLINE mappendWAsync #-}
811{-# SPECIALIZE mappendWAsync :: WAsyncT IO a -> WAsyncT IO a -> WAsyncT IO a #-}
812mappendWAsync :: MonadAsync m => WAsyncT m a -> WAsyncT m a -> WAsyncT m a
813mappendWAsync m1 m2 = fromStream $ wAsync (toStream m1) (toStream m2)
815instance MonadAsync m => Semigroup (WAsyncT m a) where
818------------------------------------------------------------------------------
820------------------------------------------------------------------------------
822instance MonadAsync m => Monoid (WAsyncT m a) where
826------------------------------------------------------------------------------
828------------------------------------------------------------------------------
830-- GHC: if we change the implementation of bindWith with arguments in a
831-- different order we see a significant performance degradation (~2x).
832{-# INLINE bindWAsync #-}
833{-# SPECIALIZE bindWAsync :: WAsyncT IO a -> (a -> WAsyncT IO b) -> WAsyncT IO b #-}
834bindWAsync :: MonadAsync m => WAsyncT m a -> (a -> WAsyncT m b) -> WAsyncT m b
835bindWAsync m f = fromStream $ K.bindWith wAsync (adapt m) (\a -> adapt $ f a)
837-- GHC: if we specify arguments in the definition of (>>=) we see a significant
838-- performance degradation (~2x).
839instance MonadAsync m => Monad (WAsyncT m) where
843{-# INLINE apWAsync #-}
844{-# SPECIALIZE apWAsync :: WAsyncT IO (a -> b) -> WAsyncT IO a -> WAsyncT IO b #-}
845apWAsync :: MonadAsync m => WAsyncT m (a -> b) -> WAsyncT m a -> WAsyncT m b
846apWAsync mf m = ap (adapt mf) (adapt m)
848instance (Monad m, MonadAsync m) => Applicative (WAsyncT m) where
849 pure = WAsyncT . K.yield
852------------------------------------------------------------------------------
854------------------------------------------------------------------------------
856MONAD_COMMON_INSTANCES(WAsyncT, MONADPARALLEL)
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