on master help sign in
Update docs/examples for folds in prelude
commit43623cb06dd40879ce8305fc3866950b9314467f authorHarendra Kumar <harendra@composewell.com> dateThu 1 Apr 2021, 18:55 (6 years ago) parent0801c36
5 files changed+97−128
src/Streamly/Internal/Data/Stream/IsStream/Common.hs+7−6 src/Streamly/Internal/Data/Stream/IsStream/Eliminate.hs+58−58 src/Streamly/Internal/Data/Stream/IsStream/Reduce.hs+2−2 src/Streamly/Internal/Data/Stream/IsStream/Transform.hs+28−13 src/Streamly/Prelude.hs+2−49
src/Streamly/Internal/Data/Stream/IsStream/Common.hs+7−6
@@ -234,17 +234,18 @@ relTimesWith = fmap snd . timesWith
234234-- >>> Stream.fold Fold.sum (Stream.enumerateFromTo 1 100)
235235-- 5050
236236--
237−-- @fold f = parse (Parser.fromFold f)@
238−--
239−-- Folds can never fail, therefore, they always produce a default value even
240−-- when no input is provided. It means we can always fold an empty stream and
241−-- get a valid result. For example:
237+-- Folds never fail, therefore, they produce a default value even when no input
238+-- is provided. It means we can always fold an empty stream and get a valid
239+-- result. For example:
242240--
243241-- >>> Stream.fold Fold.sum Stream.nil
244242-- 0
245243--
246244-- However, 'foldMany' on an empty stream results in an empty stream.
247−-- Therefore, @fold f@ is not the same as @head . foldMany f@.
245+-- Therefore, @Stream.fold f@ is not the same as @Stream.head . Stream.foldMany
246+-- f@.
247+--
248+-- @fold f = Stream.parse (Parser.fromFold f)@
248249--
249250-- @since 0.7.0
250251{-# INLINE fold #-}
src/Streamly/Internal/Data/Stream/IsStream/Eliminate.hs+58−58
@@ -17,6 +17,9 @@
1717
1818module Streamly.Internal.Data.Stream.IsStream.Eliminate
1919 (
20+ -- * Running Examples
21+ -- $setup
22+
2023 -- * Running a 'Fold'
2124 -- See "Streamly.Internal.Data.Fold".
2225 fold
@@ -183,8 +186,9 @@ import Prelude hiding
183186
184187-- $setup
185188-- >>> :m
189+-- >>> import Streamly.Prelude (SerialT)
186190-- >>> import qualified Streamly.Prelude as Stream
187−-- >>> import qualified Streamly.Internal.Data.Stream.IsStream as Stream (parse)
191+-- >>> import qualified Streamly.Internal.Data.Stream.IsStream as Stream
188192-- >>> import qualified Streamly.Internal.Data.Parser as Parser
189193-- >>> import qualified Streamly.Data.Fold as Fold
190194
@@ -362,7 +366,7 @@ parseK = parse
362366-- Note:
363367--
364368-- @
365−-- fold f = parse (Parser.fromFold f)
369+-- fold f = Stream.parse (Parser.fromFold f)
366370-- @
367371--
368372-- @parse p@ is not the same as @head . parseMany p@ on an empty stream.
@@ -393,7 +397,7 @@ parse_ = parseD_ . PRK.fromParserK
393397
394398-- XXX this can utilize parallel mapping if we implement it as drain . mapM
395399-- |
396−-- > mapM_ = drain . mapM
400+-- > mapM_ = Stream.drain . Stream.mapM
397401--
398402-- Apply a monadic action to each element of the stream and discard the output
399403-- of the action. This is not really a pure transformation operation but a
@@ -406,11 +410,11 @@ mapM_ f m = S.mapM_ f $ toStreamS m
406410
407411-- |
408412-- > drain = mapM_ (\_ -> return ())
409−-- > drain = fold Fold.drain
413+-- > drain = Stream.fold Fold.drain
410414--
411415-- Run a stream, discarding the results. By default it interprets the stream
412416-- as 'SerialT', to run other types of streams use the type adapting
413−-- combinators for example @drain . 'asyncly'@.
417+-- combinators for example @Stream.drain . 'asyncly'@.
414418--
415419-- @since 0.7.0
416420{-# INLINE drain #-}
@@ -418,8 +422,8 @@ drain :: Monad m => SerialT m a -> m ()
418422drain = P.drain
419423
420424-- |
421−-- > drainN n = drain . take n
422−-- > drainN n = fold (Fold.take n Fold.drain)
425+-- > drainN n = Stream.drain . Stream.take n
426+-- > drainN n = Stream.fold (Fold.take n Fold.drain)
423427--
424428-- Run maximum up to @n@ iterations of a stream.
425429--
@@ -440,7 +444,7 @@ runN :: Monad m => Int -> SerialT m a -> m ()
440444runN = drainN
441445
442446-- |
443−-- > drainWhile p = drain . takeWhile p
447+-- > drainWhile p = Stream.drain . Stream.takeWhile p
444448--
445449-- Run a stream as long as the predicate holds true.
446450--
@@ -472,7 +476,7 @@ runStream = drain
472476
473477-- | Determine whether the stream is empty.
474478--
475−-- > null = fold Fold.null
479+-- > null = Stream.fold Fold.null
476480--
477481-- @since 0.1.1
478482{-# INLINE null #-}
@@ -482,7 +486,7 @@ null = S.null . toStreamS
482486-- | Extract the first element of the stream, if any.
483487--
484488-- > head = (!! 0)
485−-- > head = fold Fold.head
489+-- > head = Stream.fold Fold.head
486490--
487491-- @since 0.1.0
488492{-# INLINE head #-}
@@ -499,7 +503,7 @@ headElse :: Monad m => a -> SerialT m a -> m a
499503headElse x = D.headElse x . toStreamD
500504
501505-- |
502−-- > tail = fmap (fmap snd) . uncons
506+-- > tail = fmap (fmap snd) . Stream.uncons
503507--
504508-- Extract all but the first element of the stream, if any.
505509--
@@ -517,8 +521,8 @@ init m = K.init (K.adapt m)
517521
518522-- | Extract the last element of the stream, if any.
519523--
520−-- > last xs = xs !! (length xs - 1)
521−-- > last = fold Fold.last
524+-- > last xs = xs !! (Stream.length xs - 1)
525+-- > last = Stream.fold Fold.last
522526--
523527-- @since 0.1.1
524528{-# INLINE last #-}
@@ -527,7 +531,7 @@ last m = S.last $ toStreamS m
527531
528532-- | Determine whether an element is present in the stream.
529533--
530−-- > elem = fold Fold.elem
534+-- > elem = Stream.fold Fold.elem
531535--
532536-- @since 0.1.0
533537{-# INLINE elem #-}
@@ -536,7 +540,7 @@ elem e m = S.elem e (toStreamS m)
536540
537541-- | Determine whether an element is not present in the stream.
538542--
539−-- > notElem = fold Fold.length
543+-- > notElem = Stream.fold Fold.length
540544--
541545-- @since 0.1.0
542546{-# INLINE notElem #-}
@@ -552,7 +556,7 @@ length = foldl' (\n _ -> n + 1) 0
552556
553557-- | Determine whether all elements of a stream satisfy a predicate.
554558--
555−-- > all = fold Fold.all
559+-- > all = Stream.fold Fold.all
556560--
557561-- @since 0.1.0
558562{-# INLINE all #-}
@@ -561,7 +565,7 @@ all p m = S.all p (toStreamS m)
561565
562566-- | Determine whether any of the elements of a stream satisfy a predicate.
563567--
564−-- > any = fold Fold.any
568+-- > any = Stream.fold Fold.any
565569--
566570-- @since 0.1.0
567571{-# INLINE any #-}
@@ -570,7 +574,7 @@ any p m = S.any p (toStreamS m)
570574
571575-- | Determines if all elements of a boolean stream are True.
572576--
573−-- > and = fold Fold.and
577+-- > and = Stream.fold Fold.and
574578--
575579-- @since 0.5.0
576580{-# INLINE and #-}
@@ -579,7 +583,7 @@ and = all (==True)
579583
580584-- | Determines whether at least one element of a boolean stream is True.
581585--
582−-- > or = fold Fold.or
586+-- > or = Stream.fold Fold.or
583587--
584588-- @since 0.5.0
585589{-# INLINE or #-}
@@ -590,7 +594,7 @@ or = any (==True)
590594-- the stream is empty. Note that this is not numerically stable for floating
591595-- point numbers.
592596--
593−-- > sum = fold Fold.sum
597+-- > sum = Stream.fold Fold.sum
594598--
595599-- @since 0.1.0
596600{-# INLINE sum #-}
@@ -600,7 +604,7 @@ sum = foldl' (+) 0
600604-- | Determine the product of all elements of a stream of numbers. Returns @1@
601605-- when the stream is empty.
602606--
603−-- > product = fold Fold.product
607+-- > product = Stream.fold Fold.product
604608--
605609-- @since 0.1.1
606610{-# INLINE product #-}
@@ -609,7 +613,7 @@ product = foldl' (*) 1
609613
610614-- | Fold a stream of monoid elements by appending them.
611615--
612−-- > mconcat = fold Fold.mconcat
616+-- > mconcat = Stream.fold Fold.mconcat
613617--
614618-- /Pre-release/
615619{-# INLINE mconcat #-}
@@ -619,7 +623,7 @@ mconcat = foldr mappend mempty
619623-- |
620624-- @
621625-- minimum = 'minimumBy' compare
622− -- minimum = fold Fold.minimum
626+ -- minimum = Stream.fold Fold.minimum
623627-- @
624628--
625629-- Determine the minimum element in a stream.
@@ -632,7 +636,7 @@ minimum m = S.minimum (toStreamS m)
632636-- | Determine the minimum element in a stream using the supplied comparison
633637-- function.
634638--
635−-- > minimumBy = fold Fold.minimumBy
639+-- > minimumBy = Stream.fold Fold.minimumBy
636640--
637641-- @since 0.6.0
638642{-# INLINE minimumBy #-}
@@ -642,7 +646,7 @@ minimumBy cmp m = S.minimumBy cmp (toStreamS m)
642646-- |
643647-- @
644648-- maximum = 'maximumBy' compare
645−-- maximum = fold Fold.maximum
649+-- maximum = Stream.fold Fold.maximum
646650-- @
647651--
648652-- Determine the maximum element in a stream.
@@ -655,7 +659,7 @@ maximum = P.maximum
655659-- | Determine the maximum element in a stream using the supplied comparison
656660-- function.
657661--
658−-- > maximumBy = fold Fold.maximumBy
662+-- > maximumBy = Stream.fold Fold.maximumBy
659663--
660664-- @since 0.6.0
661665{-# INLINE maximumBy #-}
@@ -684,8 +688,8 @@ m !! i = toStreamS m S.!! i
684688-- | In a stream of (key-value) pairs @(a, b)@, return the value @b@ of the
685689-- first pair where the key equals the given value @a@.
686690--
687−-- > lookup = snd <$> find ((==) . fst)
688−-- > lookup = fold Fold.lookup
691+-- > lookup = snd <$> Stream.find ((==) . fst)
692+-- > lookup = Stream.fold Fold.lookup
689693--
690694-- @since 0.5.0
691695{-# INLINE lookup #-}
@@ -695,7 +699,7 @@ lookup a m = S.lookup a (toStreamS m)
695699-- | Like 'findM' but with a non-monadic predicate.
696700--
697701-- > find p = findM (return . p)
698−-- > find = fold Fold.find
702+-- > find = Stream.fold Fold.find
699703--
700704-- @since 0.5.0
701705{-# INLINE find #-}
@@ -704,7 +708,7 @@ find p m = S.find p (toStreamS m)
704708
705709-- | Returns the first element that satisfies the given predicate.
706710--
707−-- > findM = fold Fold.findM
711+-- > findM = Stream.fold Fold.findM
708712--
709713-- @since 0.6.0
710714{-# INLINE findM #-}
@@ -713,7 +717,7 @@ findM p m = S.findM p (toStreamS m)
713717
714718-- | Returns the first index that satisfies the given predicate.
715719--
716−-- > findIndex = fold Fold.findIndex
720+-- > findIndex = Stream.fold Fold.findIndex
717721--
718722-- @since 0.5.0
719723{-# INLINE findIndex #-}
@@ -722,7 +726,7 @@ findIndex p = head . findIndices p
722726
723727-- | Returns the first index where a given value is found in the stream.
724728--
725−-- > elemIndex a = findIndex (== a)
729+-- > elemIndex a = Stream.findIndex (== a)
726730--
727731-- @since 0.5.0
728732{-# INLINE elemIndex #-}
@@ -735,7 +739,7 @@ elemIndex a = findIndex (== a)
735739
736740-- |
737741-- @
738−-- toList = S.foldr (:) []
742+-- toList = Stream.foldr (:) []
739743-- @
740744--
741745-- Convert a stream into a list in the underlying monad. The list can be
@@ -752,7 +756,7 @@ toList = P.toList
752756
753757-- |
754758-- @
755−-- toListRev = S.foldl' (flip (:)) []
759+-- toListRev = Stream.foldl' (flip (:)) []
756760-- @
757761--
758762-- Convert a stream into a list in reverse order in the underlying monad.
@@ -787,7 +791,7 @@ toHandle h = go
787791-- | Convert a stream to a pure stream.
788792--
789793-- @
790−-- toStream = foldr cons nil
794+-- toStream = Stream.foldr Stream.cons Stream.nil
791795-- @
792796--
793797-- /Pre-release/
@@ -799,7 +803,7 @@ toStream = foldr K.cons K.nil
799803-- | Convert a stream to a pure stream in reverse order.
800804--
801805-- @
802−-- toStreamRev = foldl' (flip cons) nil
806+-- toStreamRev = Stream.foldl' (flip Stream.cons) Stream.nil
803807-- @
804808--
805809-- /Pre-release/
@@ -825,10 +829,18 @@ toStreamRev = foldl' (flip K.cons) K.nil
825829-- The @.@ at the end of the operator is a mnemonic for termination of the
826830-- stream.
827831--
828−-- @
829−-- S.foldlM' (\\_ a -> threadDelay 1000000 >> print a) ()
830−-- |$. S.repeatM (threadDelay 1000000 >> return 1)
831−-- @
832+-- In the example below, each stage introduces a delay of 1 sec but output is
833+-- printed every second because both stages are concurrent.
834+--
835+-- >>> import Control.Concurrent (threadDelay)
836+-- >>> import Streamly.Prelude ((|$.))
837+-- >>> :{
838+-- Stream.foldlM' (\_ a -> threadDelay 1000000 >> print a) (return ())
839+-- |$. Stream.replicateM 3 (threadDelay 1000000 >> return 1)
840+-- :}
841+-- 1
842+-- 1
843+-- 1
832844--
833845-- /Concurrent/
834846--
@@ -850,13 +862,9 @@ infixr 0 |$.
850862foldAsync :: (IsStream t, MonadAsync m) => (t m a -> m b) -> (t m a -> m b)
851863foldAsync = (|$.)
852864
853−-- | Parallel reverse function application operator for applying a run or fold
854−-- functions to a stream. Just like '|$.' except that the operands are reversed.
865+-- | Same as '|$.' but with arguments reversed.
855866--
856−-- @
857−-- S.repeatM (threadDelay 1000000 >> return 1)
858−-- |&. S.foldlM' (\\_ a -> threadDelay 1000000 >> print a) ()
859−-- @
867+-- > (|&.) = flip (|$.)
860868--
861869-- /Concurrent/
862870--
@@ -876,10 +884,8 @@ infixl 1 |&.
876884-- | Returns 'True' if the first stream is the same as or a prefix of the
877885-- second. A stream is a prefix of itself.
878886--
879−-- @
880−-- > S.isPrefixOf (S.fromList "hello") (S.fromList "hello" :: SerialT IO Char)
887+-- >>> Stream.isPrefixOf (Stream.fromList "hello") (Stream.fromList "hello" :: SerialT IO Char)
881888-- True
882−-- @
883889--
884890-- @since 0.6.0
885891{-# INLINE isPrefixOf #-}
@@ -889,10 +895,8 @@ isPrefixOf m1 m2 = D.isPrefixOf (toStreamD m1) (toStreamD m2)
889895-- | Returns 'True' if the first stream is an infix of the second. A stream is
890896-- considered an infix of itself.
891897--
892−-- @
893−-- > S.isInfixOf (S.fromList "hello") (S.fromList "hello" :: SerialT IO Char)
898+-- > Stream.isInfixOf (Stream.fromList "hello") (Stream.fromList "hello" :: SerialT IO Char)
894899-- True
895−-- @
896900--
897901-- Space: @O(n)@ worst case where @n@ is the length of the infix.
898902--
@@ -928,10 +932,8 @@ isInfixOf infx stream = do
928932-- | Returns 'True' if the first stream is a suffix of the second. A stream is
929933-- considered a suffix of itself.
930934--
931−-- @
932−-- > S.isSuffixOf (S.fromList "hello") (S.fromList "hello" :: SerialT IO Char)
935+-- >>> Stream.isSuffixOf (Stream.fromList "hello") (Stream.fromList "hello" :: SerialT IO Char)
933936-- True
934−-- @
935937--
936938-- Space: @O(n)@, buffers entire input stream and the suffix.
937939--
@@ -947,10 +949,8 @@ isSuffixOf suffix stream = reverse suffix `isPrefixOf` reverse stream
947949-- the second stream. The elements do not have to occur consecutively. A stream
948950-- is a subsequence of itself.
949951--
950−-- @
951−-- > S.isSubsequenceOf (S.fromList "hlo") (S.fromList "hello" :: SerialT IO Char)
952+-- >>> Stream.isSubsequenceOf (Stream.fromList "hlo") (Stream.fromList "hello" :: SerialT IO Char)
952953-- True
953−-- @
954954--
955955-- @since 0.6.0
956956{-# INLINE isSubsequenceOf #-}
src/Streamly/Internal/Data/Stream/IsStream/Reduce.hs+2−2
@@ -281,8 +281,8 @@ foldManyPost f m = D.fromStreamD $ D.foldManyPost f (D.toStreamD m)
281281-- >>> Stream.toList $ Stream.foldMany f $ Stream.fromList []
282282-- []
283283--
284−-- Note @foldMany (take 0)@ would result in an infinite loop in a non-empty
285−-- stream.
284+-- Note @Stream.foldMany (Fold.take 0)@ would result in an infinite loop in a
285+-- non-empty stream.
286286--
287287-- @since 0.8.0
288288--
src/Streamly/Internal/Data/Stream/IsStream/Transform.hs+28−13
@@ -566,6 +566,9 @@ trace_ eff = Serial.mapM (\x -> eff >> return x)
566566
567567-- | Scan a stream using the given monadic fold.
568568--
569+-- >>> Stream.toList $ Stream.takeWhile (< 10) $ Stream.scan Fold.sum (Stream.fromList [1..10])
570+-- [0,1,3,6]
571+--
569572-- @since 0.7.0
570573{-# INLINE scan #-}
571574scan :: (IsStream t, Monad m) => Fold m a b -> t m a -> t m b
@@ -573,6 +576,19 @@ scan = P.scanOnce
573576
574577-- | Postscan a stream using the given monadic fold.
575578--
579+-- The following example extracts the input stream up to a point where the
580+-- running average of elements is no more than 10:
581+--
582+-- >>> import Data.Maybe (fromJust)
583+-- >>> let avg = Fold.teeWith (/) Fold.sum (fmap fromIntegral Fold.length)
584+-- >>> :{
585+-- Stream.toList
586+-- $ Stream.map (fromJust . fst)
587+-- $ Stream.takeWhile (\(_,x) -> x <= 10)
588+-- $ Stream.postscan (Fold.tee Fold.last avg) (Stream.enumerateFromTo 1.0 100.0)
589+-- :}
590+-- [1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0,13.0,14.0,15.0,16.0,17.0,18.0,19.0]
591+--
576592-- @since 0.7.0
577593{-# INLINE postscan #-}
578594postscan :: (IsStream t, Monad m) => Fold m a b -> t m a -> t m b
@@ -1473,11 +1489,16 @@ rights = fmap (fromRight undefined) . filter isRight
14731489-- 1 second delay.
14741490--
14751491--
1476−-- @
1477−-- drain $
1478−-- S.mapM (\\x -> threadDelay 1000000 >> print x)
1479−-- |$ S.repeatM (threadDelay 1000000 >> return 1)
1480−-- @
1492+-- >>> import Control.Concurrent (threadDelay)
1493+-- >>> import Streamly.Prelude ((|$))
1494+-- >>> :{
1495+-- Stream.drain $
1496+-- Stream.mapM (\x -> threadDelay 1000000 >> print x)
1497+-- |$ Stream.replicateM 3 (threadDelay 1000000 >> return 1)
1498+-- :}
1499+-- 1
1500+-- 1
1501+-- 1
14811502--
14821503-- /Concurrent/
14831504--
@@ -1500,15 +1521,9 @@ applyAsync :: (IsStream t, MonadAsync m)
15001521 => (t m a -> t m b) -> (t m a -> t m b)
15011522applyAsync = (|$)
15021523
1503−-- | Parallel reverse function application operator for streams; just like the
1504−-- regular reverse function application operator '&' except that it is
1505−-- concurrent.
1524+-- | Same as '|$' but with arguments reversed.
15061525--
1507−-- @
1508−-- drain $
1509−-- S.repeatM (threadDelay 1000000 >> return 1)
1510−-- |& S.mapM (\\x -> threadDelay 1000000 >> print x)
1511−-- @
1526+-- (|&) = flip (|$)
15121527--
15131528-- /Concurrent/
15141529--
src/Streamly/Prelude.hs+2−49
@@ -1135,48 +1135,8 @@ import Streamly.Internal.Data.Stream.IsStream
11351135
11361136-- $runningfolds
11371137--
1138−-- "Streamly.Data.Fold" module defines composable left folds which can be combined
1139−-- together in many interesting ways. Those folds can be run using 'fold'.
1140−-- The following two ways of folding are equivalent in functionality and
1141−-- performance,
1142−--
1143−-- >>> Stream.fold Fold.sum (Stream.enumerateFromTo 1 100)
1144−-- 5050
1145−-- >>> Stream.sum (Stream.enumerateFromTo 1 100)
1146−-- 5050
1147−--
1148−-- However, left folds cannot terminate early even if it does not need to
1149−-- consume more input to determine the result. Therefore, the performance is
1150−-- equivalent only for full folds like 'sum' and 'length'. For partial folds
1151−-- like 'head' or 'any' the the folds defined in this module may be much more
1152−-- efficient because they are implemented as right folds that terminate as soon
1153−-- as we get the result. Note that when a full fold is composed with a partial
1154−-- fold in parallel the performance is not impacted as we anyway have to
1155−-- consume the whole stream due to the full fold.
1156−--
1157−-- >>> Stream.head (1 `Stream.cons` undefined)
1158−-- Just 1
1159−-- >>> Stream.fold Fold.head (1 `Stream.cons` undefined)
1160−-- Just 1
1161−--
1162−-- However, we can wrap the fold in a scan to convert it into a lazy stream of
1163−-- fold steps. We can then terminate the stream whenever we want. For example,
1164−--
1165−-- >>> Stream.toList $ Stream.take 1 $ Stream.scan Fold.head (1 `Stream.cons` undefined)
1166−-- [Nothing]
1167−--
1168−-- The following example extracts the input stream up to a point where the
1169−-- running average of elements is no more than 10:
1170−--
1171−-- >>> import Data.Maybe (fromJust)
1172−-- >>> let avg = Fold.teeWith (/) Fold.sum (fmap fromIntegral Fold.length)
1173−-- >>> :{
1174−-- Stream.toList
1175−-- $ Stream.map (fromJust . fst)
1176−-- $ Stream.takeWhile (\(_,x) -> x <= 10)
1177−-- $ Stream.postscan (Fold.tee Fold.last avg) (Stream.enumerateFromTo 1.0 100.0)
1178−-- :}
1179−-- [1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0,13.0,14.0,15.0,16.0,17.0,18.0,19.0]
1138+-- See "Streamly.Data.Fold" for an overview of composable folds. All folds in
1139+-- this module can be expressed in terms of composable folds using 'fold'.
11801140--
11811141-- $application
11821142--
@@ -1186,13 +1146,6 @@ import Streamly.Internal.Data.Stream.IsStream
11861146-- operators applying stream processing functions such that each stage of the
11871147-- stream can run in parallel. The operators start with a @|@; we can read '|$'
11881148-- as "@parallel dollar@" to remember that @|@ comes before '$'.
1189−--
1190−-- Imports for the code snippets below:
1191−--
1192−-- @
1193−-- import qualified Streamly.Prelude as S
1194−-- import Control.Concurrent
1195−-- @
11961149
11971150-- $concurrency
11981151--