on master help sign in
Add concurrent function application operators
commite60357957c2aef7de902053ff10a7c3389aaea67 authorHarendra Kumar <harendra.kumar@gmail.com> dateSat 26 May 2018, 17:02 (8 years ago) parent092ecb9
7 files changed+341−57
Changelog.md+5−1 src/Streamly.hs+34−14 src/Streamly/Core.hs+86−29 src/Streamly/Prelude.hs+47 src/Streamly/Streams.hs+91−9 src/Streamly/Tutorial.hs+1 test/Prop.hs+77−4
Changelog.md+5−1
@@ -2,8 +2,10 @@
22
33### Breaking changes
44
5−* Some prelude functions, to which concurrency capability has been added, will
5+* Some prelude functions, to whom concurrency capability has been added, will
66 now require a `MonadAsync` constraint.
7+* Remove the deprecated "<|" operator and reuse it for reverse concurrent fold
8+ application operator.
79
810### Bug Fixes
911
@@ -22,6 +24,8 @@
2224 concurrently when used with concurrent stream types.
2325* Monad transformation functions `mapM` and `sequence` can now map actions
2426 concurrently when used at appropriate stream types.
27+* Added concurrent function application operators to run stages of a
28+ stream processing function application pipeline concurrently.
2529
2630## 0.2.0
2731
src/Streamly.hs+34−14
@@ -80,7 +80,18 @@ module Streamly
8080 , ZipSerialM
8181 , ZipAsyncM
8282
83− -- * Polymorphic Sum Operations
83+ -- * Running Streams
84+ , runStream
85+
86+ -- * Parallel Function Application
87+ -- $application
88+ , (|$)
89+ , (|&)
90+ , (<|)
91+ , (|>)
92+ , mkAsync
93+
94+ -- * Merging Streams
8495 -- $sum
8596 , serial
8697 , wSerial
@@ -89,6 +100,12 @@ module Streamly
89100 , wAsync
90101 , parallel
91102
103+ -- * Folding Containers of Streams
104+ -- $foldutils
105+ , foldWith
106+ , foldMapWith
107+ , forEachWith
108+
92109 -- * Stream Type Adapters
93110 -- $adapters
94111 , IsStream ()
@@ -113,18 +130,6 @@ module Streamly
113130 , ZipSerial
114131 , ZipAsync
115132
116− -- * Running Streams
117− , runStream
118−
119− -- * Transformation
120− , mkAsync
121−
122− -- * Polymorphic Fold Utilities
123− -- $foldutils
124− , foldWith
125− , foldMapWith
126− , forEachWith
127−
128133 -- * Re-exports
129134 , Semigroup (..)
130135 -- * Deprecated
@@ -143,7 +148,6 @@ module Streamly
143148 , zipping
144149 , zippingAsync
145150 , (<=>)
146− , (<|)
147151 )
148152where
149153
@@ -174,6 +178,22 @@ import Data.Semigroup (Semigroup(..))
174178-- corresponding elements of two streams together. Note that these types are
175179-- not monads.
176180
181+-- $application
182+--
183+-- Several stream processing functions can be called in a chain using regular
184+-- function application, using the '$' operator or reverse function application
185+-- operator '&'. This section provides corresponding operators that can apply
186+-- stream processing functions such that ach stage of the stream can run in
187+-- parallel.
188+--
189+-- Imports for the code snippets below:
190+--
191+-- @
192+-- import Streamly
193+-- import qualified Streamly.Prelude as S
194+-- import Control.Concurrent
195+-- @
196+
177197-- $sum
178198-- The 'Semigroup' operation '<>' of each stream type combines two streams in a
179199-- type specific manner. This section provides polymorphic versions of '<>'
src/Streamly/Core.hs+86−29
@@ -48,6 +48,10 @@ module Streamly.Core
4848 , wAsync
4949 , parallel
5050
51+ -- * applications
52+ , applyWith
53+ , runWith
54+
5155 -- * zip
5256 , zipWith
5357 , zipAsyncWith
@@ -66,18 +70,10 @@ module Streamly.Core
6670 )
6771where
6872
69−-- MVar diagnostics has some overhead - around 5% on asyncly null benchmark,
70−-- but keep it on for now to debug problems quickly if and when they happen.
71−#define DIAGNOSTICS
72−
7373import Control.Concurrent (ThreadId, myThreadId, threadDelay)
7474import Control.Concurrent.MVar (MVar, newEmptyMVar, tryTakeMVar,
7575 tryPutMVar, takeMVar, putMVar)
7676import Control.Exception (SomeException (..))
77−#ifdef DIAGNOSTICS
78−import Control.Exception (catch,
79− BlockedIndefinitelyOnMVar(..))
80−#endif
8177import qualified Control.Exception.Lifted as EL
8278import Control.Monad (when)
8379import Control.Monad.Catch (MonadThrow, throwM)
@@ -105,6 +101,17 @@ import GHC.Exts
105101import GHC.Conc (ThreadId(..))
106102import GHC.IO (IO(..))
107103
104+-- MVar diagnostics has some overhead - around 5% on asyncly null benchmark,
105+-- but keep it on for now to debug problems quickly if and when they happen.
106+#define DIAGNOSTICS
107+
108+#ifdef DIAGNOSTICS
109+import Control.Exception (catches, throwIO, Handler(..),
110+ BlockedIndefinitelyOnMVar(..),
111+ BlockedIndefinitelyOnSTM(..))
112+import System.IO (hPutStrLn, stderr)
113+#endif
114+
108115------------------------------------------------------------------------------
109116-- Parent child thread communication type
110117------------------------------------------------------------------------------
@@ -193,41 +200,63 @@ data SVar m a =
193200{-# NOINLINE dumpSVar #-}
194201dumpSVar :: SVar m a -> IO String
195202dumpSVar sv = do
203+ tid <- myThreadId
196204 (oqList, oqLen) <- readIORef $ outputQueue sv
197205 db <- tryTakeMVar $ doorBell sv
198206 srn <- tryTakeMVar $ siren sv
199− (oheap, oheapSize) <- readIORef $ outputHeap sv
200− hsrn <- tryTakeMVar $ heapSiren sv
201− (wq, wqSize) <- readIORef $ workQueue sv
207+ aheadDump <-
208+ if svarStyle sv == AheadVar
209+ then do
210+ (oheap, oheapSize) <- readIORef $ outputHeap sv
211+ hsrn <- tryTakeMVar $ heapSiren sv
212+ (wq, wqSize) <- readIORef $ workQueue sv
213+ return $ unlines
214+ [ "heap length computed = " ++ show (H.size oheap)
215+ , "heap length maintained = " ++ show oheapSize
216+ , "heap siren = " ++ show hsrn
217+ , "work queue length computed = " ++ show (length wq)
218+ , "work queue length maintained = " ++ show wqSize
219+ ]
220+ else return []
221+
202222 waiting <- readIORef $ waitingForWork sv
203223 rthread <- readIORef $ runningThreads sv
204224 workers <- readIORef $ activeWorkers sv
205225 -- XXX queueEmpty should be made IO return type
206226
207227 return $ unlines
208− [ "style = " ++ show (svarStyle sv)
228+ [ "tid = " ++ show tid
229+ , "style = " ++ show (svarStyle sv)
209230 , "outputQueue length computed = " ++ show (length oqList)
210231 , "outputQueue length maintained = " ++ show oqLen
211232 , "output doorBell = " ++ show db
212233 , "worker siren = " ++ show srn
213− , "heap length computed = " ++ show (H.size oheap)
214− , "heap length maintained = " ++ show oheapSize
215− , "heap siren = " ++ show hsrn
216− , "work queue length computed = " ++ show (length wq)
217− , "work queue length maintained = " ++ show wqSize
218− , "waitingForWork = " ++ show waiting
234+ ]
235+ ++ aheadDump ++ unlines
236+ [ "waitingForWork = " ++ show waiting
219237 , "running threads = " ++ show rthread
220238 , "running thread count = " ++ show workers
221239 ]
222240
223241{-# NOINLINE mvarExcHandler #-}
224242mvarExcHandler :: SVar m a -> String -> BlockedIndefinitelyOnMVar -> IO ()
225−mvarExcHandler sv label BlockedIndefinitelyOnMVar = do
243+mvarExcHandler sv label e@BlockedIndefinitelyOnMVar = do
226244 svInfo <- dumpSVar sv
227− error $ label ++ " " ++ "BlockedIndefinitelyOnMVar\n" ++ svInfo
245+ hPutStrLn stderr $ label ++ " " ++ "BlockedIndefinitelyOnMVar\n" ++ svInfo
246+ throwIO e
247+
248+{-# NOINLINE stmExcHandler #-}
249+stmExcHandler :: SVar m a -> String -> BlockedIndefinitelyOnSTM -> IO ()
250+stmExcHandler sv label e@BlockedIndefinitelyOnSTM = do
251+ svInfo <- dumpSVar sv
252+ hPutStrLn stderr $ label ++ " " ++ "BlockedIndefinitelyOnSTM\n" ++ svInfo
253+ throwIO e
228254
229255withDBGMVar :: SVar m a -> String -> IO () -> IO ()
230−withDBGMVar sv label action = action `catch` mvarExcHandler sv label
256+withDBGMVar sv label action =
257+ action `catches` [ Handler (mvarExcHandler sv label)
258+ , Handler (stmExcHandler sv label)
259+ ]
231260#else
232261withDBGMVar :: SVar m a -> String -> IO () -> IO ()
233262withDBGMVar _ _ action = action
@@ -759,7 +788,7 @@ delThread sv tid =
759788-- If present then delete else add. This takes care of out of order add and
760789-- delete i.e. a delete arriving before we even added a thread.
761790-- This occurs when the forked thread is done even before the 'addThread' right
762−-- after the fork is executed.
791+-- after the fork gets a chance to run.
763792{-# INLINE modifyThread #-}
764793modifyThread :: MonadIO m => SVar m a -> ThreadId -> m ()
765794modifyThread sv tid = do
@@ -797,7 +826,10 @@ pushWorker sv = do
797826-- using a CAS based modification.
798827{-# NOINLINE pushWorkerPar #-}
799828pushWorkerPar :: MonadAsync m => SVar m a -> Stream m a -> m ()
800−pushWorkerPar sv m =
829+pushWorkerPar sv m = do
830+ -- We do not use activeWorkers in case of ParallelVar but still there is no
831+ -- harm in maintaining it correctly.
832+ liftIO $ atomicModifyIORefCAS_ (activeWorkers sv) $ \n -> n + 1
801833 doFork (runOne sv m) (handleChildException sv) >>= modifyThread sv
802834
803835-- XXX When the queue is LIFO we can put a limit on the number of dispatches.
@@ -1050,8 +1082,11 @@ newStreamVar1 style m = do
10501082 -- Note: We must have all the work on the queue before sending the
10511083 -- pushworker, otherwise the pushworker may exit before we even get a
10521084 -- chance to push.
1053− liftIO $ (enqueue sv) m
1054− pushWorker sv
1085+ if style == ParallelVar
1086+ then pushWorkerPar sv m
1087+ else do
1088+ liftIO $ (enqueue sv) m
1089+ pushWorker sv
10551090 return sv
10561091
10571092-- | Create a new SVar and enqueue one stream computation on it.
@@ -1245,6 +1280,26 @@ _alt m1 m2 = Stream $ \_ stp sng yld ->
12451280 let stop = runStream m2 Nothing stp sng yld
12461281 in runStream m1 Nothing stop sng yld
12471282
1283+------------------------------------------------------------------------------
1284+-- Stream to stream function application
1285+------------------------------------------------------------------------------
1286+
1287+applyWith :: MonadAsync m
1288+ => SVarStyle -> (Stream m a -> Stream m b) -> Stream m a -> Stream m b
1289+applyWith style f m = Stream $ \svr stp sng yld -> do
1290+ sv <- newStreamVar1 style m
1291+ runStream (f $ fromStreamVar sv) svr stp sng yld
1292+
1293+------------------------------------------------------------------------------
1294+-- Stream runner function application
1295+------------------------------------------------------------------------------
1296+
1297+runWith :: MonadAsync m
1298+ => SVarStyle -> (Stream m a -> m b) -> Stream m a -> m b
1299+runWith style f m = do
1300+ sv <- newStreamVar1 style m
1301+ f $ fromStreamVar sv
1302+
12481303------------------------------------------------------------------------------
12491304-- Zipping
12501305------------------------------------------------------------------------------
@@ -1261,10 +1316,6 @@ zipWith f m1 m2 = go m1 m2
12611316 yield1 a ra = merge a ra
12621317 (runStream mx) Nothing stp single1 yield1
12631318
1264−mkAsync :: MonadAsync m => Stream m a -> m (Stream m a)
1265−mkAsync m = newStreamVar1 AsyncVar m
1266− >>= return . fromStreamVar
1267−
12681319zipAsyncWith :: MonadAsync m
12691320 => (a -> b -> c) -> Stream m a -> Stream m b -> Stream m c
12701321zipAsyncWith f m1 m2 = Stream $ \_ stp sng yld -> do
@@ -1272,6 +1323,12 @@ zipAsyncWith f m1 m2 = Stream $ \_ stp sng yld -> do
12721323 mb <- mkAsync m2
12731324 (runStream (zipWith f ma mb)) Nothing stp sng yld
12741325
1326+ where
1327+
1328+ mkAsync :: MonadAsync m => Stream m a -> m (Stream m a)
1329+ mkAsync m = newStreamVar1 AsyncVar m
1330+ >>= return . fromStreamVar
1331+
12751332-------------------------------------------------------------------------------
12761333-- Transformers
12771334-------------------------------------------------------------------------------
src/Streamly/Prelude.hs+47
@@ -191,6 +191,13 @@ unfoldr step = fromStream . go
191191-- 2
192192-- 3
193193-- @
194+-- When run concurrently, the next unfold can run concurrently while previously
195+-- generated value is being processed by the next stream stage.
196+--
197+-- @
198+-- (asyncly $ S.unfoldrM (\\n -> liftIO (threadDelay 1000000) >> return (Just (n, n + 1))) 0)
199+-- & S.foldlM' (\\_ a -> threadDelay 1000000 >> print a) ()
200+-- @
194201--
195202-- /Concurrent/
196203--
@@ -215,6 +222,11 @@ fromFoldable = Prelude.foldr cons nil
215222
216223-- | Construct a stream from a 'Foldable' containing monadic actions.
217224--
225+-- @
226+-- runStream $ serially $ S.fromFoldableM $ replicate 10 (threadDelay 1000000 >> print 1)
227+-- runStream $ asyncly $ S.fromFoldableM $ replicate 10 (threadDelay 1000000 >> print 1)
228+-- @
229+--
218230-- /Concurrent (do not use with 'parallely' on infinite containers)/
219231--
220232-- @since 0.3.0
@@ -245,6 +257,12 @@ once = fromStream . S.once
245257
246258-- | Generate a stream by performing a monadic action @n@ times.
247259--
260+--
261+-- @
262+-- runStream $ serially $ S.replicateM 10 $ (threadDelay 1000000 >> print 1)
263+-- runStream $ asyncly $ S.replicateM 10 $ (threadDelay 1000000 >> print 1)
264+-- @
265+--
248266-- /Concurrent/
249267--
250268-- @since 0.1.1
@@ -255,6 +273,11 @@ replicateM n m = go n
255273
256274-- | Generate a stream by repeatedly executing a monadic action forever.
257275--
276+-- @
277+-- runStream $ serially $ S.take 10 $ S.repeatM $ (threadDelay 1000000 >> print 1)
278+-- runStream $ asyncly $ S.take 10 $ S.repeatM $ (threadDelay 1000000 >> print 1)
279+-- @
280+--
258281-- /Concurrent, infinite (do not use with 'parallely')/
259282--
260283-- @since 0.2.0
@@ -273,6 +296,14 @@ iterate step = fromStream . go
273296-- | Iterate a monadic function from a seed value, streaming the results
274297-- forever.
275298--
299+-- @
300+-- runStream $ serially $ S.take 10 $ S.iterateM
301+-- (\\x -> threadDelay 1000000 >> print x >> return (x + 1)) 0
302+--
303+-- runStream $ asyncly $ S.take 10 $ S.iterateM
304+-- (\\x -> threadDelay 1000000 >> print x >> return (x + 1)) 0
305+-- @
306+--
276307-- /Concurrent, infinite (do not use with 'parallely')/
277308--
278309-- @since 0.1.2
@@ -699,6 +730,14 @@ maximum m = go Nothing (toStream m)
699730-- | Replace each element of the stream with the result of a monadic action
700731-- applied on the element.
701732--
733+-- @
734+-- runStream $ S.replicateM 10 (return 1)
735+-- & (serially . S.mapM (\\x -> threadDelay 1000000 >> print x))
736+--
737+-- runStream $ S.replicateM 10 (return 1)
738+-- & (asyncly . S.mapM (\\x -> threadDelay 1000000 >> print x))
739+-- @
740+--
702741-- /Concurrent (do not use with 'parallely' on infinite streams)/
703742--
704743-- @since 0.1.0
@@ -728,6 +767,14 @@ mapM_ f m = go (toStream m)
728767-- | Reduce a stream of monadic actions to a stream of the output of those
729768-- actions.
730769--
770+-- @
771+-- runStream $ S.replicateM 10 (return $ threadDelay 1000000 >> print 1)
772+-- & (serially . S.sequence)
773+--
774+-- runStream $ S.replicateM 10 (return $ threadDelay 1000000 >> print 1)
775+-- & (asyncly . S.sequence)
776+-- @
777+--
731778-- /Concurrent (do not use with 'parallely' on infinite streams)/
732779--
733780-- @since 0.1.0
src/Streamly/Streams.hs+91−9
@@ -41,6 +41,10 @@ module Streamly.Streams
4141
4242 -- * Transformation
4343 , mkAsync
44+ , (|$)
45+ , (|&)
46+ , (|>)
47+ , (<|)
4448
4549 -- * Merging Streams
4650 , serial
@@ -50,7 +54,6 @@ module Streamly.Streams
5054 , wAsync
5155 , parallel
5256 , (<=>) --deprecated
53− , (<|) --deprecated
5457
5558 -- * IO Streams
5659 , Serial
@@ -154,10 +157,18 @@ class IsStream t where
154157 -- ["hello","world"]
155158 -- @
156159 --
160+ -- @
161+ -- let delay = threadDelay 1000000 >> print 1
162+ -- runStream $ serially $ delay |: delay |: delay |: nil
163+ -- runStream $ parallely $ delay |: delay |: delay |: nil
164+ -- @
165+ --
157166 -- /Concurrent (do not use 'parallely' to construct infinite streams)/
158167 --
159168 -- @since 0.2.0
160169 (|:) :: MonadAsync m => m a -> t m a -> t m a
170+ -- We can define (|:) just as 'consM' but it is defined explicitly for each
171+ -- type because we want to use SPECIALIZE pragma on the definition.
161172
162173-- | Same as 'IsStream'.
163174--
@@ -307,6 +318,85 @@ mkAsync m = do
307318 sv <- S.newStreamVar1 AsyncVar (toStream m)
308319 return $ fromSVar sv
309320
321+{-# INLINE applyWith #-}
322+applyWith :: (IsStream t, MonadAsync m)
323+ => SVarStyle -> (t m a -> t m b) -> t m a -> t m b
324+applyWith style f x = fromStream $
325+ S.applyWith style (toStream . f . fromStream) (toStream x)
326+
327+{-# INLINE runWith #-}
328+runWith :: (IsStream t, MonadAsync m)
329+ => SVarStyle -> (t m a -> m b) -> t m a -> m b
330+runWith style f x = S.runWith style (f . fromStream) (toStream x)
331+
332+-- | Parallel function application operator for streams; just like the regular
333+-- function application operator '$' except that it is concurrent. The
334+-- following code prints a value every second even though each stage adds a 1
335+-- second delay.
336+--
337+--
338+-- @
339+-- runStream $
340+-- S.mapM (\\x -> threadDelay 1000000 >> print x)
341+-- |$ S.repeatM (threadDelay 1000000 >> return 1)
342+-- @
343+--
344+-- /Concurrent/
345+--
346+-- @since 0.3.0
347+{-# INLINE (|$) #-}
348+(|$) :: (IsStream t, MonadAsync m) => (t m a -> t m b) -> t m a -> t m b
349+f |$ x = applyWith ParallelVar f x
350+
351+-- | Parallel reverse function application operator for streams; just like the
352+-- regular reverse function application operator '&' except that it is
353+-- concurrent.
354+--
355+-- @
356+-- runStream $
357+-- S.repeatM (threadDelay 1000000 >> return 1)
358+-- |& S.mapM (\\x -> threadDelay 1000000 >> print x)
359+-- @
360+--
361+-- /Concurrent/
362+--
363+-- @since 0.3.0
364+{-# INLINE (|&) #-}
365+(|&) :: (IsStream t, MonadAsync m) => t m a -> (t m a -> t m b) -> t m b
366+x |& f = f |$ x
367+
368+-- | Parallel function application operator; applies a @run@ or @fold@ function
369+-- to a stream such that the fold consumer and the stream producer run in
370+-- parallel. A @run@ or @fold@ function reduces the stream to a value in the
371+-- underlying monad.
372+--
373+-- @
374+-- S.foldlM' (\\_ a -> threadDelay 1000000 >> print a) ()
375+-- <| S.repeatM (threadDelay 1000000 >> return 1)
376+-- @
377+--
378+-- /Concurrent/
379+--
380+-- @since 0.3.0
381+{-# INLINE (<|) #-}
382+(<|) :: (IsStream t, MonadAsync m) => (t m a -> m b) -> t m a -> m b
383+f <| x = runWith ParallelVar f x
384+
385+-- | Parallel reverse function application operator for applying a run or fold
386+-- functions to a stream. Just like '<|' except that the operands are reversed.
387+--
388+-- @
389+-- S.repeatM (threadDelay 1000000 >> return 1)
390+-- |> S.foldlM' (\\_ a -> threadDelay 1000000 >> print a) ()
391+-- @
392+--
393+-- /Concurrent/
394+--
395+-- @since 0.3.0
396+{-# INLINE (|>) #-}
397+(|>) :: (IsStream t, MonadAsync m) => t m a -> (t m a -> m b) -> m b
398+x |> f = f <| x
399+
310400------------------------------------------------------------------------------
311401-- CPP macros for common instances
312402------------------------------------------------------------------------------
@@ -776,14 +866,6 @@ async m1 m2 = fromStream $ S.async (toStream m1) (toStream m2)
776866instance MonadAsync m => Semigroup (AsyncT m a) where
777867 (<>) = async
778868
779−-- | Same as 'async'.
780−--
781−-- @since 0.1.0
782−{-# DEPRECATED (<|) "Please use 'async' instead." #-}
783−{-# INLINE (<|) #-}
784−(<|) :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a
785−(<|) = async
786−
787869------------------------------------------------------------------------------
788870-- Monoid
789871------------------------------------------------------------------------------
src/Streamly/Tutorial.hs+1
@@ -386,6 +386,7 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
386386-- where required.
387387--
388388-- TBD add more details and examples about concurrent transformation.
389+-- TBD add details about concurrent function application.
389390
390391-- $semigroup
391392--
test/Prop.hs+77−4
@@ -2,7 +2,8 @@
22
33module Main (main) where
44
5−import Control.Exception (BlockedIndefinitelyOnMVar(..), catch)
5+import Control.Exception (BlockedIndefinitelyOnMVar(..), catches,
6+ BlockedIndefinitelyOnSTM(..), Handler(..))
67import Control.Monad (when)
78import Control.Applicative (ZipList(..))
89import Control.Concurrent (MVar, takeMVar, putMVar, newEmptyMVar)
@@ -68,10 +69,19 @@ transformFromList constr eq listOp op a =
6869 let list = listOp a
6970 equals eq stream list
7071
71−dbgMVar :: String -> IO a -> IO a
72+mvarExcHandler :: String -> BlockedIndefinitelyOnMVar -> IO ()
73+mvarExcHandler label BlockedIndefinitelyOnMVar = do
74+ error $ label ++ " " ++ "BlockedIndefinitelyOnMVar\n"
75+
76+stmExcHandler :: String -> BlockedIndefinitelyOnSTM -> IO ()
77+stmExcHandler label BlockedIndefinitelyOnSTM = do
78+ error $ label ++ " " ++ "BlockedIndefinitelyOnSTM\n"
79+
80+dbgMVar :: String -> IO () -> IO ()
7281dbgMVar label action =
73− action `catch` \BlockedIndefinitelyOnMVar ->
74− error $ label ++ " " ++ "BlockedIndefinitelyOnMVar"
82+ action `catches` [ Handler (mvarExcHandler label)
83+ , Handler (stmExcHandler label)
84+ ]
7585
7686-- | first n actions takeMVar and the last action performs putMVar n times
7787mvarSequenceOp :: MVar () -> Word8 -> Word8 -> IO Word8
@@ -131,6 +141,7 @@ concurrentUnfoldrM
131141 -> Property
132142concurrentUnfoldrM eq op n =
133143 monadicIO $ do
144+ -- XXX we should test empty list case as well
134145 let list = [0..n]
135146 stream <- run $ do
136147 -- putStrLn $ "concurrentUnfoldrM: " ++ show n
@@ -154,6 +165,61 @@ concurrentUnfoldrM eq op n =
154165 return x
155166 equals eq stream list
156167
168+concurrentApplication :: Word8 -> Property
169+concurrentApplication n =
170+ monadicIO $ do
171+ -- XXX we should test empty list case as well
172+ let list = [0..n]
173+ stream <- run $ do
174+ -- putStrLn $ "concurrentApplication: " ++ show n
175+ mv <- newEmptyMVar :: IO (MVar ())
176+ -- since unfoldr happens in parallel with the stream processing we
177+ -- can do two takeMVar in one iteration. If it is not parallel then
178+ -- this will not work and the test will fail.
179+ A.toList $ do
180+ sourceUnfoldrM mv n |&
181+ (A.mapM $ \x -> do
182+ let msg = show x ++ "/" ++ show n
183+ if even x
184+ then do
185+ dbgMVar ("first take concurrentApp " ++ msg)
186+ (takeMVar mv)
187+ if n > x
188+ then dbgMVar ("second take concurrentApp " ++ msg)
189+ (takeMVar mv)
190+ else return ()
191+ else return ()
192+ return x)
193+ equals (==) stream list
194+
195+sourceUnfoldrM1 :: IsStream t => Word8 -> t IO Word8
196+sourceUnfoldrM1 n = A.unfoldrM step 0
197+ where
198+ -- argument must be integer to avoid overflow of word8 at 255
199+ step :: Int -> IO (Maybe (Word8, Int))
200+ step cnt = do
201+ if cnt > fromIntegral n
202+ then return Nothing
203+ else return (Just (fromIntegral cnt, cnt + 1))
204+
205+concurrentFoldlApplication :: Word8 -> Property
206+concurrentFoldlApplication n =
207+ monadicIO $ do
208+ -- XXX we should test empty list case as well
209+ let list = [0..n]
210+ stream <- run $ do
211+ sourceUnfoldrM1 n |> A.foldlM' (\xs x -> return (x : xs)) []
212+ equals (==) (reverse stream) list
213+
214+concurrentFoldrApplication :: Word8 -> Property
215+concurrentFoldrApplication n =
216+ monadicIO $ do
217+ -- XXX we should test empty list case as well
218+ let list = [0..n]
219+ stream <- run $ do
220+ sourceUnfoldrM1 n |> A.foldrM (\x xs -> return (x : xs)) []
221+ equals (==) stream list
222+
157223foldFromList
158224 :: ([Int] -> t IO Int)
159225 -> (t IO Int -> SerialT IO Int)
@@ -564,6 +630,13 @@ main = hspec $ do
564630 concurrentOps folded "wAsyncly folded" wAsyncly sortEq
565631 concurrentOps folded "parallely folded" parallely sortEq
566632
633+ prop "concurrent application" $ withMaxSuccess maxTestCount $
634+ concurrentApplication
635+ prop "concurrent foldr application" $ withMaxSuccess maxTestCount $
636+ concurrentFoldrApplication
637+ prop "concurrent foldl application" $ withMaxSuccess maxTestCount $
638+ concurrentFoldlApplication
639+
567640 describe "Stream elimination operations" $ do
568641 eliminationOps A.fromFoldable "serially" serially
569642 eliminationOps A.fromFoldable "aheadly" aheadly