on master help sign in
fix foldxM strictness

The main change is a single line change in StreamK.hs in foldxM routine. Major changes in this commit are due to: 1) Added strictness tests for all foldl and scanl rotuines 2) refactoring to enable independent benchmarking for StreamK, to measure the impact of the change.

commit55f9cfa3710d97a08a6b6a4fd85a26fa8783c351 authorHarendra Kumar <harendra.kumar@gmail.com> dateFri 13 Jul 2018, 17:47 (8 years ago) parent6a4a55b
11 files changed+345−205
Changelog.md+6 benchmark/BaseStreams.hs+2−2 benchmark/StreamDOps.hs+3−3 benchmark/StreamKOps.hs+14−13 src/Streamly.hs+81 src/Streamly/Streams/Prelude.hs+1−87 src/Streamly/Streams/StreamK.hs+46−2 src/Streamly/Streams/Zip.hs+5−45 stack-7.10.yaml+2−2 streamly.cabal+53−26 test/Main.hs+132−25
Changelog.md+6
@@ -1,3 +1,9 @@
1+## Unreleased
2+
3+### Bug Fixes
4+
5+* foldxM was not fully strict, fixed.
6+
17## 0.4.0
28
39### Breaking changes
benchmark/BaseStreams.hs+2−2
@@ -37,7 +37,7 @@ main = do
3737 , benchIO "unfoldrM" D.toNull D.sourceUnfoldrM
3838 , benchIO "fromEnum" D.toNull D.sourceFromEnum
3939
40− , benchIO "fromFoldable" D.toNull D.sourceFromFoldable
40+ , benchIO "fromList" D.toNull D.sourceFromList
4141 -- , benchIO "fromFoldableM" D.sourceFromFoldableM
4242 ]
4343 , bgroup "elimination"
@@ -64,7 +64,7 @@ main = do
6464 -- , benchIO "fromEnum" K.toNull K.sourceFromEnum
6565
6666 , benchIO "fromFoldable" K.toNull K.sourceFromFoldable
67− , benchIO "fromFoldableM" K.toNull K.sourceFromFoldableM
67+ -- , benchIO "fromFoldableM" K.toNull K.sourceFromFoldableM
6868
6969 -- appends
7070 , benchIO "foldMapWith" K.toNull K.sourceFoldMapWith
benchmark/StreamDOps.hs+3−3
@@ -96,9 +96,9 @@ sourceUnfoldrM n = S.unfoldrM step n
9696sourceFromEnum :: Monad m => Int -> Stream m Int
9797sourceFromEnum n = S.enumFromStepN n 1 value
9898
99−{-# INLINE sourceFromFoldable #-}
100−sourceFromFoldable :: Monad m => Int -> Stream m Int
101−sourceFromFoldable n = S.fromList [n..n+value]
99+{-# INLINE sourceFromList #-}
100+sourceFromList :: Monad m => Int -> Stream m Int
101+sourceFromList n = S.fromList [n..n+value]
102102
103103{-# INLINE source #-}
104104source :: Monad m => Int -> Stream m Int
benchmark/StreamKOps.hs+14−13
@@ -13,10 +13,9 @@ import Prelude
1313 (Monad, Int, (+), ($), (.), return, fmap, even, (>), (<=),
1414 subtract, undefined, Maybe(..), not)
1515
16−import qualified Streamly.Streams.StreamK as S hiding (runStream)
17−-- import qualified Streamly.Streams.Serial as S
18−import qualified Streamly as S
19−import qualified Streamly.Prelude as P
16+import qualified Streamly.Streams.StreamK as S
17+import qualified Streamly.Streams.Prelude as S
18+import qualified Streamly.SVar as S
2019
2120value, maxValue :: Int
2221value = 1000000
@@ -68,7 +67,7 @@ mapM :: S.MonadAsync m => Stream m Int -> m ()
6867-- Stream generation and elimination
6968-------------------------------------------------------------------------------
7069
71−type Stream m a = S.SerialT m a
70+type Stream m a = S.Stream m a
7271
7372{-# INLINE sourceUnfoldr #-}
7473sourceUnfoldr :: Int -> Stream m Int
@@ -98,17 +97,19 @@ sourceFromEnum n = S.enumFromStepN n 1 value
9897sourceFromFoldable :: Int -> Stream m Int
9998sourceFromFoldable n = S.fromFoldable [n..n+value]
10099
100+{-
101101{-# INLINE sourceFromFoldableM #-}
102102sourceFromFoldableM :: S.MonadAsync m => Int -> Stream m Int
103−sourceFromFoldableM n = P.fromFoldableM (Prelude.fmap return [n..n+value])
103+sourceFromFoldableM n = S.fromFoldableM (Prelude.fmap return [n..n+value])
104+-}
104105
105106{-# INLINE sourceFoldMapWith #-}
106−sourceFoldMapWith :: Monad m => Int -> Stream m Int
107−sourceFoldMapWith n = S.foldMapWith (S.<>) return [n..n+value]
107+sourceFoldMapWith :: Int -> Stream m Int
108+sourceFoldMapWith n = S.foldMapWith (S.serial) S.yield [n..n+value]
108109
109110{-# INLINE sourceFoldMapWithM #-}
110111sourceFoldMapWithM :: Monad m => Int -> Stream m Int
111−sourceFoldMapWithM n = S.foldMapWith (S.<>) (S.yieldM . return) [n..n+value]
112+sourceFoldMapWithM n = S.foldMapWith (S.serial) (S.yieldM . return) [n..n+value]
112113
113114{-# INLINE source #-}
114115source :: S.MonadAsync m => Int -> Stream m Int
@@ -160,15 +161,15 @@ filterAllOut = transform . S.filter (> maxValue)
160161filterAllIn = transform . S.filter (<= maxValue)
161162takeOne = transform . S.take 1
162163takeAll = transform . S.take maxValue
163−takeWhileTrue = transform . P.takeWhile (<= maxValue)
164−dropAll = transform . P.drop maxValue
165−dropWhileTrue = transform . P.dropWhile (<= maxValue)
164+takeWhileTrue = transform . S.takeWhile (<= maxValue)
165+dropAll = transform . S.drop maxValue
166+dropWhileTrue = transform . S.dropWhile (<= maxValue)
166167
167168-------------------------------------------------------------------------------
168169-- Zipping and concat
169170-------------------------------------------------------------------------------
170171
171−zip src = transform $ (P.zipWith (,) src src)
172+zip src = transform $ (S.zipWith (,) src src)
172173concat _n = return ()
173174
174175-------------------------------------------------------------------------------
src/Streamly.hs+81
@@ -55,6 +55,14 @@
5555-- import Streamly
5656-- @
5757
58+{-# LANGUAGE CPP #-}
59+
60+#if __GLASGOW_HASKELL__ >= 800
61+{-# OPTIONS_GHC -Wno-orphans #-}
62+#endif
63+
64+#include "Streamly/Streams/inline.h"
65+
5866module Streamly
5967 (
6068 MonadAsync
@@ -168,6 +176,79 @@ import Streamly.Streams.SVar (maxThreads, maxBuffer)
168176import Streamly.SVar (MonadAsync)
169177import Data.Semigroup (Semigroup(..))
170178
179+import qualified Streamly.Streams.StreamD as D
180+import qualified Streamly.Streams.StreamK as K
181+
182+-- XXX This should perhaps be moved to Prelude.
183+
184+------------------------------------------------------------------------------
185+-- Eliminating a stream
186+------------------------------------------------------------------------------
187+
188+-- | Run a streaming composition, discard the results. By default it interprets
189+-- the stream as 'SerialT', to run other types of streams use the type adapting
190+-- combinators for example @runStream . 'asyncly'@.
191+--
192+-- @since 0.2.0
193+{-# INLINE_EARLY runStream #-}
194+runStream :: Monad m => SerialT m a -> m ()
195+runStream m = D.runStream $ D.fromStreamK (toStream m)
196+{-# RULES "runStream fallback to CPS" [1]
197+ forall a. D.runStream (D.fromStreamK a) = K.runStream a #-}
198+
199+-- | Same as 'runStream'
200+--
201+-- @since 0.1.0
202+{-# DEPRECATED runStreaming "Please use runStream instead." #-}
203+runStreaming :: (Monad m, IsStream t) => t m a -> m ()
204+runStreaming = runStream . K.adapt
205+
206+-- | Same as @runStream@.
207+--
208+-- @since 0.1.0
209+{-# DEPRECATED runStreamT "Please use runStream instead." #-}
210+runStreamT :: Monad m => SerialT m a -> m ()
211+runStreamT = runStream
212+
213+-- | Same as @runStream . wSerially@.
214+--
215+-- @since 0.1.0
216+{-# DEPRECATED runInterleavedT "Please use 'runStream . interleaving' instead." #-}
217+runInterleavedT :: Monad m => WSerialT m a -> m ()
218+runInterleavedT = runStream . K.adapt
219+
220+-- | Same as @runStream . parallely@.
221+--
222+-- @since 0.1.0
223+{-# DEPRECATED runParallelT "Please use 'runStream . parallely' instead." #-}
224+runParallelT :: Monad m => ParallelT m a -> m ()
225+runParallelT = runStream . K.adapt
226+
227+-- | Same as @runStream . asyncly@.
228+--
229+-- @since 0.1.0
230+{-# DEPRECATED runAsyncT "Please use 'runStream . asyncly' instead." #-}
231+runAsyncT :: Monad m => AsyncT m a -> m ()
232+runAsyncT = runStream . K.adapt
233+
234+-- | Same as @runStream . zipping@.
235+--
236+-- @since 0.1.0
237+{-# DEPRECATED runZipStream "Please use 'runStream . zipSerially instead." #-}
238+runZipStream :: Monad m => ZipSerialM m a -> m ()
239+runZipStream = runStream . K.adapt
240+
241+-- | Same as @runStream . zippingAsync@.
242+--
243+-- @since 0.1.0
244+{-# DEPRECATED runZipAsync "Please use 'runStream . zipAsyncly instead." #-}
245+runZipAsync :: Monad m => ZipAsyncM m a -> m ()
246+runZipAsync = runStream . K.adapt
247+
248+------------------------------------------------------------------------------
249+-- Documentation
250+------------------------------------------------------------------------------
251+
171252-- $serial
172253--
173254-- Serial streams compose serially or non-concurrently. In a composed stream,
src/Streamly/Streams/Prelude.hs+1−87
@@ -9,12 +9,6 @@
99{-# LANGUAGE StandaloneDeriving #-}
1010{-# LANGUAGE UndecidableInstances #-} -- XXX
1111
12−#if __GLASGOW_HASKELL__ >= 800
13−{-# OPTIONS_GHC -Wno-orphans #-}
14−#endif
15−
16−#include "inline.h"
17−
1812-- |
1913-- Module : Streamly.Streams.Prelude
2014-- Copyright : (c) 2017 Harendra Kumar
@@ -27,96 +21,16 @@
2721--
2822module Streamly.Streams.Prelude
2923 (
30− -- * Elimination
31− runStream
32− , runStreaming -- deprecated
33− , runStreamT -- deprecated
34− , runInterleavedT -- deprecated
35− , runParallelT -- deprecated
36− , runAsyncT -- deprecated
37− , runZipStream -- deprecated
38− , runZipAsync -- deprecated
39−
4024 -- * Fold Utilities
41− , foldWith
25+ foldWith
4226 , foldMapWith
4327 , forEachWith
4428 )
4529where
4630
4731import Streamly.Streams.StreamK (IsStream(..))
48−import Streamly.Streams.Serial (SerialT, WSerialT)
49−import Streamly.Streams.Parallel (ParallelT)
50−import Streamly.Streams.Async (AsyncT)
51−import Streamly.Streams.Zip (ZipSerialM, ZipAsyncM)
52−
53−import qualified Streamly.Streams.StreamD as D
5432import qualified Streamly.Streams.StreamK as K
5533
56−------------------------------------------------------------------------------
57−-- Eliminating a stream
58−------------------------------------------------------------------------------
59−
60−-- | Run a streaming composition, discard the results. By default it interprets
61−-- the stream as 'SerialT', to run other types of streams use the type adapting
62−-- combinators for example @runStream . 'asyncly'@.
63−--
64−-- @since 0.2.0
65−{-# INLINE_EARLY runStream #-}
66−runStream :: Monad m => SerialT m a -> m ()
67−runStream m = D.runStream $ D.fromStreamK (toStream m)
68−{-# RULES "runStream fallback to CPS" [1]
69− forall a. D.runStream (D.fromStreamK a) = K.runStream a #-}
70−
71−-- | Same as 'runStream'
72−--
73−-- @since 0.1.0
74−{-# DEPRECATED runStreaming "Please use runStream instead." #-}
75−runStreaming :: (Monad m, IsStream t) => t m a -> m ()
76−runStreaming = runStream . K.adapt
77−
78−-- | Same as @runStream@.
79−--
80−-- @since 0.1.0
81−{-# DEPRECATED runStreamT "Please use runStream instead." #-}
82−runStreamT :: Monad m => SerialT m a -> m ()
83−runStreamT = runStream
84−
85−-- | Same as @runStream . wSerially@.
86−--
87−-- @since 0.1.0
88−{-# DEPRECATED runInterleavedT "Please use 'runStream . interleaving' instead." #-}
89−runInterleavedT :: Monad m => WSerialT m a -> m ()
90−runInterleavedT = runStream . K.adapt
91−
92−-- | Same as @runStream . parallely@.
93−--
94−-- @since 0.1.0
95−{-# DEPRECATED runParallelT "Please use 'runStream . parallely' instead." #-}
96−runParallelT :: Monad m => ParallelT m a -> m ()
97−runParallelT = runStream . K.adapt
98−
99−-- | Same as @runStream . asyncly@.
100−--
101−-- @since 0.1.0
102−{-# DEPRECATED runAsyncT "Please use 'runStream . asyncly' instead." #-}
103−runAsyncT :: Monad m => AsyncT m a -> m ()
104−runAsyncT = runStream . K.adapt
105−
106−-- | Same as @runStream . zipping@.
107−--
108−-- @since 0.1.0
109−{-# DEPRECATED runZipStream "Please use 'runStream . zipSerially instead." #-}
110−runZipStream :: Monad m => ZipSerialM m a -> m ()
111−runZipStream = runStream . K.adapt
112−
113−-- | Same as @runStream . zippingAsync@.
114−--
115−-- @since 0.1.0
116−{-# DEPRECATED runZipAsync "Please use 'runStream . zipAsyncly instead." #-}
117−runZipAsync :: Monad m => ZipAsyncM m a -> m ()
118−runZipAsync = runStream . K.adapt
119−
12034------------------------------------------------------------------------------
12135-- Fold Utilities
12236------------------------------------------------------------------------------
src/Streamly/Streams/StreamK.hs+46−2
@@ -116,6 +116,10 @@ module Streamly.Streams.StreamK
116116 -- ** Map and Filter
117117 , mapMaybe
118118
119+ -- ** Zipping
120+ , zipWith
121+ , zipWithM
122+
119123 -- * Semigroup Style Composition
120124 , serial
121125
@@ -137,7 +141,7 @@ import Data.Semigroup (Semigroup(..))
137141import Prelude
138142 hiding (foldl, foldr, last, map, mapM, mapM_, repeat, sequence,
139143 take, filter, all, any, takeWhile, drop, dropWhile, minimum,
140− maximum, elem, notElem, null, head, tail)
144+ maximum, elem, notElem, null, head, tail, zipWith)
141145import qualified Prelude
142146
143147import Streamly.SVar
@@ -521,7 +525,7 @@ foldxM step begin done m = go begin (toStream m)
521525 go !acc m1 =
522526 let stop = acc >>= done
523527 single a = acc >>= \b -> step b a >>= done
524− yieldk a r = acc >>= \b -> go (step b a) r
528+ yieldk a r = acc >>= \b -> step b a >>= \x -> go (return x) r
525529 in (unStream m1) defState stop single yieldk
526530
527531-- | Like 'foldl'' but with a monadic step function.
@@ -811,6 +815,46 @@ mapMaybe f m = go (toStream m)
811815 Nothing -> (unStream r) (rstState st) stp single yieldk
812816 in unStream m1 (rstState st) stp single yieldk
813817
818+------------------------------------------------------------------------------
819+-- Serial Zipping
820+------------------------------------------------------------------------------
821+
822+{-# INLINE zipWithS #-}
823+zipWithS :: (a -> b -> c) -> Stream m a -> Stream m b -> Stream m c
824+zipWithS f m1 m2 = go m1 m2
825+ where
826+ go mx my = Stream $ \st stp sng yld -> do
827+ let merge a ra =
828+ let single2 b = sng (f a b)
829+ yield2 b rb = yld (f a b) (go ra rb)
830+ in unStream my (rstState st) stp single2 yield2
831+ let single1 a = merge a nil
832+ yield1 a ra = merge a ra
833+ unStream mx (rstState st) stp single1 yield1
834+
835+-- | Zip two streams serially using a pure zipping function.
836+--
837+-- @since 0.1.0
838+{-# INLINABLE zipWith #-}
839+zipWith :: IsStream t => (a -> b -> c) -> t m a -> t m b -> t m c
840+zipWith f m1 m2 = fromStream $ zipWithS f (toStream m1) (toStream m2)
841+
842+-- | Zip two streams serially using a monadic zipping function.
843+--
844+-- @since 0.1.0
845+zipWithM :: (IsStream t, Monad m) => (a -> b -> m c) -> t m a -> t m b -> t m c
846+zipWithM f m1 m2 = fromStream $ go (toStream m1) (toStream m2)
847+ where
848+ go mx my = Stream $ \st stp sng yld -> do
849+ let merge a ra =
850+ let runIt x = unStream x (rstState st) stp sng yld
851+ single2 b = f a b >>= sng
852+ yield2 b rb = f a b >>= \x -> runIt (x `cons` go ra rb)
853+ in unStream my (rstState st) stp single2 yield2
854+ let single1 a = merge a nil
855+ yield1 a ra = merge a ra
856+ unStream mx (rstState st) stp single1 yield1
857+
814858------------------------------------------------------------------------------
815859-- Semigroup
816860------------------------------------------------------------------------------
src/Streamly/Streams/Zip.hs+5−45
@@ -20,8 +20,8 @@
2020--
2121module Streamly.Streams.Zip
2222 (
23− zipWith
24− , zipWithM
23+ K.zipWith
24+ , K.zipWithM
2525 , zipAsyncWith
2626 , zipAsyncWithM
2727
@@ -50,46 +50,6 @@ import qualified Streamly.Streams.StreamK as K
5050
5151#include "Instances.hs"
5252
53−------------------------------------------------------------------------------
54−-- Serial Zipping
55−------------------------------------------------------------------------------
56−
57−{-# INLINE zipWithS #-}
58−zipWithS :: (a -> b -> c) -> Stream m a -> Stream m b -> Stream m c
59−zipWithS f m1 m2 = go m1 m2
60− where
61− go mx my = Stream $ \st stp sng yld -> do
62− let merge a ra =
63− let single2 b = sng (f a b)
64− yield2 b rb = yld (f a b) (go ra rb)
65− in unStream my (rstState st) stp single2 yield2
66− let single1 a = merge a K.nil
67− yield1 a ra = merge a ra
68− unStream mx (rstState st) stp single1 yield1
69−
70−-- | Zip two streams serially using a pure zipping function.
71−--
72−-- @since 0.1.0
73−{-# INLINABLE zipWith #-}
74−zipWith :: IsStream t => (a -> b -> c) -> t m a -> t m b -> t m c
75−zipWith f m1 m2 = fromStream $ zipWithS f (toStream m1) (toStream m2)
76−
77−-- | Zip two streams serially using a monadic zipping function.
78−--
79−-- @since 0.1.0
80−zipWithM :: (IsStream t, Monad m) => (a -> b -> m c) -> t m a -> t m b -> t m c
81−zipWithM f m1 m2 = fromStream $ go (toStream m1) (toStream m2)
82− where
83− go mx my = Stream $ \st stp sng yld -> do
84− let merge a ra =
85− let runIt x = unStream x (rstState st) stp sng yld
86− single2 b = f a b >>= sng
87− yield2 b rb = f a b >>= \x -> runIt (x `K.cons` go ra rb)
88− in unStream my (rstState st) stp single2 yield2
89− let single1 a = merge a K.nil
90− yield1 a ra = merge a ra
91− unStream mx (rstState st) stp single1 yield1
92−
9353------------------------------------------------------------------------------
9454-- Serially Zipping Streams
9555------------------------------------------------------------------------------
@@ -157,7 +117,7 @@ instance Monad m => Functor (ZipSerialM m) where
157117
158118instance Monad m => Applicative (ZipSerialM m) where
159119 pure = ZipSerialM . K.repeat
160− m1 <*> m2 = fromStream $ zipWith id (toStream m1) (toStream m2)
120+ m1 <*> m2 = fromStream $ K.zipWith id (toStream m1) (toStream m2)
161121
162122------------------------------------------------------------------------------
163123-- Parallel Zipping
@@ -172,7 +132,7 @@ zipAsyncWith :: (IsStream t, MonadAsync m)
172132zipAsyncWith f m1 m2 = fromStream $ Stream $ \st stp sng yld -> do
173133 ma <- mkAsync' (rstState st) m1
174134 mb <- mkAsync' (rstState st) m2
175− unStream (toStream (zipWith f ma mb)) (rstState st) stp sng yld
135+ unStream (toStream (K.zipWith f ma mb)) (rstState st) stp sng yld
176136
177137-- | Zip two streams asyncly (i.e. both the elements being zipped are generated
178138-- concurrently) using a monadic zipping function.
@@ -183,7 +143,7 @@ zipAsyncWithM :: (IsStream t, MonadAsync m)
183143zipAsyncWithM f m1 m2 = fromStream $ Stream $ \st stp sng yld -> do
184144 ma <- mkAsync' (rstState st) m1
185145 mb <- mkAsync' (rstState st) m2
186− unStream (toStream (zipWithM f ma mb)) (rstState st) stp sng yld
146+ unStream (toStream (K.zipWithM f ma mb)) (rstState st) stp sng yld
187147
188148------------------------------------------------------------------------------
189149-- Parallely Zipping Streams
stack-7.10.yaml+2−2
@@ -15,5 +15,5 @@ extra-deps:
1515flags: {}
1616extra-package-dbs: []
1717# For mac ports installed SDL library on Mac OS X
18−#extra-include-dirs:
19−#- /opt/local/include
18+extra-include-dirs:
19+- /opt/local/include
streamly.cabal+53−26
@@ -274,12 +274,11 @@ test-suite parallel-loops
274274-- Benchmarks
275275-------------------------------------------------------------------------------
276276
277−benchmark base
277+benchmark linear
278278 type: exitcode-stdio-1.0
279279 hs-source-dirs: benchmark
280− main-is: BaseStreams.hs
281− other-modules: StreamDOps
282− , StreamKOps
280+ main-is: Linear.hs
281+ other-modules: LinearOps
283282 default-language: Haskell2010
284283 ghc-options: -O2 -Wall
285284 if flag(dev)
@@ -295,22 +294,18 @@ benchmark base
295294 -Wredundant-constraints
296295 -Wnoncanonical-monad-instances
297296 -Wnoncanonical-monadfail-instances
298− if flag(dev)
299− buildable: True
300− build-depends:
301− streamly
302− , base >= 4.8 && < 5
303− , deepseq >= 1.4.0 && < 1.5
304− , random >= 1.0 && < 2.0
305− , gauge >= 0.2.3 && < 0.3
306− else
307− buildable: False
297+ build-depends:
298+ streamly
299+ , base >= 4.8 && < 5
300+ , deepseq >= 1.4.0 && < 1.5
301+ , random >= 1.0 && < 2.0
302+ , gauge >= 0.2.3 && < 0.3
308303
309−benchmark linear
304+benchmark nested
310305 type: exitcode-stdio-1.0
311306 hs-source-dirs: benchmark
312− main-is: Linear.hs
313− other-modules: LinearOps
307+ main-is: Nested.hs
308+ other-modules: NestedOps
314309 default-language: Haskell2010
315310 ghc-options: -O2 -Wall
316311 if flag(dev)
@@ -333,11 +328,25 @@ benchmark linear
333328 , random >= 1.0 && < 2.0
334329 , gauge >= 0.2.3 && < 0.3
335330
336−benchmark nested
331+-------------------------------------------------------------------------------
332+-- Internal benchmarks for unexposed modules
333+-------------------------------------------------------------------------------
334+
335+-- We have to copy the streamly library modules here because there is no
336+-- way to use unexposed modules from the library.
337+
338+benchmark base
337339 type: exitcode-stdio-1.0
338− hs-source-dirs: benchmark
339− main-is: Nested.hs
340− other-modules: NestedOps
340+ hs-source-dirs: benchmark, src
341+ main-is: BaseStreams.hs
342+ other-modules: Streamly.SVar
343+ , Streamly.Streams.StreamK
344+ , Streamly.Streams.StreamD
345+ , Streamly.Streams.Prelude
346+
347+ , StreamDOps
348+ , StreamKOps
349+
341350 default-language: Haskell2010
342351 ghc-options: -O2 -Wall
343352 if flag(dev)
@@ -353,12 +362,30 @@ benchmark nested
353362 -Wredundant-constraints
354363 -Wnoncanonical-monad-instances
355364 -Wnoncanonical-monadfail-instances
365+
356366 build-depends:
357− streamly
358− , base >= 4.8 && < 5
359− , deepseq >= 1.4.0 && < 1.5
360− , random >= 1.0 && < 2.0
361− , gauge >= 0.2.3 && < 0.3
367+ base >= 4.8 && < 5
368+ , deepseq >= 1.4.0 && < 1.5
369+ , random >= 1.0 && < 2.0
370+ , gauge >= 0.2.3 && < 0.3
371+
372+ , ghc-prim >= 0.2 && < 0.6
373+ , containers >= 0.5 && < 0.6
374+ , heaps >= 0.3 && < 0.4
375+
376+ -- concurrency
377+ , atomic-primops >= 0.8 && < 0.9
378+ , lockfree-queue >= 0.2.3 && < 0.3
379+
380+ , exceptions >= 0.8 && < 0.11
381+ , monad-control >= 1.0 && < 2
382+ , mtl >= 2.2 && < 3
383+ , transformers >= 0.4 && < 0.6
384+ , transformers-base >= 0.4 && < 0.5
385+
386+ if impl(ghc < 8.0)
387+ build-depends:
388+ semigroups >= 0.18 && < 0.19
362389
363390executable chart-linear
364391 default-language: Haskell2010
test/Main.hs+132−25
@@ -1,16 +1,19 @@
11{-# LANGUAGE CPP #-}
22{-# LANGUAGE FlexibleContexts #-}
33{-# LANGUAGE RankNTypes #-}
4+{-# LANGUAGE ScopedTypeVariables #-}
45
56module Main (main) where
67
78import Control.Concurrent (threadDelay)
8−import Control.Exception (Exception, try)
9+import Control.Exception (Exception, try, ErrorCall(..), catch, throw)
910import Control.Monad.Catch (throwM, MonadThrow)
1011import Control.Monad.Error.Class (throwError, MonadError)
1112import Control.Monad.Trans.Except (runExceptT, ExceptT)
1213import Data.Foldable (forM_, fold)
1314import Data.List (sort)
15+
16+import Data.IORef
1417import Test.Hspec
1518
1619import Streamly
@@ -88,30 +91,6 @@ main = hspec $ do
8891 (toListParallel ((,) <$> (return 1) <*> (return 2 <> return 3)) >>= return . sort)
8992 `shouldReturn` ([(1,2),(1,3)] :: [(Int, Int)])
9093
91− ---------------------------------------------------------------------------
92− -- Semigroup/Monoidal Composition strict ordering checks
93− ---------------------------------------------------------------------------
94−
95− -- test both (<>) and mappend to make sure we are using correct instance
96− -- for Monoid that is using the right version of semigroup. Instance
97− -- deriving can cause us to pick wrong instances sometimes.
98−
99− describe "WSerial interleaved (<>) ordering check" $ interleaveCheck wSerially (<>)
100− describe "WSerial interleaved mappend ordering check" $ interleaveCheck wSerially mappend
101−
102− -- describe "WAsync interleaved (<>) ordering check" $ interleaveCheck wAsyncly (<>)
103− -- describe "WAsync interleaved mappend ordering check" $ interleaveCheck wAsyncly mappend
104−
105− describe "Async (<>) time order check" $ parallelCheck asyncly (<>)
106− describe "Async mappend time order check" $ parallelCheck asyncly mappend
107−
108− -- XXX this keeps failing intermittently, need to investigate
109− -- describe "WAsync (<>) time order check" $ parallelCheck wAsyncly (<>)
110− -- describe "WAsync mappend time order check" $ parallelCheck wAsyncly mappend
111−
112− describe "Parallel (<>) time order check" $ parallelCheck parallely (<>)
113− describe "Parallel mappend time order check" $ parallelCheck parallely mappend
114−
11594 ---------------------------------------------------------------------------
11695 -- Monoidal Compositions, multiset equality checks
11796 ---------------------------------------------------------------------------
@@ -384,6 +363,50 @@ main = hspec $ do
384363 describe "take on infinite concurrent stream" $ takeInfinite wAsyncly
385364 describe "take on infinite concurrent stream" $ takeInfinite aheadly
386365
366+ ---------------------------------------------------------------------------
367+ -- Folds are strict enough
368+ ---------------------------------------------------------------------------
369+
370+ it "foldx is strict enough" checkFoldxStrictness
371+ it "foldl' is strict enough" checkFoldl'Strictness
372+ it "scanx is strict enough" checkScanxStrictness
373+ it "scanl' is strict enough" checkScanl'Strictness
374+ it "foldxM is strict enough" (checkFoldMStrictness foldxMStrictCheck)
375+ it "foldlM' is strict enough" (checkFoldMStrictness foldlM'StrictCheck)
376+ it "scanlM' is strict enough" (checkScanlMStrictness scanlM'StrictCheck)
377+
378+ ---------------------------------------------------------------------------
379+ -- Slower tests are at the end
380+ ---------------------------------------------------------------------------
381+
382+ ---------------------------------------------------------------------------
383+ -- Semigroup/Monoidal Composition strict ordering checks
384+ ---------------------------------------------------------------------------
385+
386+ -- test both (<>) and mappend to make sure we are using correct instance
387+ -- for Monoid that is using the right version of semigroup. Instance
388+ -- deriving can cause us to pick wrong instances sometimes.
389+
390+ describe "WSerial interleaved (<>) ordering check" $ interleaveCheck wSerially (<>)
391+ describe "WSerial interleaved mappend ordering check" $ interleaveCheck wSerially mappend
392+
393+ -- describe "WAsync interleaved (<>) ordering check" $ interleaveCheck wAsyncly (<>)
394+ -- describe "WAsync interleaved mappend ordering check" $ interleaveCheck wAsyncly mappend
395+
396+ describe "Async (<>) time order check" $ parallelCheck asyncly (<>)
397+ describe "Async mappend time order check" $ parallelCheck asyncly mappend
398+
399+ -- XXX this keeps failing intermittently, need to investigate
400+ -- describe "WAsync (<>) time order check" $ parallelCheck wAsyncly (<>)
401+ -- describe "WAsync mappend time order check" $ parallelCheck wAsyncly mappend
402+
403+ describe "Parallel (<>) time order check" $ parallelCheck parallely (<>)
404+ describe "Parallel mappend time order check" $ parallelCheck parallely mappend
405+
406+ ---------------------------------------------------------------------------
407+ -- Thread limits
408+ ---------------------------------------------------------------------------
409+
387410 it "asyncly crosses thread limit (2000 threads)" $
388411 runStream (asyncly $ fold $
389412 replicate 2000 $ S.yieldM $ threadDelay 1000000)
@@ -394,6 +417,90 @@ main = hspec $ do
394417 replicate 4000 $ S.yieldM $ threadDelay 1000000)
395418 `shouldReturn` ()
396419
420+
421+checkFoldxStrictness :: IO ()
422+checkFoldxStrictness = do
423+ let s = return (1 :: Int) `S.consM` error "failure"
424+ catch (S.foldx (\_ a -> if a == 1 then error "success" else "done")
425+ "begin" id s)
426+ (\e -> case e of
427+ ErrorCall err -> return err
428+ _ -> throw e)
429+ `shouldReturn` "success"
430+
431+checkFoldl'Strictness :: IO ()
432+checkFoldl'Strictness = do
433+ let s = return (1 :: Int) `S.consM` error "failure"
434+ catch (S.foldl' (\_ a -> if a == 1 then error "success" else "done")
435+ "begin" s)
436+ (\e -> case e of
437+ ErrorCall err -> return err
438+ _ -> throw e)
439+ `shouldReturn` "success"
440+
441+checkScanxStrictness :: IO ()
442+checkScanxStrictness = do
443+ let s = return (1 :: Int) `S.consM` error "failure"
444+ catch
445+ (runStream (
446+ S.scanx (\_ a ->
447+ if a == 1
448+ then error "success"
449+ else "done")
450+ "begin" id s
451+ )
452+ >> return "finished"
453+ )
454+ (\e -> case e of
455+ ErrorCall err -> return err
456+ _ -> throw e)
457+ `shouldReturn` "success"
458+
459+checkScanl'Strictness :: IO ()
460+checkScanl'Strictness = do
461+ let s = return (1 :: Int) `S.consM` error "failure"
462+ catch
463+ (runStream
464+ (S.scanl'
465+ (\_ a ->
466+ if a == 1
467+ then error "success"
468+ else "done")
469+ "begin"
470+ s)
471+ >> return "finished"
472+ )
473+ (\e -> case e of
474+ ErrorCall err -> return err
475+ _ -> throw e)
476+ `shouldReturn` "success"
477+
478+foldlM'StrictCheck :: IORef Int -> SerialT IO Int -> IO ()
479+foldlM'StrictCheck ref s =
480+ S.foldlM' (\_ _ -> writeIORef ref 1) () s
481+
482+foldxMStrictCheck :: IORef Int -> SerialT IO Int -> IO ()
483+foldxMStrictCheck ref s =
484+ S.foldxM (\_ _ -> writeIORef ref 1) (return ()) return s
485+
486+checkFoldMStrictness :: (IORef Int -> SerialT IO Int -> IO ()) -> IO ()
487+checkFoldMStrictness f = do
488+ ref <- newIORef 0
489+ let s = return 1 `S.consM` error "x"
490+ catch (f ref s) (\(_ :: ErrorCall) -> return ())
491+ readIORef ref `shouldReturn` 1
492+
493+scanlM'StrictCheck :: IORef Int -> SerialT IO Int -> SerialT IO ()
494+scanlM'StrictCheck ref s =
495+ S.scanlM' (\_ _ -> writeIORef ref 1) () s
496+
497+checkScanlMStrictness :: (IORef Int -> SerialT IO Int -> SerialT IO ()) -> IO ()
498+checkScanlMStrictness f = do
499+ ref <- newIORef 0
500+ let s = return 1 `S.consM` error "x"
501+ catch (runStream $ f ref s) (\(_ :: ErrorCall) -> return ())
502+ readIORef ref `shouldReturn` 1
503+
397504takeInfinite :: IsStream t => (t IO Int -> SerialT IO Int) -> Spec
398505takeInfinite t = do
399506 it "take 1" $