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.Streams.SVar (fromSVar)
58import Streamly.Streams.Serial (map)
60import Streamly.Streams.StreamK
61 (IsStream(..), Stream, mkStream, foldStream, adapt, foldStreamShared,
63import qualified Streamly.Streams.StreamK as K
65#include "Instances.hs"
67-------------------------------------------------------------------------------
69-------------------------------------------------------------------------------
71{-# INLINE workLoopLIFO #-}
79workLoopLIFO q st sv winfo = run
86 Nothing -> liftIO $ sendStop sv winfo
87 Just m -> foldStreamSVar sv st yieldk single run m
90 res <- liftIO $ sendYield sv winfo (ChildYield a)
91 if res then run else liftIO $ sendStop sv winfo
94 res <- liftIO $ sendYield sv winfo (ChildYield a)
96 then foldStreamSVar sv st yieldk single run r
101 dequeue = liftIO $ atomicModifyIORefCAS q $ \case
103 x : xs -> (xs, Just x)
105-- We duplicate workLoop for yield limit and no limit cases because it has
106-- around 40% performance overhead in the worst case.
108-- XXX we can pass yinfo directly as an argument here so that we do not have to
109-- make a check every time.
110{-# INLINE workLoopLIFOLimited #-}
113 => IORef [Stream m a]
118workLoopLIFOLimited q st sv winfo = run
125 Nothing -> liftIO $ sendStop sv winfo
127 -- XXX This is just a best effort minimization of concurrency
128 -- to the yield limit. If the stream is made of concurrent
129 -- streams we do not reserve the yield limit in the constituent
130 -- streams before executing the action. This can be done
131 -- though, by sharing the yield limit ref with downstream
132 -- actions via state passing. Just a todo.
133 yieldLimitOk <- liftIO $ decrementYieldLimit sv
136 let stop = liftIO (incrementYieldLimit sv) >> run
137 foldStreamSVar sv st yieldk single stop m
138 -- Avoid any side effects, undo the yield limit decrement if we
139 -- never yielded anything.
142 incrementYieldLimit sv
146 res <- liftIO $ sendYield sv winfo (ChildYield a)
147 if res then run else liftIO $ sendStop sv winfo
149 -- XXX can we pass on the yield limit downstream to limit the concurrency
150 -- of constituent streams.
152 res <- liftIO $ sendYield sv winfo (ChildYield a)
153 yieldLimitOk <- liftIO $ decrementYieldLimit sv
154 let stop = liftIO (incrementYieldLimit sv) >> run
155 if res && yieldLimitOk
156 then foldStreamSVar sv st yieldk single stop r
158 incrementYieldLimit sv
162 dequeue = liftIO $ atomicModifyIORefCAS q $ \case
164 x : xs -> (xs, Just x)
166-------------------------------------------------------------------------------
168-------------------------------------------------------------------------------
170-- XXX we can remove sv as it is derivable from st
172{-# INLINE workLoopFIFO #-}
175 => LinkedQueue (Stream m a)
180workLoopFIFO q st sv winfo = run
185 work <- liftIO $ tryPopR q
187 Nothing -> liftIO $ sendStop sv winfo
188 Just m -> foldStreamSVar sv st yieldk single run m
191 res <- liftIO $ sendYield sv winfo (ChildYield a)
192 if res then run else liftIO $ sendStop sv winfo
195 res <- liftIO $ sendYield sv winfo (ChildYield a)
197 then foldStreamSVar sv st yieldk single run r
202{-# INLINE workLoopFIFOLimited #-}
205 => LinkedQueue (Stream m a)
210workLoopFIFOLimited q st sv winfo = run
215 work <- liftIO $ tryPopR q
217 Nothing -> liftIO $ sendStop sv winfo
219 yieldLimitOk <- liftIO $ decrementYieldLimit sv
222 let stop = liftIO (incrementYieldLimit sv) >> run
223 foldStreamSVar sv st yieldk single stop m
226 incrementYieldLimit sv
230 res <- liftIO $ sendYield sv winfo (ChildYield a)
231 if res then run else liftIO $ sendStop sv winfo
234 res <- liftIO $ sendYield sv winfo (ChildYield a)
235 yieldLimitOk <- liftIO $ decrementYieldLimit sv
236 let stop = liftIO (incrementYieldLimit sv) >> run
237 if res && yieldLimitOk
238 then foldStreamSVar sv st yieldk single stop r
240 incrementYieldLimit sv
244-------------------------------------------------------------------------------
246-- This code belongs in SVar.hs but is kept here for perf reasons
247-------------------------------------------------------------------------------
249-- XXX we have this function in this file because passing runStreamLIFO as a
250-- function argument to this function results in a perf degradation of more
251-- than 10%. Need to investigate what the root cause is.
252-- Interestingly, the same thing does not make any difference for Ahead.
253getLifoSVar :: forall m a. MonadAsync m
254 => State Stream m a -> RunInIO m -> IO (SVar Stream m a)
255getLifoSVar st mrun = do
256 outQ <- newIORef ([], 0)
257 outQMv <- newEmptyMVar
259 wfw <- newIORef False
260 running <- newIORef S.empty
262 yl <- case getYieldLimit st of
263 Nothing -> return Nothing
264 Just x -> Just <$> newIORef x
265 rateInfo <- getYieldRateInfo st
267 stats <- newSVarStats
270 let isWorkFinished _ = null <$> readIORef q
272 let isWorkFinishedLimited sv = do
274 case remainingWork sv of
278 Nothing -> return False
279 qEmpty <- null <$> readIORef q
280 return $ qEmpty || yieldsDone
282 let getSVar :: SVar Stream m a
283 -> (SVar Stream m a -> m [ChildEvent a])
284 -> (SVar Stream m a -> m Bool)
285 -> (SVar Stream m a -> IO Bool)
286 -> (IORef [Stream m a]
292 getSVar sv readOutput postProc workDone wloop = SVar
295 , maxBufferLimit = getMaxBuffer st
296 , maxWorkerLimit = getMaxThreads st
297 , yieldRateInfo = rateInfo
298 , outputDoorBell = outQMv
299 , readOutputQ = readOutput sv
300 , postProcess = postProc sv
301 , workerThreads = running
302 , workLoop = wloop q st{streamVar = Just sv} sv
303 , enqueue = enqueueLIFO sv q
304 , isWorkDone = workDone sv
305 , isQueueDone = workDone sv
307 , svarStyle = AsyncVar
309 , workerCount = active
310 , accountThread = delThread sv
311 , workerStopMVar = undefined
313 , svarInspectMode = getInspectMode st
315 , aheadWorkQueue = undefined
316 , outputHeap = undefined
321 case getStreamRate st of
323 case getYieldLimit st of
324 Nothing -> getSVar sv readOutputQBounded
328 Just _ -> getSVar sv readOutputQBounded
330 isWorkFinishedLimited
333 case getYieldLimit st of
334 Nothing -> getSVar sv readOutputQPaced
338 Just _ -> getSVar sv readOutputQPaced
340 isWorkFinishedLimited
344getFifoSVar :: forall m a. MonadAsync m
345 => State Stream m a -> RunInIO m -> IO (SVar Stream m a)
346getFifoSVar st mrun = do
347 outQ <- newIORef ([], 0)
348 outQMv <- newEmptyMVar
350 wfw <- newIORef False
351 running <- newIORef S.empty
353 yl <- case getYieldLimit st of
354 Nothing -> return Nothing
355 Just x -> Just <$> newIORef x
356 rateInfo <- getYieldRateInfo st
358 stats <- newSVarStats
361 let isWorkFinished _ = nullQ q
362 let isWorkFinishedLimited sv = do
364 case remainingWork sv of
368 Nothing -> return False
370 return $ qEmpty || yieldsDone
372 let getSVar :: SVar Stream m a
373 -> (SVar Stream m a -> m [ChildEvent a])
374 -> (SVar Stream m a -> m Bool)
375 -> (SVar Stream m a -> IO Bool)
376 -> (LinkedQueue (Stream m a)
382 getSVar sv readOutput postProc workDone wloop = SVar
385 , maxBufferLimit = getMaxBuffer st
386 , maxWorkerLimit = getMaxThreads st
387 , yieldRateInfo = rateInfo
388 , outputDoorBell = outQMv
389 , readOutputQ = readOutput sv
390 , postProcess = postProc sv
391 , workerThreads = running
392 , workLoop = wloop q st{streamVar = Just sv} sv
393 , enqueue = enqueueFIFO sv q
394 , isWorkDone = workDone sv
395 , isQueueDone = workDone sv
397 , svarStyle = WAsyncVar
399 , workerCount = active
400 , accountThread = delThread sv
401 , workerStopMVar = undefined
403 , svarInspectMode = getInspectMode st
405 , aheadWorkQueue = undefined
406 , outputHeap = undefined
411 case getStreamRate st of
413 case getYieldLimit st of
414 Nothing -> getSVar sv readOutputQBounded
418 Just _ -> getSVar sv readOutputQBounded
420 isWorkFinishedLimited
423 case getYieldLimit st of
424 Nothing -> getSVar sv readOutputQPaced
428 Just _ -> getSVar sv readOutputQPaced
430 isWorkFinishedLimited
434{-# INLINABLE newAsyncVar #-}
435newAsyncVar :: MonadAsync m
436 => State Stream m a -> Stream m a -> m (SVar Stream m a)
438 mrun <- captureMonadState
439 sv <- liftIO $ getLifoSVar st mrun
442-- XXX Get rid of this?
443-- | Make a stream asynchronous, triggers the computation and returns a stream
444-- in the underlying monad representing the output generated by the original
445-- computation. The returned action is exhaustible and must be drained once. If
446-- not drained fully we may have a thread blocked forever and once exhausted it
447-- will always return 'empty'.
450{-# INLINABLE mkAsync #-}
451mkAsync :: (IsStream t, MonadAsync m) => t m a -> m (t m a)
452mkAsync m = fmap fromSVar (newAsyncVar defState (toStream m))
454{-# INLINABLE mkAsync' #-}
455mkAsync' :: (IsStream t, MonadAsync m) => State Stream m a -> t m a -> m (t m a)
456mkAsync' st m = fmap fromSVar (newAsyncVar st (toStream m))
458-- | Create a new SVar and enqueue one stream computation on it.
459{-# INLINABLE newWAsyncVar #-}
460newWAsyncVar :: MonadAsync m
461 => State Stream m a -> Stream m a -> m (SVar Stream m a)
462newWAsyncVar st m = do
463 mrun <- captureMonadState
464 sv <- liftIO $ getFifoSVar st mrun
467------------------------------------------------------------------------------
468-- Running streams concurrently
469------------------------------------------------------------------------------
471-- Concurrency rate control.
473-- Our objective is to create more threads on demand if the consumer is running
474-- faster than us. As soon as we encounter a concurrent composition we create a
475-- push pull pair of threads. We use an SVar for communication between the
476-- consumer, pulling from the SVar and the producer who is pushing to the SVar.
477-- The producer creates more threads if the SVar drains and becomes empty, that
478-- is the consumer is running faster.
480-- XXX Note 1: This mechanism can be problematic if the initial production
481-- latency is high, we may end up creating too many threads. So we need some
482-- way to monitor and use the latency as well. Having a limit on the dispatches
483-- (programmer controlled) may also help.
485-- TBD Note 2: We may want to run computations at the lower level of the
486-- composition tree serially even when they are composed using a parallel
487-- combinator. We can use 'serial' in place of 'async' and 'wSerial' in
488-- place of 'wAsync'. If we find that an SVar immediately above a computation
489-- gets drained empty we can switch to parallelizing the computation. For that
490-- we can use a state flag to fork the rest of the computation at any point of
491-- time inside the Monad bind operation if the consumer is running at a faster
494-- TBD Note 3: the binary operation ('parallel') composition allows us to
495-- dispatch a chunkSize of only 1. If we have to dispatch in arbitrary
496-- chunksizes we will need to compose the parallel actions using a data
497-- constructor (A Free container) instead so that we can divide it in chunks of
498-- arbitrary size before dispatching. If the stream is composed of
499-- hierarchically composed grains of different sizes then we can always switch
500-- to a desired granularity depending on the consumer speed.
502-- TBD Note 4: for pure work (when we are not in the IO monad) we can divide it
503-- into just the number of CPUs.
505-- | Join two computations on the currently running 'SVar' queue for concurrent
506-- execution. When we are using parallel composition, an SVar is passed around
507-- as a state variable. We try to schedule a new parallel computation on the
508-- SVar passed to us. The first time, when no SVar exists, a new SVar is
509-- created. Subsequently, 'joinStreamVarAsync' may get called when a computation
510-- already scheduled on the SVar is further evaluated. For example, when (a
511-- `parallel` b) is evaluated it calls a 'joinStreamVarAsync' to put 'a' and 'b' on
512-- the current scheduler queue.
514-- The 'SVarStyle' required by the current composition context is passed as one
515-- of the parameters. If the scheduling and composition style of the new
516-- computation being scheduled is different than the style of the current SVar,
517-- then we create a new SVar and schedule it on that. The newly created SVar
518-- joins as one of the computations on the current SVar queue.
520-- Cases when we need to switch to a new SVar:
522-- * (x `parallel` y) `parallel` (t `parallel` u) -- all of them get scheduled on the same SVar
523-- * (x `parallel` y) `parallel` (t `async` u) -- @t@ and @u@ get scheduled on a new child SVar
524-- because of the scheduling policy change.
525-- * if we 'adapt' a stream of type 'async' to a stream of type
526-- 'Parallel', we create a new SVar at the transitioning bind.
527-- * When the stream is switching from disjunctive composition to conjunctive
528-- composition and vice-versa we create a new SVar to isolate the scheduling
531forkSVarAsync :: (IsStream t, MonadAsync m)
532 => SVarStyle -> t m a -> t m a -> t m a
533forkSVarAsync style m1 m2 = mkStream $ \st stp sng yld -> do
535 AsyncVar -> newAsyncVar st (concurrently (toStream m1) (toStream m2))
536 WAsyncVar -> newWAsyncVar st (concurrently (toStream m1) (toStream m2))
537 _ -> error "illegal svar type"
538 foldStream st stp sng yld $ fromSVar sv
540 concurrently ma mb = mkStream $ \st stp sng yld -> do
541 liftIO $ enqueue (fromJust $ streamVar st) mb
542 foldStreamShared st stp sng yld ma
544{-# INLINE joinStreamVarAsync #-}
545joinStreamVarAsync :: (IsStream t, MonadAsync m)
546 => SVarStyle -> t m a -> t m a -> t m a
547joinStreamVarAsync style m1 m2 = mkStream $ \st stp sng yld ->
549 Just sv | svarStyle sv == style -> do
550 liftIO $ enqueue sv (toStream m2)
551 foldStreamShared st stp sng yld m1
552 _ -> foldStreamShared st stp sng yld (forkSVarAsync style m1 m2)
554------------------------------------------------------------------------------
555-- Semigroup and Monoid style compositions for parallel actions
556------------------------------------------------------------------------------
558-- | Polymorphic version of the 'Semigroup' operation '<>' of 'AsyncT'.
559-- Merges two streams possibly concurrently, preferring the
560-- elements from the left one when available.
564async :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a
565async = joinStreamVarAsync AsyncVar
570{-# DEPRECATED (<|) "Please use 'async' instead." #-}
572(<|) :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a
575-- IMPORTANT: using a monomorphically typed and SPECIALIZED consMAsync makes a
576-- huge difference in the performance of consM in IsStream instance even we
577-- have a SPECIALIZE in the instance.
579-- | XXX we can implement it more efficienty by directly implementing instead
580-- of combining streams using async.
581{-# INLINE consMAsync #-}
582{-# SPECIALIZE consMAsync :: IO a -> AsyncT IO a -> AsyncT IO a #-}
583consMAsync :: MonadAsync m => m a -> AsyncT m a -> AsyncT m a
584consMAsync m r = fromStream $ K.yieldM m `async` (toStream r)
586------------------------------------------------------------------------------
588------------------------------------------------------------------------------
590-- | Deep async composition or async composition with depth first traversal. In
591-- a left to right 'Semigroup' composition it tries to yield elements from the
592-- left stream as long as it can, but it can run the right stream in parallel
593-- if it needs to, based on demand. The right stream can be run if the left
594-- stream blocks on IO or cannot produce elements fast enough for the consumer.
597-- main = ('toList' . 'asyncly' $ (fromFoldable [1,2]) \<> (fromFoldable [3,4])) >>= print
603-- Any exceptions generated by a constituent stream are propagated to the
604-- output stream. The output and exceptions from a single stream are guaranteed
605-- to arrive in the same order in the resulting stream as they were generated
606-- in the input stream. However, the relative ordering of elements from
607-- different streams in the resulting stream can vary depending on scheduling
608-- and generation delays.
610-- Similarly, the monad instance of 'AsyncT' /may/ run each iteration
611-- concurrently based on demand. More concurrent iterations are started only
612-- if the previous iterations are not able to produce enough output for the
617-- import qualified "Streamly.Prelude" as S
618-- import Control.Concurrent
620-- main = 'runStream' . 'asyncly' $ do
621-- n <- return 3 \<\> return 2 \<\> return 1
623-- threadDelay (n * 1000000)
624-- myThreadId >>= \\tid -> putStrLn (show tid ++ ": Delay " ++ show n)
627-- ThreadId 40: Delay 1
628-- ThreadId 39: Delay 2
629-- ThreadId 38: Delay 3
632-- All iterations may run in the same thread if they do not block.
634-- Note that async composition with depth first traversal can be used to
635-- combine infinite number of streams as it explores only a bounded number of
639newtype AsyncT m a = AsyncT {getAsyncT :: Stream m a}
640 deriving (MonadTrans)
642-- | A demand driven left biased parallely composing IO stream of elements of
643-- type @a@. See 'AsyncT' documentation for more details.
646type Async = AsyncT IO
648-- | Fix the type of a polymorphic stream as 'AsyncT'.
651asyncly :: IsStream t => AsyncT m a -> t m a
654instance IsStream AsyncT where
660------------------------------------------------------------------------------
662------------------------------------------------------------------------------
664-- Monomorphically typed version of "async" for better performance of Semigroup
666{-# INLINE mappendAsync #-}
667{-# SPECIALIZE mappendAsync :: AsyncT IO a -> AsyncT IO a -> AsyncT IO a #-}
668mappendAsync :: MonadAsync m => AsyncT m a -> AsyncT m a -> AsyncT m a
669mappendAsync m1 m2 = fromStream $ async (toStream m1) (toStream m2)
671instance MonadAsync m => Semigroup (AsyncT m a) where
674------------------------------------------------------------------------------
676------------------------------------------------------------------------------
678instance MonadAsync m => Monoid (AsyncT m a) where
682------------------------------------------------------------------------------
684------------------------------------------------------------------------------
686{-# INLINE bindAsync #-}
687{-# SPECIALIZE bindAsync :: AsyncT IO a -> (a -> AsyncT IO b) -> AsyncT IO b #-}
688bindAsync :: MonadAsync m => AsyncT m a -> (a -> AsyncT m b) -> AsyncT m b
689bindAsync m f = fromStream $ K.bindWith async (adapt m) (\a -> adapt $ f a)
691instance MonadAsync m => Monad (AsyncT m) where
695{-# INLINE apAsync #-}
696{-# SPECIALIZE apAsync :: AsyncT IO (a -> b) -> AsyncT IO a -> AsyncT IO b #-}
697apAsync :: MonadAsync m => AsyncT m (a -> b) -> AsyncT m a -> AsyncT m b
698apAsync mf m = ap (adapt mf) (adapt m)
700instance (Monad m, MonadAsync m) => Applicative (AsyncT m) where
701 pure = AsyncT . K.yield
704------------------------------------------------------------------------------
706------------------------------------------------------------------------------
708MONAD_COMMON_INSTANCES(AsyncT, MONADPARALLEL)
710------------------------------------------------------------------------------
712------------------------------------------------------------------------------
714-- | XXX we can implement it more efficienty by directly implementing instead
715-- of combining streams using wAsync.
716{-# INLINE consMWAsync #-}
717{-# SPECIALIZE consMWAsync :: IO a -> WAsyncT IO a -> WAsyncT IO a #-}
718consMWAsync :: MonadAsync m => m a -> WAsyncT m a -> WAsyncT m a
719consMWAsync m r = fromStream $ K.yieldM m `wAsync` (toStream r)
721-- | Polymorphic version of the 'Semigroup' operation '<>' of 'WAsyncT'.
722-- Merges two streams concurrently choosing elements from both fairly.
726wAsync :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a
727wAsync = joinStreamVarAsync WAsyncVar
729-- | Wide async composition or async composition with breadth first traversal.
730-- The Semigroup instance of 'WAsyncT' concurrently /traverses/ the composed
731-- streams using a depth first travesal or in a round robin fashion, yielding
732-- elements from both streams alternately.
735-- main = ('toList' . 'wAsyncly' $ (fromFoldable [1,2]) \<> (fromFoldable [3,4])) >>= print
741-- Any exceptions generated by a constituent stream are propagated to the
742-- output stream. The output and exceptions from a single stream are guaranteed
743-- to arrive in the same order in the resulting stream as they were generated
744-- in the input stream. However, the relative ordering of elements from
745-- different streams in the resulting stream can vary depending on scheduling
746-- and generation delays.
748-- Similarly, the 'Monad' instance of 'WAsyncT' runs /all/ iterations fairly
749-- concurrently using a round robin scheduling.
753-- import qualified "Streamly.Prelude" as S
754-- import Control.Concurrent
756-- main = 'runStream' . 'wAsyncly' $ do
757-- n <- return 3 \<\> return 2 \<\> return 1
759-- threadDelay (n * 1000000)
760-- myThreadId >>= \\tid -> putStrLn (show tid ++ ": Delay " ++ show n)
763-- ThreadId 40: Delay 1
764-- ThreadId 39: Delay 2
765-- ThreadId 38: Delay 3
768-- Unlike 'AsyncT' all iterations are guaranteed to run fairly
769-- concurrently, unconditionally.
771-- Note that async composition with breadth first traversal can only combine a
772-- finite number of streams as it needs to retain state for each unfinished
776newtype WAsyncT m a = WAsyncT {getWAsyncT :: Stream m a}
777 deriving (MonadTrans)
779-- | A round robin parallely composing IO stream of elements of type @a@.
780-- See 'WAsyncT' documentation for more details.
783type WAsync = WAsyncT IO
785-- | Fix the type of a polymorphic stream as 'WAsyncT'.
788wAsyncly :: IsStream t => WAsyncT m a -> t m a
791instance IsStream WAsyncT where
792 toStream = getWAsyncT
797------------------------------------------------------------------------------
799------------------------------------------------------------------------------
801{-# INLINE mappendWAsync #-}
802{-# SPECIALIZE mappendWAsync :: WAsyncT IO a -> WAsyncT IO a -> WAsyncT IO a #-}
803mappendWAsync :: MonadAsync m => WAsyncT m a -> WAsyncT m a -> WAsyncT m a
804mappendWAsync m1 m2 = fromStream $ wAsync (toStream m1) (toStream m2)
806instance MonadAsync m => Semigroup (WAsyncT m a) where
809------------------------------------------------------------------------------
811------------------------------------------------------------------------------
813instance MonadAsync m => Monoid (WAsyncT m a) where
817------------------------------------------------------------------------------
819------------------------------------------------------------------------------
821{-# INLINE bindWAsync #-}
822{-# SPECIALIZE bindWAsync :: WAsyncT IO a -> (a -> WAsyncT IO b) -> WAsyncT IO b #-}
823bindWAsync :: MonadAsync m => WAsyncT m a -> (a -> WAsyncT m b) -> WAsyncT m b
824bindWAsync m f = fromStream $ K.bindWith wAsync (adapt m) (\a -> adapt $ f a)
826instance MonadAsync m => Monad (WAsyncT m) where
830{-# INLINE apWAsync #-}
831{-# SPECIALIZE apWAsync :: WAsyncT IO (a -> b) -> WAsyncT IO a -> WAsyncT IO b #-}
832apWAsync :: MonadAsync m => WAsyncT m (a -> b) -> WAsyncT m a -> WAsyncT m b
833apWAsync mf m = ap (adapt mf) (adapt m)
835instance (Monad m, MonadAsync m) => Applicative (WAsyncT m) where
836 pure = WAsyncT . K.yield
839------------------------------------------------------------------------------
841------------------------------------------------------------------------------
843MONAD_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