on master help sign in
Add concurrent lookahead stream type "Ahead"
commit9da3fccd2078cceb2cba7c1791f94e36131c030d authorHarendra Kumar <harendra.kumar@gmail.com> dateThu 17 May 2018, 11:22 (8 years ago) parenta4e4c07
8 files changed+613−15
benchmark/Linear.hs+2−1 benchmark/Nested.hs+13 src/Streamly.hs+8−1 src/Streamly/Core.hs+330−2 src/Streamly/Streams.hs+139−6 streamly.cabal+4−2 test/Main.hs+98−2 test/Prop.hs+19−1
benchmark/Linear.hs+2−1
@@ -52,9 +52,10 @@ main = do
5252 , bgroup "append"
5353 [ benchIOAppend "serially" $ Ops.append serially
5454 , benchIOAppend "wSerially" $ Ops.append wSerially
55+ , benchIOAppend "aheadly" $ Ops.append aheadly
5556 , benchIOAppend "asyncly" $ Ops.append asyncly
5657 , benchIOAppend "wAsyncly" $ Ops.append wAsyncly
57− , benchIOAppend "parallely" $ Ops.append parallely
58+ , benchIOAppend "parallely" $ Ops.append parallely
5859 ]
5960 , bgroup "compose"
6061 [ benchIO "mapM" Ops.composeMapM
benchmark/Nested.hs+13
@@ -53,6 +53,19 @@ main = do
5353 , benchIO "breakAfterSome" $ Ops.breakAfterSome wSerially
5454 ]
5555
56+ , bgroup "aheadly"
57+ [ benchIO "append" $ Ops.append aheadly
58+ , benchIO "toNull0" $ Ops.toNull0 aheadly
59+ , benchIO "toList0" $ Ops.toList0 aheadly
60+ , benchIO "toNull" $ Ops.toNull aheadly
61+ , benchIO "toList" $ Ops.toList aheadly
62+ , benchIO "toListSome" $ Ops.toListSome aheadly
63+ , benchIO "filterAllOut" $ Ops.filterAllOut aheadly
64+ , benchIO "filterAllIn" $ Ops.filterAllIn aheadly
65+ , benchIO "filterSome" $ Ops.filterSome aheadly
66+ , benchIO "breakAfterSome" $ Ops.breakAfterSome aheadly
67+ ]
68+
5669 , bgroup "asyncly"
5770 [ benchIO "append" $ Ops.append asyncly
5871 , benchIO "toNull0" $ Ops.toNull0 asyncly
src/Streamly.hs+8−1
@@ -65,7 +65,11 @@ module Streamly
6565 , SerialT
6666 , WSerialT
6767
68− -- ** Parallel Streams
68+ -- ** Concurrent Lookahead Streams
69+ -- $lookahead
70+ , AheadT
71+
72+ -- ** Concurrent Asynchronous Streams
6973 -- $async
7074 , AsyncT
7175 , WAsyncT
@@ -80,6 +84,7 @@ module Streamly
8084 -- $sum
8185 , serial
8286 , wSerial
87+ , ahead
8388 , async
8489 , wAsync
8590 , parallel
@@ -91,6 +96,7 @@ module Streamly
9196 , serially
9297 , wSerially
9398 , asyncly
99+ , aheadly
94100 , wAsyncly
95101 , parallely
96102 , zipSerially
@@ -100,6 +106,7 @@ module Streamly
100106 -- * IO Streams
101107 , Serial
102108 , WSerial
109+ , Ahead
103110 , Async
104111 , WAsync
105112 , Parallel
src/Streamly/Core.hs+330−2
@@ -36,6 +36,7 @@ module Streamly.Core
3636 -- * Semigroup Style Composition
3737 , serial
3838 , wSerial
39+ , ahead
3940 , async
4041 , wAsync
4142 , parallel
@@ -66,7 +67,7 @@ where
6667
6768import Control.Concurrent (ThreadId, myThreadId, threadDelay)
6869import Control.Concurrent.MVar (MVar, newEmptyMVar, tryTakeMVar,
69− tryPutMVar, takeMVar)
70+ tryPutMVar, takeMVar, putMVar)
7071import Control.Exception (SomeException (..))
7172import qualified Control.Exception.Lifted as EL
7273import Control.Monad (when)
@@ -81,6 +82,8 @@ import Data.Atomics (casIORef, readForCAS, peekTicket
8182import Data.Concurrent.Queue.MichaelScott (LinkedQueue, newQ, pushL,
8283 tryPopR, nullQ)
8384import Data.Functor (void)
85+import Data.Heap (Heap, Entry(..))
86+import qualified Data.Heap as H
8487import Data.IORef (IORef, modifyIORef, newIORef,
8588 readIORef, atomicModifyIORef)
8689import Data.Maybe (isNothing, fromJust)
@@ -102,19 +105,27 @@ data ChildEvent a =
102105 ChildYield a
103106 | ChildStop ThreadId (Maybe SomeException)
104107
108+-- | Sorting out of turn outputs in a heap for Ahead style streams
109+data AheadHeapEntry a =
110+ AheadYieldSingle a
111+ | AheadYieldMany a (MVar ()) -- value, MVar to wakeup for more values
112+
105113------------------------------------------------------------------------------
106114-- State threaded around the monad for thread management
107115------------------------------------------------------------------------------
108116
117+-- XXX this is redundant as we always start a new SVar on bind
109118-- | Conjunction is used for monadic/product style composition. Disjunction is
110119-- used for fold/sum style composition. We need to distinguish the two types of
111120-- SVars so that the scheduling of the two is independent.
112121data SVarTag = Conjunction | Disjunction deriving Eq
113122
123+-- XXX use a separate data structure for each type of SVar
114124data SVarSched =
115125 LIFO -- depth first concurrent
116126 | FIFO -- breadth first concurrent
117127 | Par -- all parallel
128+ | SVarAhead
118129 deriving Eq
119130
120131-- | Identify the type of the SVar. Two computations using the same style can
@@ -155,7 +166,18 @@ data SVar m a =
155166 , doorBell :: MVar () -- signal the consumer about output
156167 , siren :: MVar () -- hooter for workers to begin work
157168
169+ -- Output synchronization mechanism for Ahead streams (Ahead and
170+ -- wAhead). We maintain a heap of out of sequence ahead of time
171+ -- generated outputs and the sequence number of the task that is
172+ -- currently at the head of the stream. Concurrent execute ahead
173+ -- tasks that have a sequence number greater than the task at the
174+ -- head should add their output to the heap.
175+ , outputHeap :: IORef (Heap (Entry Int (AheadHeapEntry a)), Int)
176+ , heapSiren :: MVar () -- hooter for workers to continue
177+ -- adding to the heap
178+
158179 -- Shared work queue
180+ , workQueue :: IORef ([Stream m a], Int)
159181 , enqueue :: Stream m a -> IO ()
160182 , queueEmpty :: m Bool
161183 , runqueue :: m ()
@@ -308,6 +330,9 @@ doFork action exHandler =
308330
309331-- TBD Each worker can have their own queue and the consumer can empty one
310332-- queue at a time, that way contention can be reduced.
333+
334+-- | This function is used by the producer threads to queue output for the
335+-- consumer thread to consume.
311336{-# INLINE send #-}
312337send :: SVar m a -> ChildEvent a -> IO ()
313338send sv msg = do
@@ -340,6 +365,10 @@ send sv msg = do
340365sendStop :: SVar m a -> IO ()
341366sendStop sv = myThreadId >>= \tid -> send sv (ChildStop tid Nothing)
342367
368+-------------------------------------------------------------------------------
369+-- Async
370+-------------------------------------------------------------------------------
371+
343372-- Note: For purely right associated expressions this queue should have at most
344373-- one element. It grows to more than one when we have left associcated
345374-- expressions. Large left associated compositions can grow this to a
@@ -367,6 +396,10 @@ runqueueLIFO sv q = run
367396 [] -> ([], Nothing)
368397 x : xs -> (xs, Just x)
369398
399+-------------------------------------------------------------------------------
400+-- WAsync
401+-------------------------------------------------------------------------------
402+
370403{-# INLINE enqueueFIFO #-}
371404enqueueFIFO :: LinkedQueue (Stream m a) -> Stream m a -> IO ()
372405enqueueFIFO = pushL
@@ -387,6 +420,10 @@ runqueueFIFO sv q = run
387420 single a = sendit a >> run
388421 yield a r = sendit a >> liftIO (enqueueFIFO q r) >> run
389422
423+-------------------------------------------------------------------------------
424+-- Parallel
425+-------------------------------------------------------------------------------
426+
390427{-# INLINE runOne #-}
391428runOne :: MonadIO m => SVar m a -> Stream m a -> m ()
392429runOne sv m = (runStream m) (Just sv) stop single yield
@@ -398,6 +435,226 @@ runOne sv m = (runStream m) (Just sv) stop single yield
398435 single a = sendit a >> stop
399436 yield a r = sendit a >> runOne sv r
400437
438+-------------------------------------------------------------------------------
439+-- Ahead
440+-------------------------------------------------------------------------------
441+
442+-- Lookahead streams can execute multiple tasks concurrently, ahead of time but
443+-- always serves them in the same order as they appear in the stream. To
444+-- implement lookahead streams efficiently we assign a sequence number to each
445+-- task when the task is picked up for execution. When the task finishes the
446+-- output is tagged with the same sequence number and we rearrange the outputs
447+-- in sequence based on that number.
448+--
449+-- To explain the mechanism imagine that the current task at the head of the
450+-- stream has a "token" to yield to the output. The ownership of the token is
451+-- determined by the current sequence number maintained in outputHeap. Sequence
452+-- number is assigned when a task is queued. When a thread dequeues a task it
453+-- picks up the sequence number as well and when the output is ready it uses
454+-- the sequence number to queue the output to the outputQueue.
455+--
456+-- The thread with current sequence number sends the output directly to the
457+-- outputQueue. Other threads push the output to the outputHeap. When the task
458+-- being queued on the heap is a stream of many elements we yield the first
459+-- element on the heap and an MVar to wakeup for more elements. When such a
460+-- task gets the "token" for outputQueue it can directly keep yielding all the
461+-- elements to the outputQueue without checking for the "token".
462+--
463+-- Note that no two outputs in the heap can have the same sequence numbers and
464+-- therefore we do not need a stable heap. We have also separated the buffer
465+-- for the current task (outputQueue) and the pending tasks (outputHeap) so
466+-- that the pending tasks cannot interfere with the current task. Note that for
467+-- a single task just the outputQueue is enough and for the case of many
468+-- threads just a heap is good enough. However we balance between these two
469+-- cases, so that both are efficient.
470+--
471+-- For bigger streams it may make sense to have separate buffers for each
472+-- stream. However, for singleton streams this may become inefficient. However,
473+-- if we do not have separate buffers, then the streams that come later in
474+-- sequence may hog the buffer, hindering the streams that are ahead. For this
475+-- reason we have a single element buffer limitation for the streams being
476+-- executed in advance.
477+--
478+-- This scheme works pretty efficiently with only 50% more overhead compared to
479+-- the Async streams where we do not have any kind of sequencing of the
480+-- outputs. It is especially devised so that we are most efficient when we have
481+-- short tasks and need just a single thread. Also when a thread yields many
482+-- items it can hold lockfree access to the outputQueue and do it efficiently.
483+--
484+-- XXX Maybe we can start the ahead threads at a lower cpu and IO priority so
485+-- that they do not hog the resources and hinder the progress of the threads in
486+-- front of them.
487+
488+{-# INLINE sendToHeap #-}
489+sendToHeap :: SVar m a -> AheadHeapEntry a -> Int -> IO Bool
490+sendToHeap sv msg seqNo = do
491+ let maxHeapSize = 1500
492+
493+ -- Maybe we can use a lock instead of using CAS
494+ -- insertions in the heap may be expensive if the heap is big
495+ -- Note: len is valid only when added is True
496+ (len, added) <- atomicModifyIORefCAS (outputHeap sv) $
497+ \ref@(h, snum) ->
498+ if seqNo > snum
499+ then ((H.insert (Entry seqNo msg) h, snum), (H.size h, True))
500+ else (ref, (0, False))
501+
502+ -- this is only for the case when the consumer generated siren can get
503+ -- missed due to race.
504+ when (len < maxHeapSize) $ void $ tryPutMVar (heapSiren sv) ()
505+
506+ if added
507+ then do
508+ when (len + 1 >= maxHeapSize) $ do
509+ takeMVar (heapSiren sv)
510+ (h, _) <- readIORef (outputHeap sv)
511+ when (H.size h <= maxHeapSize) $ void $ tryPutMVar (siren sv) ()
512+ return False
513+ else do
514+ case msg of
515+ AheadYieldSingle a -> send sv (ChildYield a)
516+ AheadYieldMany a mv -> do
517+ send sv (ChildYield a)
518+ putMVar mv ()
519+ return True
520+
521+popFromHeap
522+ :: IORef (Heap (Entry Int (AheadHeapEntry a)), Int)
523+ -> Int
524+ -> IO (Maybe (AheadHeapEntry a))
525+popFromHeap hp nextSeqNo =
526+ atomicModifyIORefCAS hp $ \(h, snum) -> do
527+ let r = H.uncons h
528+ let n = max nextSeqNo snum
529+ case r of
530+ Nothing -> ((h, n), Nothing)
531+ Just (Entry seqNo ev, hp') ->
532+ if (seqNo <= n)
533+ then ((hp', seqNo), Just ev)
534+ else ((h, n), Nothing)
535+
536+-- | As soon as we set the nextSeqNo in outputHeap we have transferred the
537+-- output "token" to the thread executing the task with that sequence number.
538+-- We have to make sure that there is no entry in the heap that has nextSeqNo
539+-- before we set nextSeqNo as the expected sequence number in the heap. It is
540+-- possible that when we are clearing the heap entry of the current sequence
541+-- number the next sequence number gets added and then we have to clear that,
542+-- this can keep going on and on.
543+drainHeap :: SVar m a -> Int -> IO ()
544+drainHeap sv nextSeqNo = do
545+ r <- popFromHeap (outputHeap sv) nextSeqNo
546+ case r of
547+ Nothing -> return ()
548+ Just ev -> do
549+ case ev of
550+ AheadYieldSingle a -> do
551+ send sv (ChildYield a)
552+ atomicModifyIORefCAS_ (outputHeap sv) $ \(h, snum) ->
553+ (h, snum + 1)
554+ drainHeap sv nextSeqNo
555+ AheadYieldMany a mv -> do
556+ send sv (ChildYield a)
557+ putMVar mv ()
558+
559+-- | The thread that has the token has the responsibility to move any pending
560+-- entries in the heap to the outputQueue. When the token is assigned to a
561+-- sequence number we must not have any entries for that sequence number in the
562+-- heap.
563+releaseToken :: SVar m a -> Int -> IO ()
564+releaseToken sv nextSeqNo = do
565+ drainHeap sv nextSeqNo
566+ void $ tryPutMVar (heapSiren sv) ()
567+
568+-- | When we have the output "token" we are allowed to append to the
569+-- outputQueue otherwise we have to insert the output in the outputHeap.
570+-- Returns whether we still have the token.
571+handleOutput :: SVar m a
572+ -> Bool
573+ -> Int
574+ -> Int
575+ -> a
576+ -> AheadHeapEntry a
577+ -> IO Bool
578+handleOutput sv haveToken prevSeqNo seqNo a ev = do
579+ if haveToken
580+ then do
581+ let nextSeqNo = prevSeqNo + 1
582+ if seqNo == nextSeqNo
583+ then send sv (ChildYield a) >> return True
584+ else do
585+ releaseToken sv nextSeqNo
586+ toHeap
587+ else toHeap
588+ where toHeap = sendToHeap sv ev seqNo
589+
590+-- Left associated ahead expressions are expensive. We start a new SVar for
591+-- each left associative expression. The queue is used only for right
592+-- associated expression, we queue the right expression and execute the left.
593+-- Thererefore the queue never has more than on item in it.
594+{-# INLINE enqueueAhead #-}
595+enqueueAhead :: IORef ([Stream m a], Int) -> Stream m a -> IO ()
596+enqueueAhead q m = do
597+ atomicModifyIORefCAS_ q $ \ case
598+ ([], n) -> ([m], n + 1) -- increment sequence
599+ _ -> error "not empty"
600+
601+runqueueAhead :: MonadIO m => SVar m a -> IORef ([Stream m a], Int) -> m ()
602+runqueueAhead sv q = run False (-1)
603+
604+ where
605+
606+ run haveToken prevSeqNo = do
607+ work <- dequeue
608+ case work of
609+ Nothing -> liftIO $ do
610+ when haveToken $ releaseToken sv (prevSeqNo + 1)
611+ sendStop sv
612+ Just (m, seqNo) -> do
613+ let tStatus = haveToken || seqNo == 0
614+ let stop = do
615+ let nextSeqNo = prevSeqNo + 1
616+ if tStatus && seqNo /= nextSeqNo
617+ then do
618+ liftIO $ releaseToken sv nextSeqNo
619+ run False seqNo
620+ else run tStatus seqNo
621+ let single a = do
622+ tokenStatus <- liftIO $
623+ handleOutput sv tStatus prevSeqNo seqNo a
624+ (AheadYieldSingle a)
625+ run tokenStatus seqNo
626+
627+ -- If we are yielding more than one item and we do not have the
628+ -- token then we insert the current item in the pending
629+ -- outputHeap and wait for our turn.
630+ let
631+ stopNext = run True seqNo
632+ singleNext a = do
633+ liftIO $ send sv (ChildYield a)
634+ run True seqNo
635+ yieldNext a r = do
636+ liftIO $ send sv (ChildYield a)
637+ (runStream r) (Just sv) stopNext singleNext yieldNext
638+
639+ yield a r = do
640+ -- XXX we can allocate the MVar when actually inserting
641+ -- in the heap and then return it.
642+ liftIO $ do
643+ mv <- newEmptyMVar
644+ gotToken <-
645+ handleOutput sv tStatus prevSeqNo seqNo a
646+ (AheadYieldMany a mv)
647+ when (not gotToken) $ void $ takeMVar mv
648+ (runStream r) (Just sv) stopNext singleNext yieldNext
649+
650+ (runStream m) (Just sv) stop single yield
651+
652+ dequeue = liftIO $ do
653+ atomicModifyIORefCAS q $ \case
654+ ([], n) -> (([], n), Nothing)
655+ (x : [], n) -> (([], n), Just (x, n))
656+ _ -> error "more than one item on queue"
657+
401658-- Thread tracking is needed for two reasons:
402659--
403660-- 1) Killing threads on exceptions. Threads may not be left to go away by
@@ -481,7 +738,9 @@ sendWorkerWait sv = do
481738 (_, n) <- liftIO $ readIORef (outputQueue sv)
482739 when (n <= 0) $ do
483740 -- XXX the queue may be empty temporarily if the worker has dequeued
484− -- the work item but has not enqueued the remaining part yet.
741+ -- the work item but has not enqueued the remaining part yet. For the
742+ -- same reason, a worker may come back if it tries to dequeue when the
743+ -- queue is empty, even though the whole work has not finished yet.
485744 done <- queueEmpty sv
486745 if not done
487746 then do
@@ -553,7 +812,10 @@ getFifoSVar ctype = do
553812 SVar { outputQueue = outQ
554813 , doorBell = outQMv
555814 , siren = hooter
815+ , outputHeap = undefined
816+ , heapSiren = undefined
556817 , runningThreads = running
818+ , workQueue = undefined
557819 , runqueue = runqueueFIFO sv q
558820 , enqueue = pushL q
559821 , queueEmpty = liftIO $ nullQ q
@@ -575,7 +837,10 @@ getLifoSVar ctype = do
575837 SVar { outputQueue = outQ
576838 , doorBell = outQMv
577839 , siren = hooter
840+ , outputHeap = undefined
841+ , heapSiren = undefined
578842 , runningThreads = running
843+ , workQueue = undefined
579844 , runqueue = runqueueLIFO sv q
580845 , enqueue = enqueueLIFO q
581846 , queueEmpty = checkEmpty
@@ -595,7 +860,10 @@ getParSVar style = do
595860 SVar { outputQueue = outQ
596861 , doorBell = outQMv
597862 , siren = hooter
863+ , outputHeap = undefined
864+ , heapSiren = undefined
598865 , runningThreads = running
866+ , workQueue = undefined
599867 , runqueue = undefined
600868 , enqueue = undefined
601869 , queueEmpty = undefined
@@ -604,6 +872,35 @@ getParSVar style = do
604872 }
605873 in return sv
606874
875+getAheadSVar :: MonadIO m => SVarStyle -> IO (SVar m a)
876+getAheadSVar style = do
877+ outQ <- newIORef ([], 0)
878+ outH <- newIORef (H.empty, 0)
879+ outQMv <- newEmptyMVar
880+ hooter <- newEmptyMVar
881+ hsiren <- newEmptyMVar
882+ active <- newIORef 0
883+ running <- newIORef S.empty
884+ q <- newIORef ([], -1)
885+ let checkEmpty = liftIO $ do
886+ (xs, _) <- readIORef q
887+ return $ null xs
888+ let sv =
889+ SVar { outputQueue = outQ
890+ , doorBell = outQMv
891+ , siren = hooter
892+ , outputHeap = outH
893+ , heapSiren = hsiren
894+ , runningThreads = running
895+ , workQueue = q
896+ , runqueue = runqueueAhead sv q
897+ , enqueue = undefined
898+ , queueEmpty = checkEmpty
899+ , svarStyle = style
900+ , activeWorkers = active
901+ }
902+ in return sv
903+
607904-- | Create a new empty SVar.
608905newEmptySVar :: MonadAsync m => SVarStyle -> m (SVar m a)
609906newEmptySVar style = do
@@ -612,6 +909,7 @@ newEmptySVar style = do
612909 SVarStyle _ FIFO -> getFifoSVar style
613910 SVarStyle _ LIFO -> getLifoSVar style
614911 SVarStyle _ Par -> getParSVar style
912+ SVarStyle _ SVarAhead -> getAheadSVar style
615913
616914-- | Create a new SVar and enqueue one stream computation on it.
617915newStreamVar1 :: MonadAsync m => SVarStyle -> Stream m a -> m (SVar m a)
@@ -624,6 +922,17 @@ newStreamVar1 style m = do
624922 pushWorker sv
625923 return sv
626924
925+-- | Create a new SVar and enqueue one stream computation on it.
926+newStreamVarAhead :: MonadAsync m => Stream m a -> m (SVar m a)
927+newStreamVarAhead m = do
928+ sv <- newEmptySVar (SVarStyle Disjunction SVarAhead)
929+ -- Note: We must have all the work on the queue before sending the
930+ -- pushworker, otherwise the pushworker may exit before we even get a
931+ -- chance to push.
932+ liftIO $ enqueueAhead (workQueue sv) m
933+ pushWorker sv
934+ return sv
935+
627936-- | Write a stream to an 'SVar' in a non-blocking manner. The stream can then
628937-- be read back from the SVar using 'fromSVar'.
629938toStreamVar :: MonadAsync m => SVar m a -> Stream m a -> m ()
@@ -727,6 +1036,25 @@ joinStreamVarPar style m1 m2 = Stream $ \svr stp sng yld ->
7271036 pushWorkerPar sv m2
7281037 (runStream (fromStreamVar sv)) Nothing stp sng yld
7291038
1039+{-# INLINE ahead #-}
1040+ahead :: MonadAsync m => Stream m a -> Stream m a -> Stream m a
1041+ahead m1 m2 = Stream $ \svr stp sng yld -> do
1042+ let style = SVarStyle Disjunction SVarAhead
1043+ case svr of
1044+ Just sv | svarStyle sv == style -> do
1045+ liftIO $ enqueueAhead (workQueue sv) m2
1046+ -- Always run the left side on a new SVar to avoid complexity in
1047+ -- sequencing results. This means the left side cannot further
1048+ -- split into more ahead computations on the same SVar.
1049+ (runStream m1) Nothing stp sng yld
1050+ _ -> do
1051+ sv <- newStreamVarAhead (concurrently m1 m2)
1052+ (runStream (fromStreamVar sv)) Nothing stp sng yld
1053+ where
1054+ concurrently ma mb = Stream $ \svr stp sng yld -> do
1055+ liftIO $ enqueueAhead (workQueue (fromJust svr)) mb
1056+ (runStream ma) Nothing stp sng yld
1057+
7301058------------------------------------------------------------------------------
7311059-- Semigroup and Monoid style compositions for parallel actions
7321060------------------------------------------------------------------------------
src/Streamly/Streams.hs+139−6
@@ -53,6 +53,7 @@ module Streamly.Streams
5353 -- * Merging Streams
5454 , serial
5555 , wSerial
56+ , ahead
5657 , async
5758 , wAsync
5859 , parallel
@@ -62,6 +63,7 @@ module Streamly.Streams
6263 -- * IO Streams
6364 , Serial
6465 , WSerial
66+ , Ahead
6567 , Async
6668 , WAsync
6769 , Parallel
@@ -73,6 +75,7 @@ module Streamly.Streams
7375 , StreamT -- deprecated
7476 , WSerialT
7577 , InterleavedT -- deprecated
78+ , AheadT
7679 , AsyncT
7780 , WAsyncT
7881 , ParallelT
@@ -84,6 +87,7 @@ module Streamly.Streams
8487 , serially -- deprecated
8588 , wSerially
8689 , interleaving -- deprecated
90+ , aheadly
8791 , asyncly
8892 , wAsyncly
8993 , parallely
@@ -349,6 +353,9 @@ instance (MonadState s m CONSTRAINT) => MonadState s (STREAM m) where { \
349353-- all elements from the second stream.
350354--
351355-- @
356+-- import Streamly
357+-- import qualified "Streamly.Prelude" as S
358+--
352359-- main = ('toList' . 'serially' $ (fromFoldable [1,2]) \<\> (fromFoldable [3,4])) >>= print
353360-- @
354361-- @
@@ -361,7 +368,7 @@ instance (MonadState s m CONSTRAINT) => MonadState s (STREAM m) where { \
361368-- @
362369-- main = 'runStream' . 'serially' $ do
363370-- x <- return 1 \<\> return 2
364−-- liftIO $ print x
371+-- S.once $ print x
365372-- @
366373-- @
367374-- 1
@@ -374,7 +381,7 @@ instance (MonadState s m CONSTRAINT) => MonadState s (STREAM m) where { \
374381-- main = 'runStream' . 'serially' $ do
375382-- x <- return 1 \<\> return 2
376383-- y <- return 3 \<\> return 4
377−-- liftIO $ print (x, y)
384+-- S.once $ print (x, y)
378385-- @
379386-- @
380387-- (1,3)
@@ -450,6 +457,9 @@ MONAD_COMMON_INSTANCES(SerialT,)
450457-- two streams, yielding one element from each stream alternately.
451458--
452459-- @
460+-- import Streamly
461+-- import qualified "Streamly.Prelude" as S
462+--
453463-- main = ('toList' . 'wSerially' $ (fromFoldable [1,2]) \<\> (fromFoldable [3,4])) >>= print
454464-- @
455465-- @
@@ -464,7 +474,7 @@ MONAD_COMMON_INSTANCES(SerialT,)
464474-- main = 'runStream' . 'wSerially' $ do
465475-- x <- return 1 \<\> return 2
466476-- y <- return 3 \<\> return 4
467−-- liftIO $ print (x, y)
477+-- S.once $ print (x, y)
468478-- @
469479-- @
470480-- (1,3)
@@ -542,6 +552,114 @@ instance Monad m => Monad (WSerialT m) where
542552MONAD_APPLICATIVE_INSTANCE(WSerialT,)
543553MONAD_COMMON_INSTANCES(WSerialT,)
544554
555+------------------------------------------------------------------------------
556+-- AheadT
557+------------------------------------------------------------------------------
558+
559+-- | Deep ahead composition or ahead composition with depth first traversal.
560+-- The semigroup composition of 'AheadT' appends streams in a depth first
561+-- manner just like 'SerialT' except that it can produce elements concurrently
562+-- ahead of time. It is like 'AsyncT' except that 'AsyncT' produces the output
563+-- as it arrives whereas 'AheadT' orders the output in the traversal order.
564+--
565+-- @
566+-- main = ('toList' . 'aheadly' $ (fromFoldable [1,2]) \<> (fromFoldable [3,4])) >>= print
567+-- @
568+-- @
569+-- [1,2,3,4]
570+-- @
571+--
572+-- Any exceptions generated by a constituent stream are propagated to the
573+-- output stream.
574+--
575+-- Similarly, the monad instance of 'AheadT' may run each iteration
576+-- concurrently ahead of time but presents the results in the same order as
577+-- 'SerialT'.
578+--
579+-- @
580+-- import "Streamly"
581+-- import qualified "Streamly.Prelude" as S
582+-- import Control.Concurrent
583+--
584+-- main = 'runStream' . 'aheadly' $ do
585+-- n <- return 3 \<\> return 2 \<\> return 1
586+-- S.once $ do
587+-- threadDelay (n * 1000000)
588+-- myThreadId >>= \\tid -> putStrLn (show tid ++ ": Delay " ++ show n)
589+-- @
590+-- @
591+-- ThreadId 40: Delay 1
592+-- ThreadId 39: Delay 2
593+-- ThreadId 38: Delay 3
594+-- @
595+--
596+-- All iterations may run in the same thread if they do not block.
597+--
598+-- Note that ahead composition with depth first traversal can be used to
599+-- combine infinite number of streams as it explores only a bounded number of
600+-- streams at a time.
601+--
602+-- @since 0.3.0
603+newtype AheadT m a = AheadT {getAheadT :: S.Stream m a}
604+ deriving (Functor, MonadTrans)
605+
606+instance IsStream AheadT where
607+ toStream = getAheadT
608+ fromStream = AheadT
609+
610+------------------------------------------------------------------------------
611+-- Semigroup
612+------------------------------------------------------------------------------
613+
614+-- | Polymorphic version of the 'Semigroup' operation '<>' of 'AheadT'.
615+-- Merges two streams sequentially but with concurrent lookahead.
616+--
617+-- @since 0.3.0
618+{-# INLINE ahead #-}
619+ahead :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a
620+ahead m1 m2 = fromStream $ S.ahead (toStream m1) (toStream m2)
621+
622+instance MonadAsync m => Semigroup (AheadT m a) where
623+ (<>) = ahead
624+
625+------------------------------------------------------------------------------
626+-- Monoid
627+------------------------------------------------------------------------------
628+
629+instance MonadAsync m => Monoid (AheadT m a) where
630+ mempty = nil
631+ mappend = (<>)
632+
633+------------------------------------------------------------------------------
634+-- Monad
635+------------------------------------------------------------------------------
636+
637+{-# INLINE aheadbind #-}
638+aheadbind
639+ :: MonadAsync m
640+ => S.Stream m a
641+ -> (a -> S.Stream m b)
642+ -> S.Stream m b
643+aheadbind m f = go m
644+ where
645+ go (S.Stream g) =
646+ S.Stream $ \ctx stp sng yld ->
647+ let run x = (S.runStream x) ctx stp sng yld
648+ single a = run $ f a
649+ yield a r = run $ f a `S.ahead` go r
650+ in g Nothing stp single yield
651+
652+instance MonadAsync m => Monad (AheadT m) where
653+ return = pure
654+ (AheadT m) >>= f = AheadT $ aheadbind m (getAheadT . f)
655+
656+------------------------------------------------------------------------------
657+-- Other instances
658+------------------------------------------------------------------------------
659+
660+MONAD_APPLICATIVE_INSTANCE(AheadT,MONADPARALLEL)
661+MONAD_COMMON_INSTANCES(AheadT, MONADPARALLEL)
662+
545663------------------------------------------------------------------------------
546664-- AsyncT
547665------------------------------------------------------------------------------
@@ -573,11 +691,12 @@ MONAD_COMMON_INSTANCES(WSerialT,)
573691--
574692-- @
575693-- import "Streamly"
694+-- import qualified "Streamly.Prelude" as S
576695-- import Control.Concurrent
577696--
578697-- main = 'runStream' . 'asyncly' $ do
579698-- n <- return 3 \<\> return 2 \<\> return 1
580−-- liftIO $ do
699+-- S.once $ do
581700-- threadDelay (n * 1000000)
582701-- myThreadId >>= \\tid -> putStrLn (show tid ++ ": Delay " ++ show n)
583702-- @
@@ -691,11 +810,12 @@ MONAD_COMMON_INSTANCES(AsyncT, MONADPARALLEL)
691810--
692811-- @
693812-- import "Streamly"
813+-- import qualified "Streamly.Prelude" as S
694814-- import Control.Concurrent
695815--
696816-- main = 'runStream' . 'wAsyncly' $ do
697817-- n <- return 3 \<\> return 2 \<\> return 1
698−-- liftIO $ do
818+-- S.once $ do
699819-- threadDelay (n * 1000000)
700820-- myThreadId >>= \\tid -> putStrLn (show tid ++ ": Delay " ++ show n)
701821-- @
@@ -829,11 +949,12 @@ MONAD_COMMON_INSTANCES(WAsyncT, MONADPARALLEL)
829949--
830950-- @
831951-- import "Streamly"
952+-- import qualified "Streamly.Prelude" as S
832953-- import Control.Concurrent
833954--
834955-- main = 'runStream' . 'parallely' $ do
835956-- n <- return 3 \<\> return 2 \<\> return 1
836−-- liftIO $ do
957+-- S.once $ do
837958-- threadDelay (n * 1000000)
838959-- myThreadId >>= \\tid -> putStrLn (show tid ++ ": Delay " ++ show n)
839960-- @
@@ -991,6 +1112,12 @@ wSerially = adapt
9911112interleaving :: IsStream t => WSerialT m a -> t m a
9921113interleaving = wSerially
9931114
1115+-- | Fix the type of a polymorphic stream as 'AheadT'.
1116+--
1117+-- @since 0.3.0
1118+aheadly :: IsStream t => AheadT m a -> t m a
1119+aheadly = adapt
1120+
9941121-- | Fix the type of a polymorphic stream as 'AsyncT'.
9951122--
9961123-- @since 0.1.0
@@ -1097,6 +1224,12 @@ type Serial a = SerialT IO a
10971224-- @since 0.2.0
10981225type WSerial a = WSerialT IO a
10991226
1227+-- | A serial IO stream of elements of type @a@ with concurrent lookahead. See
1228+-- 'AheadT' documentation for more details.
1229+--
1230+-- @since 0.3.0
1231+type Ahead a = AheadT IO a
1232+
11001233-- | A demand driven left biased parallely composing IO stream of elements of
11011234-- type @a@. See 'AsyncT' documentation for more details.
11021235--
streamly.cabal+4−2
@@ -119,12 +119,14 @@ library
119119 build-depends: base >= 4.8 && < 5
120120 , atomic-primops >= 0.8 && < 0.9
121121 , containers >= 0.5 && < 0.6
122+ , heaps >= 0.3 && < 0.4
123+ , lockfree-queue >= 0.2.3 && < 0.3
124+
125+ -- transfomers
122126 , exceptions >= 0.8 && < 0.11
123127 , lifted-base >= 0.2 && < 0.3
124− , lockfree-queue >= 0.2.3 && < 0.3
125128 , monad-control >= 1.0 && < 2
126129 , mtl >= 2.2 && < 3
127− , stm >= 2.4.3 && < 2.5
128130 , transformers >= 0.4 && < 0.6
129131 , transformers-base >= 0.4 && < 0.5
130132
test/Main.hs+98−2
@@ -115,6 +115,7 @@ main = hspec $ do
115115 ---------------------------------------------------------------------------
116116
117117 describe "Serial Composition" $ compose serially mempty id
118+ describe "Ahead Composition" $ compose aheadly mempty id
118119 describe "WSerial Composition" $ compose wSerially mempty sort
119120 describe "Async Composition" $ compose asyncly mempty sort
120121 describe "WAsync Composition" $ compose wAsyncly mempty sort
@@ -128,7 +129,10 @@ main = hspec $ do
128129 ---------------------------------------------------------------------------
129130
130131 -- TBD need more such combinations to be tested.
131− describe "<> and <>" $ composeAndComposeSimple serially serially (cycle [[1 .. 9]])
132+ describe "serial <> and serial <>" $ composeAndComposeSimple serially serially (cycle [[1 .. 9]])
133+ describe "ahead <> and ahead <>" $ composeAndComposeSimple aheadly aheadly (cycle [[1 .. 9]])
134+ describe "ahead <> and serial <>" $ composeAndComposeSimple aheadly serially (cycle [[1 .. 9]])
135+ describe "serial <> and ahead <>" $ composeAndComposeSimple serially aheadly (cycle [[1 .. 9]])
132136
133137 describe "<> and <=>" $ composeAndComposeSimple
134138 serially
@@ -203,6 +207,7 @@ main = hspec $ do
203207 ---------------------------------------------------------------------------
204208
205209 describe "Serial loops" $ loops serially id reverse
210+ describe "Ahead loops" $ loops aheadly id reverse
206211 describe "Async parallel loops" $ loops asyncly sort sort
207212 describe "WAsync loops" $ loops wAsyncly sort sort
208213 describe "parallel loops" $ loops parallely sort sort
@@ -216,30 +221,42 @@ main = hspec $ do
216221 describe "Bind and compose Stream 3" $ bindAndComposeSimple serially asyncly
217222 describe "Bind and compose Stream 4" $ bindAndComposeSimple serially wAsyncly
218223 describe "Bind and compose Stream 5" $ bindAndComposeSimple serially parallely
224+ describe "Bind and compose Stream 6" $ bindAndComposeSimple serially aheadly
225+
226+ describe "Bind and compose Ahead Stream 0" $ bindAndComposeSimple aheadly aheadly
227+ describe "Bind and compose Ahead Stream 1" $ bindAndComposeSimple aheadly serially
228+ describe "Bind and compose Ahead Stream 2" $ bindAndComposeSimple aheadly wSerially
229+ describe "Bind and compose Ahead Stream 3" $ bindAndComposeSimple aheadly asyncly
230+ describe "Bind and compose Ahead Stream 4" $ bindAndComposeSimple aheadly wAsyncly
231+ describe "Bind and compose Ahead Stream 5" $ bindAndComposeSimple aheadly parallely
219232
220233 describe "Bind and compose Costream 1" $ bindAndComposeSimple wSerially serially
221234 describe "Bind and compose Costream 2" $ bindAndComposeSimple wSerially wSerially
222235 describe "Bind and compose Costream 3" $ bindAndComposeSimple wSerially asyncly
223236 describe "Bind and compose Costream 4" $ bindAndComposeSimple wSerially wAsyncly
224237 describe "Bind and compose Costream 5" $ bindAndComposeSimple wSerially parallely
238+ describe "Bind and compose Costream 6" $ bindAndComposeSimple wSerially aheadly
225239
226240 describe "Bind and compose Async 1" $ bindAndComposeSimple asyncly serially
227241 describe "Bind and compose Async 2" $ bindAndComposeSimple asyncly wSerially
228242 describe "Bind and compose Async 3" $ bindAndComposeSimple asyncly asyncly
229243 describe "Bind and compose Async 4" $ bindAndComposeSimple asyncly wAsyncly
230244 describe "Bind and compose Async 5" $ bindAndComposeSimple asyncly parallely
245+ describe "Bind and compose Async 6" $ bindAndComposeSimple asyncly aheadly
231246
232247 describe "Bind and compose WAsync 1" $ bindAndComposeSimple wAsyncly serially
233248 describe "Bind and compose WAsync 2" $ bindAndComposeSimple wAsyncly wSerially
234249 describe "Bind and compose WAsync 3" $ bindAndComposeSimple wAsyncly asyncly
235250 describe "Bind and compose WAsync 4" $ bindAndComposeSimple wAsyncly wAsyncly
236251 describe "Bind and compose WAsync 5" $ bindAndComposeSimple wAsyncly parallely
252+ describe "Bind and compose WAsync 6" $ bindAndComposeSimple wAsyncly aheadly
237253
238254 describe "Bind and compose Parallel 1" $ bindAndComposeSimple parallely serially
239255 describe "Bind and compose Parallel 2" $ bindAndComposeSimple parallely wSerially
240256 describe "Bind and compose Parallel 3" $ bindAndComposeSimple parallely asyncly
241257 describe "Bind and compose Parallel 4" $ bindAndComposeSimple parallely wAsyncly
242258 describe "Bind and compose Parallel 5" $ bindAndComposeSimple parallely parallely
259+ describe "Bind and compose Parallel 6" $ bindAndComposeSimple parallely aheadly
243260
244261 let fldr, fldl :: (IsStream t, Semigroup (t IO Int)) => [t IO Int] -> t IO Int
245262 fldr = foldr (<>) nil
@@ -255,6 +272,21 @@ main = hspec $ do
255272 describe "Bind and compose" $ bindAndComposeHierarchy serially wAsyncly k
256273 forM_ [fldr, fldl] $ \k ->
257274 describe "Bind and compose" $ bindAndComposeHierarchy serially parallely k
275+ forM_ [fldr, fldl] $ \k ->
276+ describe "Bind and compose serially aheadly" $ bindAndComposeHierarchy serially aheadly k
277+
278+ forM_ [fldr, fldl] $ \k ->
279+ describe "Bind and compose aheadly serially" $ bindAndComposeHierarchy aheadly serially k
280+ forM_ [fldr, fldl] $ \k ->
281+ describe "Bind and compose aheadly wSerially" $ bindAndComposeHierarchy aheadly wSerially k
282+ forM_ [fldr, fldl] $ \k ->
283+ describe "Bind and compose aheadly asyncly" $ bindAndComposeHierarchy aheadly asyncly k
284+ forM_ [fldr, fldl] $ \k ->
285+ describe "Bind and compose aheadly wAsyncly" $ bindAndComposeHierarchy aheadly wAsyncly k
286+ forM_ [fldr, fldl] $ \k ->
287+ describe "Bind and compose aheadly parallely" $ bindAndComposeHierarchy aheadly parallely k
288+ forM_ [fldr, fldl] $ \k ->
289+ describe "Bind and compose serially aheadly" $ bindAndComposeHierarchy aheadly aheadly k
258290
259291 forM_ [fldr, fldl] $ \k ->
260292 describe "Bind and compose" $ bindAndComposeHierarchy wSerially serially k
@@ -266,6 +298,8 @@ main = hspec $ do
266298 describe "Bind and compose" $ bindAndComposeHierarchy wSerially wAsyncly k
267299 forM_ [fldr, fldl] $ \k ->
268300 describe "Bind and compose" $ bindAndComposeHierarchy wSerially parallely k
301+ forM_ [fldr, fldl] $ \k ->
302+ describe "Bind and compose wserially aheadly" $ bindAndComposeHierarchy wSerially aheadly k
269303
270304 forM_ [fldr, fldl] $ \k ->
271305 describe "Bind and compose" $ bindAndComposeHierarchy asyncly serially k
@@ -277,6 +311,8 @@ main = hspec $ do
277311 describe "Bind and compose" $ bindAndComposeHierarchy asyncly wAsyncly k
278312 forM_ [fldr, fldl] $ \k ->
279313 describe "Bind and compose" $ bindAndComposeHierarchy asyncly parallely k
314+ forM_ [fldr, fldl] $ \k ->
315+ describe "Bind and compose asyncly aheadly" $ bindAndComposeHierarchy asyncly aheadly k
280316
281317 forM_ [fldr, fldl] $ \k ->
282318 describe "Bind and compose" $ bindAndComposeHierarchy wAsyncly serially k
@@ -288,6 +324,8 @@ main = hspec $ do
288324 describe "Bind and compose" $ bindAndComposeHierarchy wAsyncly wAsyncly k
289325 forM_ [fldr, fldl] $ \k ->
290326 describe "Bind and compose" $ bindAndComposeHierarchy wAsyncly parallely k
327+ forM_ [fldr, fldl] $ \k ->
328+ describe "Bind and compose wAsyncly aheadly" $ bindAndComposeHierarchy wAsyncly aheadly k
291329
292330 forM_ [fldr, fldl] $ \k ->
293331 describe "Bind and compose" $ bindAndComposeHierarchy parallely serially k
@@ -299,15 +337,19 @@ main = hspec $ do
299337 describe "Bind and compose" $ bindAndComposeHierarchy parallely wAsyncly k
300338 forM_ [fldr, fldl] $ \k ->
301339 describe "Bind and compose" $ bindAndComposeHierarchy parallely parallely k
340+ forM_ [fldr, fldl] $ \k ->
341+ describe "Bind and compose parallely aheadly" $ bindAndComposeHierarchy parallely aheadly k
302342
303343 -- Nest two lists using different styles of product compositions
304344 it "Nests two streams using monadic serial composition" nestTwoSerial
345+ it "Nests two streams using monadic ahead composition" nestTwoAhead
305346 it "Nests two streams using monadic interleaved composition" nestTwoInterleaved
306347 it "Nests two streams using monadic Async composition" nestTwoAsync
307348 it "Nests two streams using monadic WAsync composition" nestTwoWAsync
308349 it "Nests two streams using monadic parallel composition" nestTwoParallel
309350
310351 it "Nests two streams using applicative serial composition" nestTwoSerialApp
352+ it "Nests two streams using applicative ahead composition" nestTwoAheadApp
311353 it "Nests two streams using applicative interleaved composition" nestTwoInterleavedApp
312354 it "Nests two streams using applicative Async composition" nestTwoAsyncApp
313355 it "Nests two streams using applicative WAsync composition" nestTwoWAsyncApp
@@ -319,6 +361,7 @@ main = hspec $ do
319361
320362 -- TBD combine all binds and all compose in one example
321363 describe "Miscellaneous combined examples" mixedOps
364+ describe "Miscellaneous combined examples aheadly" mixedOpsAheadly
322365 describe "Simple MonadError and MonadThrow" simpleMonadError
323366
324367 {-
@@ -333,12 +376,18 @@ main = hspec $ do
333376 describe "Composed MonadThrow asyncly" $ composeWithMonadThrow asyncly
334377 describe "Composed MonadThrow wAsyncly" $ composeWithMonadThrow wAsyncly
335378 describe "Composed MonadThrow parallely" $ composeWithMonadThrow parallely
379+ describe "Composed MonadThrow aheadly" $ composeWithMonadThrow aheadly
336380
337− it "Crosses thread limit (2000 threads)" $
381+ it "asyncly crosses thread limit (2000 threads)" $
338382 runStream (asyncly $ fold $
339383 replicate 2000 $ A.once $ threadDelay 1000000)
340384 `shouldReturn` ()
341385
386+ it "aheadly crosses thread limit (4000 threads)" $
387+ runStream (aheadly $ fold $
388+ replicate 4000 $ A.once $ threadDelay 1000000)
389+ `shouldReturn` ()
390+
342391-- XXX need to test that we have promptly cleaned up everything after the error
343392-- XXX We can also check the output that we are expected to get before the
344393-- error occurs.
@@ -432,6 +481,16 @@ nestTwoSerial =
432481 return (x + y)
433482 ) `shouldReturn` ([6,7,8,9,7,8,9,10,8,9,10,11,9,10,11,12] :: [Int])
434483
484+nestTwoAhead :: Expectation
485+nestTwoAhead =
486+ let s1 = foldMapWith (<>) return [1..4]
487+ s2 = foldMapWith (<>) return [5..8]
488+ in (A.toList . aheadly) (do
489+ x <- s1
490+ y <- s2
491+ return (x + y)
492+ ) `shouldReturn` ([6,7,8,9,7,8,9,10,8,9,10,11,9,10,11,12] :: [Int])
493+
435494nestTwoSerialApp :: Expectation
436495nestTwoSerialApp =
437496 let s1 = foldMapWith (<>) return [1..4]
@@ -439,6 +498,13 @@ nestTwoSerialApp =
439498 in toListSerial ((+) <$> s1 <*> s2)
440499 `shouldReturn` ([6,7,8,9,7,8,9,10,8,9,10,11,9,10,11,12] :: [Int])
441500
501+nestTwoAheadApp :: Expectation
502+nestTwoAheadApp =
503+ let s1 = foldMapWith (<>) return [1..4]
504+ s2 = foldMapWith (<>) return [5..8]
505+ in (A.toList . aheadly) ((+) <$> s1 <*> s2)
506+ `shouldReturn` ([6,7,8,9,7,8,9,10,8,9,10,11,9,10,11,12] :: [Int])
507+
442508nestTwoInterleaved :: Expectation
443509nestTwoInterleaved =
444510 let s1 = foldMapWith (<>) return [1..4]
@@ -706,3 +772,33 @@ mixedOps = do
706772 return (x11 + y11 + z11)
707773 return (x1 + y1 + z1)
708774 return (x + y + z)
775+
776+mixedOpsAheadly :: Spec
777+mixedOpsAheadly = do
778+ it "Compose many ops" $
779+ (toListSerial composeMixed >>= return . sort)
780+ `shouldReturn` ([8,9,9,9,9,9,10,10,10,10,10,10,10,10,10,10,11,11
781+ ,11,11,11,11,11,11,11,11,12,12,12,12,12,13
782+ ] :: [Int])
783+ where
784+
785+ composeMixed :: SerialT IO Int
786+ composeMixed = do
787+ A.once $ return ()
788+ A.once $ putStr ""
789+ x <- return 1
790+ y <- return 2
791+ z <- do
792+ x1 <- wAsyncly $ return 1 <> return 2
793+ A.once $ return ()
794+ A.once $ putStr ""
795+ y1 <- aheadly $ return 1 <> return 2
796+ z1 <- do
797+ x11 <- return 1 <> return 2
798+ y11 <- aheadly $ return 1 <> return 2
799+ z11 <- parallely $ return 1 <> return 2
800+ A.once $ return ()
801+ A.once $ putStr ""
802+ return (x11 + y11 + z11)
803+ return (x1 + y1 + z1)
804+ return (x + y + z)
test/Prop.hs+19−1
@@ -318,6 +318,8 @@ main = hspec $ do
318318 functorOps folded "serially folded" serially (==)
319319 functorOps A.fromFoldable "wSerially" wSerially (==)
320320 functorOps folded "wSerially folded" wSerially (==)
321+ functorOps A.fromFoldable "aheadly" aheadly (==)
322+ functorOps folded "aheadly folded" aheadly (==)
321323 functorOps A.fromFoldable "asyncly" asyncly sortEq
322324 functorOps folded "asyncly folded" asyncly sortEq
323325 functorOps A.fromFoldable "wAsyncly" wAsyncly sortEq
@@ -332,6 +334,7 @@ main = hspec $ do
332334 describe "Semigroup operations" $ do
333335 semigroupOps "serially" serially (==)
334336 semigroupOps "wSerially" wSerially (==)
337+ semigroupOps "aheadly" aheadly (==)
335338 semigroupOps "asyncly" asyncly sortEq
336339 semigroupOps "wAsyncly" wAsyncly sortEq
337340 semigroupOps "parallely" parallely sortEq
@@ -344,6 +347,8 @@ main = hspec $ do
344347 -- XXX applicative with three arguments
345348 prop "serially applicative" $ applicativeOps A.fromFoldable serially (==)
346349 prop "serially applicative folded" $ applicativeOps folded serially (==)
350+ prop "aheadly applicative" $ applicativeOps A.fromFoldable aheadly (==)
351+ prop "aheadly applicative folded" $ applicativeOps folded aheadly (==)
347352 prop "wSerially applicative" $ applicativeOps A.fromFoldable wSerially sortEq
348353 prop "wSerially applicative folded" $ applicativeOps folded wSerially sortEq
349354 prop "asyncly applicative" $ applicativeOps A.fromFoldable asyncly sortEq
@@ -357,6 +362,8 @@ main = hspec $ do
357362 -- prop "zipAsyncly applicative" $ zipApplicative zipAsyncly (==)
358363 prop "zip monadic serially" $ zipMonadic A.fromFoldable serially (==)
359364 prop "zip monadic serially folded" $ zipMonadic folded serially (==)
365+ prop "zip monadic aheadly" $ zipMonadic A.fromFoldable aheadly (==)
366+ prop "zip monadic aheadly folded" $ zipMonadic folded aheadly (==)
360367 prop "zip monadic wSerially" $ zipMonadic A.fromFoldable wSerially (==)
361368 prop "zip monadic wSerially folded" $ zipMonadic folded wSerially (==)
362369 prop "zip monadic asyncly" $ zipMonadic A.fromFoldable asyncly (==)
@@ -368,18 +375,21 @@ main = hspec $ do
368375
369376 describe "Monad operations" $ do
370377 prop "serially monad then" $ monadThen A.fromFoldable serially (==)
378+ prop "aheadly monad then" $ monadThen A.fromFoldable aheadly (==)
371379 prop "wSerially monad then" $ monadThen A.fromFoldable wSerially sortEq
372380 prop "asyncly monad then" $ monadThen A.fromFoldable asyncly sortEq
373381 prop "wAsyncly monad then" $ monadThen A.fromFoldable wAsyncly sortEq
374382 prop "parallely monad then" $ monadThen A.fromFoldable parallely sortEq
375383
376384 prop "serially monad then folded" $ monadThen folded serially (==)
385+ prop "aheadly monad then folded" $ monadThen folded aheadly (==)
377386 prop "wSerially monad then folded" $ monadThen folded wSerially sortEq
378387 prop "asyncly monad then folded" $ monadThen folded asyncly sortEq
379388 prop "wAsyncly monad then folded" $ monadThen folded wAsyncly sortEq
380389 prop "parallely monad then folded" $ monadThen folded parallely sortEq
381390
382391 prop "serially monad bind" $ monadBind A.fromFoldable serially (==)
392+ prop "aheadly monad bind" $ monadBind A.fromFoldable aheadly (==)
383393 prop "wSerially monad bind" $ monadBind A.fromFoldable wSerially sortEq
384394 prop "asyncly monad bind" $ monadBind A.fromFoldable asyncly sortEq
385395 prop "wAsyncly monad bind" $ monadBind A.fromFoldable wAsyncly sortEq
@@ -387,6 +397,7 @@ main = hspec $ do
387397
388398 describe "Stream transform operations" $ do
389399 transformOps A.fromFoldable "serially" serially (==)
400+ transformOps A.fromFoldable "aheadly" aheadly (==)
390401 transformOps A.fromFoldable "wSerially" wSerially (==)
391402 transformOps A.fromFoldable "zipSerially" zipSerially (==)
392403 transformOps A.fromFoldable "zipAsyncly" zipAsyncly (==)
@@ -395,6 +406,7 @@ main = hspec $ do
395406 transformOps A.fromFoldable "parallely" parallely sortEq
396407
397408 transformOps folded "serially folded" serially (==)
409+ transformOps folded "aheadly folded" aheadly (==)
398410 transformOps folded "wSerially folded" wSerially (==)
399411 transformOps folded "zipSerially folded" zipSerially (==)
400412 transformOps folded "zipAsyncly folded" zipAsyncly (==)
@@ -403,6 +415,7 @@ main = hspec $ do
403415 transformOps folded "parallely folded" parallely sortEq
404416
405417 transformOpsWord8 A.fromFoldable "serially" serially
418+ transformOpsWord8 A.fromFoldable "aheadly" aheadly
406419 transformOpsWord8 A.fromFoldable "wSerially" wSerially
407420 transformOpsWord8 A.fromFoldable "zipSerially" zipSerially
408421 transformOpsWord8 A.fromFoldable "zipAsyncly" zipAsyncly
@@ -411,6 +424,7 @@ main = hspec $ do
411424 transformOpsWord8 A.fromFoldable "parallely" parallely
412425
413426 transformOpsWord8 folded "serially folded" serially
427+ transformOpsWord8 folded "aheadly folded" aheadly
414428 transformOpsWord8 folded "wSerially folded" wSerially
415429 transformOpsWord8 folded "zipSerially folded" zipSerially
416430 transformOpsWord8 folded "zipAsyncly folded" zipAsyncly
@@ -420,6 +434,7 @@ main = hspec $ do
420434
421435 describe "Stream elimination operations" $ do
422436 eliminationOps A.fromFoldable "serially" serially
437+ eliminationOps A.fromFoldable "aheadly" aheadly
423438 eliminationOps A.fromFoldable "wSerially" wSerially
424439 eliminationOps A.fromFoldable "zipSerially" zipSerially
425440 eliminationOps A.fromFoldable "zipAsyncly" zipAsyncly
@@ -428,6 +443,7 @@ main = hspec $ do
428443 eliminationOps A.fromFoldable "parallely" parallely
429444
430445 eliminationOps folded "serially folded" serially
446+ eliminationOps folded "aheadly folded" aheadly
431447 eliminationOps folded "wSerially folded" wSerially
432448 eliminationOps folded "zipSerially folded" zipSerially
433449 eliminationOps folded "zipAsyncly folded" zipAsyncly
@@ -435,13 +451,15 @@ main = hspec $ do
435451 eliminationOps folded "wAsyncly folded" wAsyncly
436452 eliminationOps folded "parallely folded" parallely
437453
438− describe "Stream elimination operations" $ do
454+ describe "Stream serial elimination operations" $ do
439455 serialEliminationOps A.fromFoldable "serially" serially
456+ serialEliminationOps A.fromFoldable "aheadly" aheadly
440457 serialEliminationOps A.fromFoldable "wSerially" wSerially
441458 serialEliminationOps A.fromFoldable "zipSerially" zipSerially
442459 serialEliminationOps A.fromFoldable "zipAsyncly" zipAsyncly
443460
444461 serialEliminationOps folded "serially folded" serially
462+ serialEliminationOps folded "aheadly folded" aheadly
445463 serialEliminationOps folded "wSerially folded" wSerially
446464 serialEliminationOps folded "zipSerially folded" zipSerially
447465 serialEliminationOps folded "zipAsyncly folded" zipAsyncly