on master help sign in
Add Streamly.Array module

* Supports fixed size immutable arrays * See the module documentations for details.

commit36c4c4ca7b505e3e99bcf7143535ac6a1bee8aab authorHarendra Kumar <harendra.kumar@gmail.com> dateMon 6 May 2019, 17:24 (7 years ago) parentdb2c560
15 files changed+1897−146
Changelog.md+10−12 benchmark/Adaptive.hs+2−2 benchmark/Array.hs+222 benchmark/ArrayOps.hs+517 benchmark/Linear.hs+17−6 benchmark/LinearOps.hs+14−7 src/Streamly/Array.hs+312 src/Streamly/Array/Types.hs+470 src/Streamly/Prelude.hs+19−9 src/Streamly/Streams/Instances.hs+3 src/Streamly/Streams/Prelude.hs+18 src/Streamly/Streams/StreamD.hs+66−102 src/Streamly/Streams/StreamD/Type.hs+112−1 streamly.cabal+69−7 test/Arrays.hs+46
Changelog.md+10−12
@@ -3,27 +3,25 @@
33### Breaking changes
44
55* Change the signature of `foldrM` to ensure that it is lazy
6−* `scanx` would require an additional `Monad m` constraint.
6+* `scanx` would now require an additional `Monad m` constraint.
77
88### Enhancements
99
10−* Add `foldrS` to fold a stream to a stream
11−* Add `foldrT` to fold a stream to an arbitrary transformer monad
12−* Add `toRevList` to fold a stream to a reverse list
10+* Add Streamly.Array module to support efficient fixed size immutable arrays
11+ for buffering and IO.
12+* Add the following to "Streamly.Prelude":
13+ * `foldrS` to fold a stream to a stream
14+ * `foldrT` to fold a stream to an arbitrary transformer monad
15+ * `toRevList` to fold a stream to a reverse list
1316
1417### Deprecations
1518
16−* Deprecate `foldr` and `foldr1`. Given the signature of `foldr`, its behavior
17− necessarily depends on the strictness or laziness of the underlying monad.
18− Therefore, it cannot be lazy enough in a strict monad like IO. For that
19− reason we no longer support this version of right fold. Please use `foldrM`
20− instead.
21−
2219* `runStream`, `foldWith`, `foldMapWith`, `forEachWith` have been moved from
2320 "Streamly" module to "Streamly.Prelude" module.
2421
25−* Deprecate `scanx`, `foldx`, `foldxM`
26−* Remove deprecated APIs `scan`, `foldl`, `foldlM`
22+* "Streamly.Prelude":
23+ * Deprecate `scanx`, `foldx`, `foldxM`, `foldr1`
24+ * Remove deprecated APIs `scan`, `foldl`, `foldlM`
2725
2826## 0.6.1
2927
benchmark/Adaptive.hs+2−2
@@ -9,7 +9,7 @@ import Control.Concurrent (threadDelay)
99import Control.Monad (when)
1010import Control.Monad.IO.Class (liftIO)
1111import Gauge
12−import Streamly
12+import Streamly hiding (runStream)
1313import Streamly.Prelude as S
1414import System.Random (randomRIO)
1515
@@ -32,7 +32,7 @@ source range = S.replicateM value $ do
3232
3333{-# INLINE run #-}
3434run :: IsStream t => (Int, Int) -> (Int, Int) -> (t IO Int -> SerialT IO Int) -> IO ()
35−run srange crange t = runStream $ do
35+run srange crange t = S.runStream $ do
3636 n <- t $ source srange
3737 d <- liftIO (randomRIO crange)
3838 when (d /= 0) $ liftIO $ threadDelay d
benchmark/Array.hsnew file+222
@@ -0,0 +1,222 @@
1+{-# LANGUAGE CPP #-}
2+-- |
3+-- Module : Main
4+-- Copyright : (c) 2018 Harendra Kumar
5+--
6+-- License : BSD3
7+-- Maintainer : harendra.kumar@gmail.com
8+
9+import Control.DeepSeq (NFData(..), deepseq)
10+import Foreign.Storable (Storable(..))
11+import System.Random (randomRIO)
12+
13+import qualified GHC.Exts as GHC
14+
15+import qualified ArrayOps as Ops
16+
17+import Gauge
18+
19+-------------------------------------------------------------------------------
20+--
21+-------------------------------------------------------------------------------
22+
23+{-# INLINE benchPure #-}
24+benchPure :: NFData b => String -> (Int -> a) -> (a -> b) -> Benchmark
25+benchPure name src f = bench name $ nfIO $
26+ randomRIO (1,1) >>= return . f . src
27+
28+{-# INLINE benchPureSink #-}
29+benchPureSink :: NFData b => String -> (Ops.Stream Int -> b) -> Benchmark
30+benchPureSink name f = benchPure name Ops.sourceIntFromTo f
31+
32+{-# INLINE benchPureSrc #-}
33+benchPureSrc :: (NFData a, Storable a)
34+ => String -> (Int -> Ops.Stream a) -> Benchmark
35+benchPureSrc name src = benchPure name src id
36+
37+mkString :: String
38+mkString = "[1" ++ concat (replicate Ops.value ",1") ++ "]"
39+
40+main :: IO ()
41+main =
42+ defaultMain
43+ [ bgroup "array"
44+ [ bgroup "generation"
45+ [ benchPureSrc "intFromTo" Ops.sourceIntFromTo
46+ , benchPureSrc "unfoldr" Ops.sourceUnfoldr
47+ , benchPureSrc "fromList" Ops.sourceFromList
48+ , benchPureSrc "IsList.fromList" Ops.sourceIsList
49+ , benchPureSrc "IsString.fromString" Ops.sourceIsString
50+ , mkString `deepseq` (bench "readsPrec" $ nf Ops.readInstance mkString)
51+ , benchPureSink "showsPrec pure streams" Ops.showInstance
52+ ]
53+ , bgroup "elimination"
54+ [ benchPureSink "id" id
55+ -- , benchPureSink "eqBy" Ops.eqBy
56+ , benchPureSink "==" Ops.eqInstance
57+ , benchPureSink "/=" Ops.eqInstanceNotEq
58+ {-
59+ , benchPureSink "cmpBy" Ops.cmpBy
60+ -}
61+ , benchPureSink "<" Ops.ordInstance
62+ , benchPureSink "min" Ops.ordInstanceMin
63+ , benchPureSink "IsList.toList" GHC.toList
64+ , benchPureSink "foldl'" Ops.pureFoldl'
65+#ifdef DEVBUILD
66+ , benchPureSink "foldable/foldl'" Ops.foldableFoldl'
67+ , benchPureSink "foldable/sum" Ops.foldableSum
68+ -- , benchPureSinkIO "traversable/mapM" Ops.traversableMapM
69+#endif
70+ ]
71+
72+ {-
73+ [ benchPureSink "uncons" Ops.uncons
74+ , benchPureSink "toNull" $ Ops.toNull serially
75+ , benchPureSink "mapM_" Ops.mapM_
76+
77+ , benchPureSink "init" Ops.init
78+ , benchPureSink "tail" Ops.tail
79+ , benchPureSink "nullHeadTail" Ops.nullHeadTail
80+
81+ -- this is too low and causes all benchmarks reported in ns
82+ -- , benchPureSink "head" Ops.head
83+ , benchPureSink "last" Ops.last
84+ -- , benchPureSink "lookup" Ops.lookup
85+ , benchPureSink "find" Ops.find
86+ , benchPureSink "findIndex" Ops.findIndex
87+ , benchPureSink "elemIndex" Ops.elemIndex
88+
89+ -- this is too low and causes all benchmarks reported in ns
90+ -- , benchPureSink "null" Ops.null
91+ , benchPureSink "elem" Ops.elem
92+ , benchPureSink "notElem" Ops.notElem
93+ , benchPureSink "all" Ops.all
94+ , benchPureSink "any" Ops.any
95+ , benchPureSink "and" Ops.and
96+ , benchPureSink "or" Ops.or
97+
98+ , benchPureSink "length" Ops.length
99+ , benchPureSink "sum" Ops.sum
100+ , benchPureSink "product" Ops.product
101+
102+ , benchPureSink "maximumBy" Ops.maximumBy
103+ , benchPureSink "maximum" Ops.maximum
104+ , benchPureSink "minimumBy" Ops.minimumBy
105+ , benchPureSink "minimum" Ops.minimum
106+
107+ , benchPureSink "toList" Ops.toList
108+ , benchPureSink "toRevList" Ops.toRevList
109+ ]
110+ -}
111+ , bgroup "transformation"
112+ [ benchPureSink "scanl'" (Ops.scanl' 1)
113+ , benchPureSink "scanl1'" (Ops.scanl1' 1)
114+ , benchPureSink "map" (Ops.map 1)
115+ {-
116+ , benchPureSink "fmap" (Ops.fmap 1)
117+ , benchPureSink "mapM" (Ops.mapM serially 1)
118+ , benchPureSink "mapMaybe" (Ops.mapMaybe 1)
119+ , benchPureSink "mapMaybeM" (Ops.mapMaybeM 1)
120+ , bench "sequence" $ nfIO $ randomRIO (1,1000) >>= \n ->
121+ Ops.sequence serially (Ops.sourceUnfoldrMAction n)
122+ , benchPureSink "findIndices" (Ops.findIndices 1)
123+ , benchPureSink "elemIndices" (Ops.elemIndices 1)
124+ , benchPureSink "reverse" (Ops.reverse 1)
125+ , benchPureSink "foldrS" (Ops.foldrS 1)
126+ , benchPureSink "foldrSMap" (Ops.foldrSMap 1)
127+ , benchPureSink "foldrT" (Ops.foldrT 1)
128+ , benchPureSink "foldrTMap" (Ops.foldrTMap 1)
129+ -}
130+ ]
131+ , bgroup "transformationX4"
132+ [ benchPureSink "scanl'" (Ops.scanl' 4)
133+ , benchPureSink "scanl1'" (Ops.scanl1' 4)
134+ , benchPureSink "map" (Ops.map 4)
135+ {-
136+ , benchPureSink "fmap" (Ops.fmap 4)
137+ , benchPureSink "mapM" (Ops.mapM serially 4)
138+ , benchPureSink "mapMaybe" (Ops.mapMaybe 4)
139+ , benchPureSink "mapMaybeM" (Ops.mapMaybeM 4)
140+ -- , bench "sequence" $ nfIO $ randomRIO (1,1000) >>= \n ->
141+ -- Ops.sequence serially (Ops.sourceUnfoldrMAction n)
142+ , benchPureSink "findIndices" (Ops.findIndices 4)
143+ , benchPureSink "elemIndices" (Ops.elemIndices 4)
144+ -}
145+ ]
146+ {-
147+ , bgroup "filtering"
148+ [ benchPureSink "filter-even" (Ops.filterEven 1)
149+ , benchPureSink "filter-all-out" (Ops.filterAllOut 1)
150+ , benchPureSink "filter-all-in" (Ops.filterAllIn 1)
151+ , benchPureSink "take-all" (Ops.takeAll 1)
152+ , benchPureSink "takeWhile-true" (Ops.takeWhileTrue 1)
153+ --, benchPureSink "takeWhileM-true" (Ops.takeWhileMTrue 1)
154+ , benchPureSink "drop-one" (Ops.dropOne 1)
155+ , benchPureSink "drop-all" (Ops.dropAll 1)
156+ , benchPureSink "dropWhile-true" (Ops.dropWhileTrue 1)
157+ --, benchPureSink "dropWhileM-true" (Ops.dropWhileMTrue 1)
158+ , benchPureSink "dropWhile-false" (Ops.dropWhileFalse 1)
159+ , benchPureSink "deleteBy" (Ops.deleteBy 1)
160+ , benchPureSink "insertBy" (Ops.insertBy 1)
161+ ]
162+ , bgroup "filteringX4"
163+ [ benchPureSink "filter-even" (Ops.filterEven 4)
164+ , benchPureSink "filter-all-out" (Ops.filterAllOut 4)
165+ , benchPureSink "filter-all-in" (Ops.filterAllIn 4)
166+ , benchPureSink "take-all" (Ops.takeAll 4)
167+ , benchPureSink "takeWhile-true" (Ops.takeWhileTrue 4)
168+ --, benchPureSink "takeWhileM-true" (Ops.takeWhileMTrue 4)
169+ , benchPureSink "drop-one" (Ops.dropOne 4)
170+ , benchPureSink "drop-all" (Ops.dropAll 4)
171+ , benchPureSink "dropWhile-true" (Ops.dropWhileTrue 4)
172+ --, benchPureSink "dropWhileM-true" (Ops.dropWhileMTrue 4)
173+ , benchPureSink "dropWhile-false" (Ops.dropWhileFalse 4)
174+ , benchPureSink "deleteBy" (Ops.deleteBy 4)
175+ , benchPureSink "insertBy" (Ops.insertBy 4)
176+ ]
177+ , bgroup "multi-stream"
178+ [ benchPureSink "eqBy" Ops.eqBy
179+ , benchPureSink "cmpBy" Ops.cmpBy
180+ , benchPureSink "zip" Ops.zip
181+ , benchPureSink "zipM" Ops.zipM
182+ , benchPureSink "mergeBy" Ops.mergeBy
183+ , benchPureSink "isPrefixOf" Ops.isPrefixOf
184+ , benchPureSink "isSubsequenceOf" Ops.isSubsequenceOf
185+ , benchPureSink "stripPrefix" Ops.stripPrefix
186+ , benchPureSrc serially "concatMap" Ops.concatMap
187+ ]
188+ -- scanl-map and foldl-map are equivalent to the scan and fold in the foldl
189+ -- library. If scan/fold followed by a map is efficient enough we may not
190+ -- need monolithic implementations of these.
191+ , bgroup "mixed"
192+ [ benchPureSink "scanl-map" (Ops.scanMap 1)
193+ , benchPureSink "foldl-map" Ops.foldl'ReduceMap
194+ , benchPureSink "sum-product-fold" Ops.sumProductFold
195+ , benchPureSink "sum-product-scan" Ops.sumProductScan
196+ ]
197+ , bgroup "mixedX4"
198+ [ benchPureSink "scan-map" (Ops.scanMap 4)
199+ , benchPureSink "drop-map" (Ops.dropMap 4)
200+ , benchPureSink "drop-scan" (Ops.dropScan 4)
201+ , benchPureSink "take-drop" (Ops.takeDrop 4)
202+ , benchPureSink "take-scan" (Ops.takeScan 4)
203+ , benchPureSink "take-map" (Ops.takeMap 4)
204+ , benchPureSink "filter-drop" (Ops.filterDrop 4)
205+ , benchPureSink "filter-take" (Ops.filterTake 4)
206+ , benchPureSink "filter-scan" (Ops.filterScan 4)
207+ , benchPureSink "filter-scanl1" (Ops.filterScanl1 4)
208+ , benchPureSink "filter-map" (Ops.filterMap 4)
209+ ]
210+ , bgroup "iterated"
211+ [ benchPureSrc serially "mapM" Ops.iterateMapM
212+ , benchPureSrc serially "scan(1/100)" Ops.iterateScan
213+ , benchPureSrc serially "scanl1(1/100)" Ops.iterateScanl1
214+ , benchPureSrc serially "filterEven" Ops.iterateFilterEven
215+ , benchPureSrc serially "takeAll" Ops.iterateTakeAll
216+ , benchPureSrc serially "dropOne" Ops.iterateDropOne
217+ , benchPureSrc serially "dropWhileFalse" Ops.iterateDropWhileFalse
218+ , benchPureSrc serially "dropWhileTrue" Ops.iterateDropWhileTrue
219+ ]
220+ -}
221+ ]
222+ ]
benchmark/ArrayOps.hsnew file+517
@@ -0,0 +1,517 @@
1+-- |
2+-- Module : ArrayOps
3+-- Copyright : (c) 2018 Harendra Kumar
4+--
5+-- License : MIT
6+-- Maintainer : harendra.kumar@gmail.com
7+
8+{-# LANGUAGE CPP #-}
9+{-# LANGUAGE FlexibleContexts #-}
10+{-# LANGUAGE ScopedTypeVariables #-}
11+{-# LANGUAGE DeriveAnyClass #-}
12+{-# LANGUAGE DeriveGeneric #-}
13+
14+module ArrayOps where
15+
16+-- import Control.Monad (when)
17+import Data.Functor.Identity (Identity, runIdentity)
18+-- import Data.Maybe (fromJust)
19+import Prelude (Int, Bool, (+), ($), (==), (>), (.), Maybe(..), undefined)
20+import qualified Prelude as P
21+#ifdef DEVBUILD
22+import qualified Data.Foldable as F
23+#endif
24+import qualified GHC.Exts as GHC
25+-- import Control.DeepSeq (NFData)
26+-- import GHC.Generics (Generic)
27+
28+import qualified Streamly as S hiding (foldMapWith, runStream)
29+import qualified Streamly.Prelude as S
30+import qualified Streamly.Array as A
31+
32+value, maxValue :: Int
33+#ifdef LINEAR_ASYNC
34+value = 10000
35+#else
36+value = 100000
37+#endif
38+maxValue = value + 1
39+
40+-------------------------------------------------------------------------------
41+-- Benchmark ops
42+-------------------------------------------------------------------------------
43+
44+-------------------------------------------------------------------------------
45+-- Stream generation and elimination
46+-------------------------------------------------------------------------------
47+
48+type Stream = A.Array
49+
50+{-# INLINE sourceUnfoldr #-}
51+sourceUnfoldr :: Int -> Stream Int
52+sourceUnfoldr n = runIdentity $ A.fromStreamN value $ S.unfoldr step n
53+ where
54+ step cnt =
55+ if cnt > n + value
56+ then Nothing
57+ else (Just (cnt, cnt + 1))
58+
59+{-# INLINE sourceIntFromTo #-}
60+sourceIntFromTo :: Int -> Stream Int
61+sourceIntFromTo n = runIdentity $ A.fromStreamN value $ S.enumerateFromTo n (n + value)
62+
63+{-# INLINE sourceFromList #-}
64+sourceFromList :: Int -> Stream Int
65+sourceFromList n = runIdentity $ A.fromStreamN value $ S.fromList [n..n+value]
66+
67+{-# INLINE sourceIsList #-}
68+sourceIsList :: Int -> Stream Int
69+sourceIsList n = runIdentity $ A.fromStreamN value $ GHC.fromList [n..n+value]
70+
71+{-# INLINE sourceIsString #-}
72+sourceIsString :: Int -> Stream P.Char
73+sourceIsString n = runIdentity $ A.fromStreamN value $ GHC.fromString (P.replicate (n + value) 'a')
74+
75+{-
76+-------------------------------------------------------------------------------
77+-- Elimination
78+-------------------------------------------------------------------------------
79+
80+{-# INLINE runStream #-}
81+runStream :: Monad m => Stream m a -> m ()
82+runStream = S.runStream
83+
84+{-# INLINE toList #-}
85+toList :: Monad m => Stream m Int -> m [Int]
86+
87+{-# INLINE head #-}
88+{-# INLINE last #-}
89+{-# INLINE maximum #-}
90+{-# INLINE minimum #-}
91+{-# INLINE find #-}
92+{-# INLINE findIndex #-}
93+{-# INLINE elemIndex #-}
94+{-# INLINE foldl1'Reduce #-}
95+head, last, minimum, maximum, find, findIndex, elemIndex, foldl1'Reduce
96+ :: Monad m => Stream m Int -> m (Maybe Int)
97+
98+{-# INLINE minimumBy #-}
99+{-# INLINE maximumBy #-}
100+minimumBy, maximumBy :: Monad m => Stream m Int -> m (Maybe Int)
101+
102+{-# INLINE foldl'Reduce #-}
103+{-# INLINE foldl'ReduceMap #-}
104+{-# INLINE foldlM'Reduce #-}
105+{-# INLINE foldrMReduce #-}
106+{-# INLINE length #-}
107+{-# INLINE sum #-}
108+{-# INLINE product #-}
109+foldl'Reduce, foldl'ReduceMap, foldlM'Reduce, foldrMReduce, length, sum, product
110+ :: Monad m
111+ => Stream m Int -> m Int
112+
113+{-# INLINE foldl'Build #-}
114+{-# INLINE foldlM'Build #-}
115+{-# INLINE foldrMBuild #-}
116+foldrMBuild, foldl'Build, foldlM'Build
117+ :: Monad m
118+ => Stream m Int -> m [Int]
119+
120+{-# INLINE all #-}
121+{-# INLINE any #-}
122+{-# INLINE and #-}
123+{-# INLINE or #-}
124+{-# INLINE null #-}
125+{-# INLINE elem #-}
126+{-# INLINE notElem #-}
127+null, elem, notElem, all, any, and, or :: Monad m => Stream m Int -> m Bool
128+
129+{-# INLINE toNull #-}
130+toNull :: Monad m => (t m a -> S.SerialT m a) -> t m a -> m ()
131+toNull t = runStream . t
132+
133+{-# INLINE uncons #-}
134+uncons :: Monad m => Stream m Int -> m ()
135+uncons s = do
136+ r <- S.uncons s
137+ case r of
138+ Nothing -> return ()
139+ Just (_, t) -> uncons t
140+
141+{-# INLINE init #-}
142+init :: Monad m => Stream m a -> m ()
143+init s = S.init s >>= Prelude.mapM_ S.runStream
144+
145+{-# INLINE tail #-}
146+tail :: Monad m => Stream m a -> m ()
147+tail s = S.tail s >>= Prelude.mapM_ tail
148+
149+{-# INLINE nullHeadTail #-}
150+nullHeadTail :: Monad m => Stream m Int -> m ()
151+nullHeadTail s = do
152+ r <- S.null s
153+ when (not r) $ do
154+ _ <- S.head s
155+ S.tail s >>= Prelude.mapM_ nullHeadTail
156+
157+{-# INLINE mapM_ #-}
158+mapM_ :: Monad m => Stream m Int -> m ()
159+mapM_ = S.mapM_ (\_ -> return ())
160+
161+toList = S.toList
162+
163+{-# INLINE toRevList #-}
164+toRevList :: Monad m => Stream m Int -> m [Int]
165+toRevList = S.toRevList
166+
167+foldrMBuild = S.foldrM (\x xs -> xs >>= return . (x :)) (return [])
168+foldl'Build = S.foldl' (flip (:)) []
169+foldlM'Build = S.foldlM' (\xs x -> return $ x : xs) []
170+
171+foldrMReduce = S.foldrM (\x xs -> xs >>= return . (x +)) (return 0)
172+foldl'Reduce = S.foldl' (+) 0
173+foldl'ReduceMap = P.fmap (+1) . S.foldl' (+) 0
174+foldl1'Reduce = S.foldl1' (+)
175+foldlM'Reduce = S.foldlM' (\xs a -> return $ a + xs) 0
176+
177+last = S.last
178+null = S.null
179+head = S.head
180+elem = S.elem maxValue
181+notElem = S.notElem maxValue
182+length = S.length
183+all = S.all (<= maxValue)
184+any = S.any (> maxValue)
185+and = S.and . S.map (<= maxValue)
186+or = S.or . S.map (> maxValue)
187+find = S.find (== maxValue)
188+findIndex = S.findIndex (== maxValue)
189+elemIndex = S.elemIndex maxValue
190+maximum = S.maximum
191+minimum = S.minimum
192+sum = S.sum
193+product = S.product
194+minimumBy = S.minimumBy compare
195+maximumBy = S.maximumBy compare
196+-}
197+
198+-------------------------------------------------------------------------------
199+-- Transformation
200+-------------------------------------------------------------------------------
201+
202+{-# INLINE transform #-}
203+transform :: Stream a -> Stream a
204+transform = P.id
205+
206+{-# INLINE composeN #-}
207+composeN :: Int -> (Stream Int -> Stream Int) -> Stream Int -> Stream Int
208+composeN n f =
209+ case n of
210+ 1 -> transform . f
211+ 2 -> transform . f . f
212+ 3 -> transform . f . f . f
213+ 4 -> transform . f . f . f . f
214+ _ -> undefined
215+
216+{-# INLINE scanl' #-}
217+{-# INLINE scanl1' #-}
218+{-# INLINE map #-}
219+{-
220+{-# INLINE fmap #-}
221+{-# INLINE mapMaybe #-}
222+{-# INLINE filterEven #-}
223+{-# INLINE filterAllOut #-}
224+{-# INLINE filterAllIn #-}
225+{-# INLINE takeOne #-}
226+{-# INLINE takeAll #-}
227+{-# INLINE takeWhileTrue #-}
228+{-# INLINE takeWhileMTrue #-}
229+{-# INLINE dropOne #-}
230+{-# INLINE dropAll #-}
231+{-# INLINE dropWhileTrue #-}
232+{-# INLINE dropWhileMTrue #-}
233+{-# INLINE dropWhileFalse #-}
234+{-# INLINE findIndices #-}
235+{-# INLINE elemIndices #-}
236+{-# INLINE insertBy #-}
237+{-# INLINE deleteBy #-}
238+{-# INLINE reverse #-}
239+{-# INLINE foldrS #-}
240+{-# INLINE foldrSMap #-}
241+{-# INLINE foldrT #-}
242+{-# INLINE foldrTMap #-}
243+ -}
244+scanl' , scanl1', map{-, fmap, mapMaybe, filterEven, filterAllOut,
245+ filterAllIn, takeOne, takeAll, takeWhileTrue, takeWhileMTrue, dropOne,
246+ dropAll, dropWhileTrue, dropWhileMTrue, dropWhileFalse,
247+ findIndices, elemIndices, insertBy, deleteBy, reverse,
248+ foldrS, foldrSMap, foldrT, foldrTMap -}
249+ :: Int -> Stream Int -> Stream Int
250+
251+{-
252+{-# INLINE mapMaybeM #-}
253+mapMaybeM :: S.MonadAsync m => Int -> Stream m Int -> m ()
254+
255+{-# INLINE mapM #-}
256+{-# INLINE map' #-}
257+{-# INLINE fmap' #-}
258+mapM, map' :: (S.IsStream t, S.MonadAsync m)
259+ => (t m Int -> S.SerialT m Int) -> Int -> t m Int -> m ()
260+
261+fmap' :: (S.IsStream t, S.MonadAsync m, P.Functor (t m))
262+ => (t m Int -> S.SerialT m Int) -> Int -> t m Int -> m ()
263+
264+{-# INLINE sequence #-}
265+sequence :: (S.IsStream t, S.MonadAsync m)
266+ => (t m Int -> S.SerialT m Int) -> t m (m Int) -> m ()
267+ -}
268+
269+onArray :: (S.SerialT Identity Int -> S.SerialT Identity Int) -> Stream Int -> Stream Int
270+onArray f arr = runIdentity $ A.fromStreamN value $ f $ A.toStream arr
271+
272+scanl' n = composeN n $ onArray $ S.scanl' (+) 0
273+scanl1' n = composeN n $ onArray $ S.scanl1' (+)
274+map n = composeN n $ onArray $ S.map (+1)
275+-- map n = composeN n $ A.map (+1)
276+{-
277+fmap n = composeN n $ Prelude.fmap (+1)
278+fmap' t n = composeN' n $ t . Prelude.fmap (+1)
279+map' t n = composeN' n $ t . S.map (+1)
280+mapM t n = composeN' n $ t . S.mapM return
281+mapMaybe n = composeN n $ S.mapMaybe
282+ (\x -> if Prelude.odd x then Nothing else Just x)
283+mapMaybeM n = composeN n $ S.mapMaybeM
284+ (\x -> if Prelude.odd x then return Nothing else return $ Just x)
285+sequence t = transform . t . S.sequence
286+filterEven n = composeN n $ S.filter even
287+filterAllOut n = composeN n $ S.filter (> maxValue)
288+filterAllIn n = composeN n $ S.filter (<= maxValue)
289+takeOne n = composeN n $ S.take 1
290+takeAll n = composeN n $ S.take maxValue
291+takeWhileTrue n = composeN n $ S.takeWhile (<= maxValue)
292+takeWhileMTrue n = composeN n $ S.takeWhileM (return . (<= maxValue))
293+dropOne n = composeN n $ S.drop 1
294+dropAll n = composeN n $ S.drop maxValue
295+dropWhileTrue n = composeN n $ S.dropWhile (<= maxValue)
296+dropWhileMTrue n = composeN n $ S.dropWhileM (return . (<= maxValue))
297+dropWhileFalse n = composeN n $ S.dropWhile (> maxValue)
298+findIndices n = composeN n $ S.findIndices (== maxValue)
299+elemIndices n = composeN n $ S.elemIndices maxValue
300+insertBy n = composeN n $ S.insertBy compare maxValue
301+deleteBy n = composeN n $ S.deleteBy (>=) maxValue
302+reverse n = composeN n $ S.reverse
303+foldrS n = composeN n $ S.foldrS S.cons S.nil
304+foldrSMap n = composeN n $ S.foldrS (\x xs -> x + 1 `S.cons` xs) S.nil
305+foldrT n = composeN n $ S.foldrT S.cons S.nil
306+foldrTMap n = composeN n $ S.foldrT (\x xs -> x + 1 `S.cons` xs) S.nil
307+
308+-------------------------------------------------------------------------------
309+-- Iteration
310+-------------------------------------------------------------------------------
311+
312+iterStreamLen, maxIters :: Int
313+iterStreamLen = 10
314+maxIters = 10000
315+
316+{-# INLINE iterateSource #-}
317+iterateSource
318+ :: S.MonadAsync m
319+ => (Stream m Int -> Stream m Int) -> Int -> Int -> Stream m Int
320+iterateSource g i n = f i (sourceUnfoldrMN iterStreamLen n)
321+ where
322+ f (0 :: Int) m = g m
323+ f x m = g (f (x P.- 1) m)
324+
325+{-# INLINE iterateMapM #-}
326+{-# INLINE iterateScan #-}
327+{-# INLINE iterateScanl1 #-}
328+{-# INLINE iterateFilterEven #-}
329+{-# INLINE iterateTakeAll #-}
330+{-# INLINE iterateDropOne #-}
331+{-# INLINE iterateDropWhileFalse #-}
332+{-# INLINE iterateDropWhileTrue #-}
333+iterateMapM, iterateScan, iterateScanl1, iterateFilterEven, iterateTakeAll,
334+ iterateDropOne, iterateDropWhileFalse, iterateDropWhileTrue
335+ :: S.MonadAsync m
336+ => Int -> Stream m Int
337+
338+-- this is quadratic
339+iterateScan = iterateSource (S.scanl' (+) 0) (maxIters `div` 10)
340+-- so is this
341+iterateScanl1 = iterateSource (S.scanl1' (+)) (maxIters `div` 10)
342+
343+iterateMapM = iterateSource (S.mapM return) maxIters
344+iterateFilterEven = iterateSource (S.filter even) maxIters
345+iterateTakeAll = iterateSource (S.take maxValue) maxIters
346+iterateDropOne = iterateSource (S.drop 1) maxIters
347+iterateDropWhileFalse = iterateSource (S.dropWhile (> maxValue)) maxIters
348+iterateDropWhileTrue = iterateSource (S.dropWhile (<= maxValue)) maxIters
349+
350+-------------------------------------------------------------------------------
351+-- Zipping and concat
352+-------------------------------------------------------------------------------
353+
354+{-# INLINE zip #-}
355+{-# INLINE zipM #-}
356+{-# INLINE mergeBy #-}
357+zip, zipM, mergeBy :: Monad m => Stream m Int -> m ()
358+
359+zip src = do
360+ r <- S.tail src
361+ let src1 = fromJust r
362+ transform (S.zipWith (,) src src1)
363+zipM src = do
364+ r <- S.tail src
365+ let src1 = fromJust r
366+ transform (S.zipWithM (curry return) src src1)
367+
368+mergeBy src = do
369+ r <- S.tail src
370+ let src1 = fromJust r
371+ transform (S.mergeBy P.compare src src1)
372+
373+{-# INLINE isPrefixOf #-}
374+{-# INLINE isSubsequenceOf #-}
375+isPrefixOf, isSubsequenceOf :: Monad m => Stream m Int -> m Bool
376+
377+isPrefixOf src = S.isPrefixOf src src
378+isSubsequenceOf src = S.isSubsequenceOf src src
379+
380+{-# INLINE stripPrefix #-}
381+stripPrefix :: Monad m => Stream m Int -> m ()
382+stripPrefix src = do
383+ _ <- S.stripPrefix src src
384+ return ()
385+
386+{-# INLINE zipAsync #-}
387+{-# INLINE zipAsyncM #-}
388+{-# INLINE zipAsyncAp #-}
389+zipAsync, zipAsyncAp, zipAsyncM :: S.MonadAsync m => Stream m Int -> m ()
390+
391+zipAsync src = do
392+ r <- S.tail src
393+ let src1 = fromJust r
394+ transform (S.zipAsyncWith (,) src src1)
395+
396+zipAsyncM src = do
397+ r <- S.tail src
398+ let src1 = fromJust r
399+ transform (S.zipAsyncWithM (curry return) src src1)
400+
401+zipAsyncAp src = do
402+ r <- S.tail src
403+ let src1 = fromJust r
404+ transform (S.zipAsyncly $ (,) <$> S.serially src
405+ <*> S.serially src1)
406+
407+{-# INLINE eqBy #-}
408+eqBy :: (Monad m, P.Eq a) => Stream m a -> m P.Bool
409+eqBy src = S.eqBy (==) src src
410+
411+{-# INLINE cmpBy #-}
412+cmpBy :: (Monad m, P.Ord a) => Stream m a -> m P.Ordering
413+cmpBy src = S.cmpBy P.compare src src
414+
415+concatStreamLen, maxNested :: Int
416+concatStreamLen = 1
417+maxNested = 100000
418+
419+{-# INLINE concatMap #-}
420+concatMap :: S.MonadAsync m => Int -> Stream m Int
421+concatMap n = S.concatMap (\_ -> sourceUnfoldrMN maxNested n)
422+ (sourceUnfoldrMN concatStreamLen n)
423+
424+-------------------------------------------------------------------------------
425+-- Mixed Composition
426+-------------------------------------------------------------------------------
427+
428+{-# INLINE scanMap #-}
429+{-# INLINE dropMap #-}
430+{-# INLINE dropScan #-}
431+{-# INLINE takeDrop #-}
432+{-# INLINE takeScan #-}
433+{-# INLINE takeMap #-}
434+{-# INLINE filterDrop #-}
435+{-# INLINE filterTake #-}
436+{-# INLINE filterScan #-}
437+{-# INLINE filterScanl1 #-}
438+{-# INLINE filterMap #-}
439+scanMap, dropMap, dropScan, takeDrop, takeScan, takeMap, filterDrop,
440+ filterTake, filterScan, filterScanl1, filterMap
441+ :: Monad m => Int -> Stream m Int -> m ()
442+
443+scanMap n = composeN n $ S.map (subtract 1) . S.scanl' (+) 0
444+dropMap n = composeN n $ S.map (subtract 1) . S.drop 1
445+dropScan n = composeN n $ S.scanl' (+) 0 . S.drop 1
446+takeDrop n = composeN n $ S.drop 1 . S.take maxValue
447+takeScan n = composeN n $ S.scanl' (+) 0 . S.take maxValue
448+takeMap n = composeN n $ S.map (subtract 1) . S.take maxValue
449+filterDrop n = composeN n $ S.drop 1 . S.filter (<= maxValue)
450+filterTake n = composeN n $ S.take maxValue . S.filter (<= maxValue)
451+filterScan n = composeN n $ S.scanl' (+) 0 . S.filter (<= maxBound)
452+filterScanl1 n = composeN n $ S.scanl1' (+) . S.filter (<= maxBound)
453+filterMap n = composeN n $ S.map (subtract 1) . S.filter (<= maxValue)
454+
455+data Pair a b = Pair !a !b deriving (Generic, NFData)
456+
457+{-# INLINE sumProductFold #-}
458+sumProductFold :: Monad m => Stream m Int -> m (Int, Int)
459+sumProductFold = S.foldl' (\(s,p) x -> (s + x, p P.* x)) (0,1)
460+
461+{-# INLINE sumProductScan #-}
462+sumProductScan :: Monad m => Stream m Int -> m (Pair Int Int)
463+sumProductScan = S.foldl' (\(Pair _ p) (s0,x) -> Pair s0 (p P.* x)) (Pair 0 1)
464+ . S.scanl' (\(s,_) x -> (s + x,x)) (0,0)
465+
466+-------------------------------------------------------------------------------
467+-- Pure stream operations
468+-------------------------------------------------------------------------------
469+
470+-}
471+{-# INLINE eqInstance #-}
472+eqInstance :: Stream Int -> Bool
473+eqInstance src = src == src
474+
475+{-# INLINE eqInstanceNotEq #-}
476+eqInstanceNotEq :: Stream Int -> Bool
477+eqInstanceNotEq src = src P./= src
478+
479+{-# INLINE ordInstance #-}
480+ordInstance :: Stream Int -> Bool
481+ordInstance src = src P.< src
482+
483+{-# INLINE ordInstanceMin #-}
484+ordInstanceMin :: Stream Int -> Stream Int
485+ordInstanceMin src = P.min src src
486+
487+{-# INLINE showInstance #-}
488+showInstance :: Stream Int -> P.String
489+showInstance src = P.show src
490+
491+{-# INLINE readInstance #-}
492+readInstance :: P.String -> Stream Int
493+readInstance str =
494+ let r = P.reads str
495+ in case r of
496+ [(x,"")] -> x
497+ _ -> P.error "readInstance: no parse"
498+
499+{-# INLINE pureFoldl' #-}
500+pureFoldl' :: Stream Int -> Int
501+pureFoldl' = runIdentity . S.foldl' (+) 0 . A.toStream
502+
503+#ifdef DEVBUILD
504+{-# INLINE foldableFoldl' #-}
505+foldableFoldl' :: Stream Int -> Int
506+foldableFoldl' = F.foldl' (+) 0
507+
508+{-# INLINE foldableSum #-}
509+foldableSum :: Stream Int -> Int
510+foldableSum = P.sum
511+#endif
512+
513+{-
514+{-# INLINE traversableMapM #-}
515+traversableMapM :: Stream Identity Int -> IO (Stream Identity Int)
516+traversableMapM = P.mapM return
517+-}
benchmark/Linear.hs+17−6
@@ -6,7 +6,7 @@
66-- License : BSD3
77-- Maintainer : harendra.kumar@gmail.com
88
9−import Control.DeepSeq (NFData(..))
9+import Control.DeepSeq (NFData(..), deepseq)
1010import Data.Functor.Identity (Identity, runIdentity)
1111import System.Random (randomRIO)
1212
@@ -68,6 +68,15 @@ benchPureSinkIO name f =
6868benchPureSrc :: String -> (Int -> SerialT Identity a) -> Benchmark
6969benchPureSrc name src = benchPure name src (runIdentity . S.runStream)
7070
71+mkString :: String
72+mkString = "fromList [1" ++ concat (replicate Ops.value ",1") ++ "]"
73+
74+mkListString :: String
75+mkListString = "[1" ++ concat (replicate Ops.value ",1") ++ "]"
76+
77+mkList :: [Int]
78+mkList = [1..Ops.value]
79+
7180main :: IO ()
7281main =
7382 defaultMain
@@ -83,11 +92,13 @@ main =
8392 , benchPureSrc "IsList.fromList" Ops.sourceIsList
8493 , benchPureSink "IsList.toList" GHC.toList
8594 , benchPureSrc "IsString.fromString" Ops.sourceIsString
86− , benchPure "readsPrec" (\n -> S.fromList [1..n :: Int])
87− Ops.readInstance
88− , benchPureSink "showsPrec" Ops.showInstance
89− , benchPure "showsPrecList" (\n -> S.fromList [1..n :: Int])
90− Ops.showInstanceList
95+ , mkString `deepseq` (bench "readsPrec pure streams" $
96+ nf Ops.readInstance mkString)
97+ , mkString `deepseq` (bench "readsPrec Haskell lists" $
98+ nf Ops.readInstanceList mkListString)
99+ , benchPureSink "showsPrec pure streams" Ops.showInstance
100+ , mkList `deepseq` (bench "showPrec Haskell lists" $
101+ nf Ops.showInstanceList mkList)
91102 , benchPureSink "foldl'" Ops.pureFoldl'
92103 , benchPureSink "foldable/foldl'" Ops.foldableFoldl'
93104 , benchPureSink "foldable/sum" Ops.foldableSum
benchmark/LinearOps.hs+14−7
@@ -575,16 +575,23 @@ showInstance :: Stream Identity Int -> P.String
575575showInstance src = P.show src
576576
577577{-# INLINE showInstanceList #-}
578−showInstanceList :: Stream Identity Int -> P.String
579−showInstanceList src = P.show (GHC.toList src P.++ [2..value])
578+showInstanceList :: [Int] -> P.String
579+showInstanceList src = P.show src
580580
581581{-# INLINE readInstance #-}
582−readInstance :: Stream Identity Int -> Stream Identity Int
583−readInstance src =
584− let r = P.reads ("fromList [1"
585− P.++ P.concat (P.replicate value ",1") P.++ "]")
582+readInstance :: P.String -> Stream Identity Int
583+readInstance str =
584+ let r = P.reads str
586585 in case r of
587− [(x,"")] -> src S.<> x
586+ [(x,"")] -> x
587+ _ -> P.error "readInstance: no parse"
588+
589+{-# INLINE readInstanceList #-}
590+readInstanceList :: P.String -> [Int]
591+readInstanceList str =
592+ let r = P.reads str
593+ in case r of
594+ [(x,"")] -> x
588595 _ -> P.error "readInstance: no parse"
589596
590597{-# INLINE pureFoldl' #-}
src/Streamly/Array.hsnew file+312
@@ -0,0 +1,312 @@
1+{-# LANGUAGE BangPatterns #-}
2+{-# LANGUAGE CPP #-}
3+{-# LANGUAGE MagicHash #-}
4+{-# LANGUAGE RecordWildCards #-}
5+{-# LANGUAGE UnboxedTuples #-}
6+{-# LANGUAGE ScopedTypeVariables #-}
7+
8+#include "Streams/inline.hs"
9+
10+-- |
11+-- Module : Streamly.Array
12+-- Copyright : (c) 2019 Harendra Kumar
13+--
14+-- License : BSD3
15+-- Maintainer : harendra.kumar@gmail.com
16+-- Stability : experimental
17+-- Portability : GHC
18+--
19+-- Arrays are the computing duals of streams. Streams are good at sequential
20+-- access, immutable transformations of in-transit data whereas arrays are good
21+-- at random access, in-place transformations of buffered data. Unlike streams
22+-- which are potentially infinite, arrays are necessarily /finite/. Arrays can
23+-- be used as an efficient interface between streams and external storage
24+-- systems like memory, files and network. Streams and arrays complete each
25+-- other to provide a general purpose computing system. The design of streamly
26+-- as a general purpose computing framework is centered around these two
27+-- fundamental aspects of computing and storage.
28+--
29+-- Even though the implementation allows for in-place mutation, the current
30+-- implementation of arrays is immutable. Arrays are of fixed size. The size of
31+-- an array is fixed at creation and cannot be changed later without copying.
32+-- Arrays are chunks of memory that hold a sequence of 'Storable' values of a
33+-- given type. They are designed to store serializable data, they cannot store
34+-- non-serializable data like functions, this is reflected by the 'Storable'
35+-- constraint. For efficient buffering of data, arrays use pinned memory and
36+-- therefore can hold arbitrary number of elements with a single movable
37+-- pointer visible to GC, therefore, adding no pressure to GC. Moreover,
38+-- pinned memory allows communication with foreign consumers and producers
39+-- (e.g. file or network IO) without copying the data.
40+--
41+-- By design, there are no transformation operations provided in this module,
42+-- only stream conversion and IO routines are provided. An array is purely a
43+-- data store, therefore, it is not a 'Functor'. Arrays can be operated upon
44+-- /efficiently/ by converting them into a stream, applying the desired
45+-- transformations on the stream and then converting it back to an array.
46+-- 'Foldable' instance is not provided because the implementation would be much
47+-- less efficient compared to folding via streams. 'Semigroup' and 'Monoid'
48+-- instances are deliberately not provided to avoid misuse; concatenating
49+-- arrays using binary operations can be highly inefficient. Instead, use
50+-- 'spliceArrays' to concatenate N arrays at once.
51+--
52+-- To summarize:
53+--
54+-- * Arrays are finite and fixed in size
55+-- * provide /O(1)/ access to elements
56+-- * store only data and not functions
57+-- * provide efficient IO interfacing
58+
59+-- Each array is one pointer visible to the GC. Too many small arrays (e.g.
60+-- single byte) are only as good as holding those elements in a Haskell list.
61+-- However, small arrays can be compacted into large ones to reduce the
62+-- overhead. To hold 32GB memory in 32k sized buffers we need 1 million arrays
63+-- if we use one array for each chunk. This is still significant to add
64+-- pressure to GC. However, we can create arrays of arrays (trees) to scale to
65+-- arbitrarily large amounts of memory but still using small chunks of
66+-- contiguous memory.
67+
68+module Streamly.Array
69+ (
70+ Array
71+
72+ -- * Construction
73+ -- , fromStream
74+ , fromStreamN
75+ -- , fromList
76+ -- , fromListN
77+
78+ -- * Elimination
79+ -- , toList
80+ , toStream
81+
82+ -- * Random Access
83+ , length
84+ , (!!)
85+
86+ -- * Streams of Arrays
87+ -- , defaultChunkSize
88+ -- , fromStreamArraysOf
89+ , flattenArrays
90+ , spliceArrays
91+
92+ -- * IO: Single Array
93+ -- , fromHandleUpto
94+ -- , fromHandleN
95+ -- , fromHandlePosUpto
96+ -- , fromHandlePosN
97+ , toHandle
98+
99+ -- * IO: Stream of Arrays
100+ -- , fromHandleArraysUpto
101+ -- , fromHandleArraysOf
102+ , fromHandleArrays
103+ )
104+where
105+
106+import Control.Monad.IO.Class (MonadIO(..))
107+import Data.Word (Word8)
108+import Foreign.ForeignPtr (withForeignPtr)
109+import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
110+import Foreign.Ptr (minusPtr, plusPtr, castPtr)
111+import Foreign.Storable (Storable(..))
112+import System.IO (Handle, hGetBufSome, hPutBuf)
113+import Prelude hiding (length, null, last, map, (!!))
114+
115+import GHC.ForeignPtr (mallocPlainForeignPtrBytes)
116+import GHC.IO (unsafeDupablePerformIO)
117+
118+import Streamly.Streams.Serial (SerialT)
119+import Streamly.Streams.StreamK.Type (IsStream, mkStream)
120+import Streamly.Array.Types
121+
122+import qualified Streamly.Streams.StreamD as D
123+import qualified Streamly.Streams.Prelude as P
124+import qualified Streamly.Prelude as S
125+
126+-------------------------------------------------------------------------------
127+-- Construction
128+-------------------------------------------------------------------------------
129+
130+-- | Create an 'Array' from the first N elements of a stream. The array is
131+-- allocated to size N, if the stream terminates before N elements then the
132+-- array may hold less than N elements.
133+--
134+-- @since 0.7.0
135+{-# INLINE fromStreamN #-}
136+fromStreamN :: (Monad m, Storable a) => Int -> SerialT m a -> m (Array a)
137+fromStreamN n m = fromStreamDN n $ D.toStreamD m
138+-- fromStreamN n = FL.foldl (FL.toArrayN n)
139+
140+-------------------------------------------------------------------------------
141+-- Elimination
142+-------------------------------------------------------------------------------
143+
144+-- | Convert an 'Array' into a stream.
145+--
146+-- @since 0.7.0
147+{-# INLINABLE toStream #-}
148+toStream :: (Monad m, IsStream t, Storable a) => Array a -> t m a
149+toStream = P.fromArray
150+
151+{-# INLINE null #-}
152+null :: Storable a => Array a -> Bool
153+null arr = length arr == 0
154+
155+{-# INLINE _last #-}
156+_last :: forall a. Storable a => Array a -> Maybe a
157+_last arr = arr !! (length arr - 1)
158+
159+-------------------------------------------------------------------------------
160+-- Random Access
161+-------------------------------------------------------------------------------
162+
163+-- | /O(1)/ Lookup the element at the given index.
164+--
165+-- @since 0.7.0
166+{-# INLINE (!!) #-}
167+(!!) :: Storable a => Array a -> Int -> Maybe a
168+arr !! i =
169+ if i < 0 || i > length arr - 1
170+ then Nothing
171+ else Just $ unsafeIndex arr i
172+
173+-------------------------------------------------------------------------------
174+-- Streams of Arrays
175+-------------------------------------------------------------------------------
176+
177+-- | Convert a stream of Arrays into a stream of their elements.
178+--
179+-- @since 0.7.0
180+{-# INLINE flattenArrays #-}
181+flattenArrays :: (IsStream t, Monad m, Storable a) => t m (Array a) -> t m a
182+flattenArrays m = D.fromStreamD $ D.flattenArrays (D.toStreamD m)
183+
184+{-
185+-- | @fromStreamArraysOf n stream@ groups the input stream into a stream of
186+-- arrays of size n.
187+{-# INLINE fromStreamArraysOf #-}
188+fromStreamArraysOf :: (IsStream t, Monad m, Storable a)
189+ => Int -> t m a -> t m (Array a)
190+fromStreamArraysOf n str =
191+ D.fromStreamD $ D.groupsOf n (fromStreamN n) (D.toStreamD str)
192+ -}
193+
194+-- | Given a stream of arrays, splice them all together to generate a single
195+-- array. The stream must be /finite/.
196+--
197+-- @since 0.7.0
198+{-# INLINABLE spliceArrays #-}
199+spliceArrays :: (Monad m, Storable a) => SerialT m (Array a) -> m (Array a)
200+spliceArrays s = do
201+ buffered <- S.foldr S.cons S.nil s
202+ len <- S.sum (S.map length buffered)
203+
204+ let !arr = unsafeDupablePerformIO $ unsafeNew len
205+ end <- S.foldl' write (aEnd arr) buffered
206+ return $ arr {aEnd = end}
207+
208+ where
209+
210+ write dst Array{..} =
211+ let !dst' = unsafeInlineIO $ withForeignPtr aStart $ \src -> do
212+ let len = aEnd `minusPtr` src
213+ memcpy (castPtr dst) (castPtr src) len
214+ return $ dst `plusPtr` len
215+ in dst'
216+
217+{-
218+-- CAUTION: a very large number (millions) of arrays can degrade performance
219+-- due to GC overhead because we need to buffer the arrays before we flatten
220+-- all the arrays.
221+--
222+-- | Create an 'Array' from a stream. This is useful when we want to create a
223+-- single array from a stream of unknown size. 'fromStreamN' is more efficient
224+-- when the size is already known.
225+--
226+{-# INLINE fromStream #-}
227+fromStream :: (Monad m, Storable a) => SerialT m a -> m (Array a)
228+fromStream m = do
229+ let s = fromStreamArraysOf defaultChunkSize $ D.toStreamD m
230+ buffered = S.foldr S.cons S.nil s
231+ len = S.sum (S.map length buffered)
232+ fromStreamN len $ flattenArrays buffered
233+-}
234+
235+{-
236+-- Move this to FileIO
237+-- XXX we should use overWrite/write
238+-- | Write a stream of arrays to a handle.
239+{-# INLINE toHandleArrays #-}
240+toHandleArrays :: (MonadIO m, Storable a) => Handle -> SerialT m (Array a) -> m ()
241+toHandleArrays h m = S.mapM_ (liftIO . toHandle h) m
242+-}
243+
244+-------------------------------------------------------------------------------
245+-- IO (Input)
246+-------------------------------------------------------------------------------
247+
248+-- | Read a 'ByteArray' from a file handle. If no data is available on the
249+-- handle it blocks until some data becomes available. If data is available
250+-- then it immediately returns that data without blocking. It reads a maximum
251+-- of up to the size requested.
252+{-# INLINABLE fromHandleUpto #-}
253+fromHandleUpto :: Int -> Handle -> IO (Array Word8)
254+fromHandleUpto size h = do
255+ ptr <- mallocPlainForeignPtrBytes size
256+ withForeignPtr ptr $ \p -> do
257+ n <- hGetBufSome h p size
258+ let v = Array
259+ { aStart = ptr
260+ , aEnd = p `plusPtr` n
261+ , aBound = p `plusPtr` size
262+ }
263+ -- XXX shrink only if the diff is significant
264+ shrinkToFit v
265+
266+-------------------------------------------------------------------------------
267+-- IO (output)
268+-------------------------------------------------------------------------------
269+
270+-- | Write an Array to a file handle.
271+--
272+-- @since 0.7.0
273+{-# INLINABLE toHandle #-}
274+toHandle :: Storable a => Handle -> Array a -> IO ()
275+toHandle _ arr | null arr = return ()
276+toHandle h Array{..} = withForeignPtr aStart $ \p -> hPutBuf h p aLen
277+ where
278+ aLen =
279+ let p = unsafeForeignPtrToPtr aStart
280+ in aEnd `minusPtr` p
281+
282+-------------------------------------------------------------------------------
283+-- Stream of Arrays IO
284+-------------------------------------------------------------------------------
285+
286+-- | @fromHandleArraysUpto size h@ reads a stream of arrays from file handle @h@.
287+-- The maximum size of a single array is limited to @size@.
288+-- 'fromHandleArraysUpto' ignores the prevailing 'TextEncoding' and 'NewlineMode'
289+-- on the 'Handle'.
290+{-# INLINABLE fromHandleArraysUpto #-}
291+fromHandleArraysUpto :: (IsStream t, MonadIO m)
292+ => Int -> Handle -> t m (Array Word8)
293+fromHandleArraysUpto size h = go
294+ where
295+ -- XXX use cons/nil instead
296+ go = mkStream $ \_ yld sng _ -> do
297+ vec <- liftIO $ fromHandleUpto size h
298+ if length vec < size
299+ then sng vec
300+ else yld vec go
301+
302+-- XXX read 'Array a' instead of Word8
303+--
304+-- | @fromHandleArrays h@ reads a stream of arrays from file handle @h@.
305+-- The maximum size of a single array is limited to @defaultChunkSize@.
306+-- 'fromHandleArrays' ignores the prevailing 'TextEncoding' and 'NewlineMode'
307+-- on the 'Handle'.
308+--
309+-- @since 0.7.0
310+{-# INLINE fromHandleArrays #-}
311+fromHandleArrays :: (IsStream t, MonadIO m) => Handle -> t m (Array Word8)
312+fromHandleArrays = fromHandleArraysUpto defaultChunkSize
src/Streamly/Array/Types.hsnew file+470
@@ -0,0 +1,470 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE BangPatterns #-}
3+{-# LANGUAGE ExistentialQuantification #-}
4+{-# LANGUAGE MagicHash #-}
5+{-# LANGUAGE RecordWildCards #-}
6+{-# LANGUAGE ScopedTypeVariables #-}
7+{-# LANGUAGE TypeFamilies #-}
8+{-# LANGUAGE UnboxedTuples #-}
9+
10+#include "inline.hs"
11+
12+-- |
13+-- Module : Streamly.Array
14+-- Copyright : (c) 2019 Composewell Technologies
15+--
16+-- License : BSD3
17+-- Maintainer : harendra.kumar@gmail.com
18+-- Stability : experimental
19+-- Portability : GHC
20+--
21+module Streamly.Array.Types
22+ (
23+ Array (..)
24+
25+ -- * Construction
26+ , unsafeInlineIO
27+ , withNewArray
28+ , unsafeNew
29+ , unsafeAppend
30+ , shrinkToFit
31+ , memcpy
32+
33+ , fromCStringAddrUnsafe
34+ , fromList
35+ , fromListN
36+ , fromStreamDN
37+
38+ -- * Elimination
39+ , unsafeIndex
40+ , length
41+ , foldl'
42+ , foldr
43+
44+ , toStreamD
45+ , toList
46+
47+ -- * Utilities
48+ , defaultChunkSize
49+ )
50+where
51+
52+import Control.Exception (assert)
53+import Control.DeepSeq (NFData(..))
54+import Control.Monad (when)
55+import Data.Functor.Identity (runIdentity)
56+import Data.Word (Word8)
57+import Foreign.C.String (CString)
58+import Foreign.C.Types (CSize(..), CInt(..))
59+import Foreign.ForeignPtr
60+ (ForeignPtr, withForeignPtr, touchForeignPtr)
61+import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
62+import Foreign.Ptr (plusPtr, minusPtr, castPtr)
63+import Foreign.Storable (Storable(..))
64+import Prelude hiding (length, foldr)
65+import Text.Read (readPrec, readListPrec, readListPrecDefault)
66+
67+import GHC.Base (Addr#, realWorld#)
68+import GHC.Exts (IsList, IsString(..))
69+import GHC.ForeignPtr (mallocPlainForeignPtrAlignedBytes, newForeignPtr_)
70+import GHC.IO (IO(IO), unsafeDupablePerformIO)
71+import GHC.Ptr (Ptr(..))
72+
73+import qualified Prelude
74+import qualified Streamly.Streams.StreamD.Type as D
75+import qualified GHC.Exts as Exts
76+
77+-------------------------------------------------------------------------------
78+-- Design Notes
79+-------------------------------------------------------------------------------
80+
81+-- There are two goals that we want to fulfill with arrays. One, holding large
82+-- amounts of data in non-GC memory and the ability to pass this data to and
83+-- from the operating system without an extra copy overhead. Two, allow random
84+-- access to elements based on their indices. The first one falls in the
85+-- category of storage buffers while the second one falls in the category of
86+-- maps/multisets/hashmaps.
87+--
88+-- For the first requirement we use an array of Storables and store it in a
89+-- ForeignPtr. We can have both immutable and mutable variants of this array
90+-- using wrappers over the same underlying type.
91+--
92+-- For the second requirement, we need a separate type for arrays of
93+-- polymorphic values, for example vectors of handler functions, lookup tables.
94+-- We can call this type a "vector" in contrast to arrays. It should not
95+-- require Storable instance for the type. In that case we need to use an
96+-- Array# instead of a ForeignPtr. This type of array would not reduce the GC
97+-- overhead much because each element of the array still needs to be scanned by
98+-- the GC. However, it can store polymorphic elements and allow random access
99+-- to those. But in most cases random access means storage, and it means we
100+-- need to avoid GC scanning except in cases of trivially small storage. One
101+-- way to achieve that would be to put the array in a Compact region. However,
102+-- if and when we mutate this, we will have to use a manual GC copying out to
103+-- another CR and freeing the old one.
104+
105+-------------------------------------------------------------------------------
106+-- Array Data Type
107+-------------------------------------------------------------------------------
108+
109+-- We require that an array stores only Storable. Arrays are used for buffering
110+-- data while streams are used for processing. If you want something to be
111+-- buffered it better be Storable so that we can store it in non-GC memory.
112+--
113+-- We can use a "Storable" constraint in the Array type and the Constraint can
114+-- be automatically provided to a function that pattern matches on the Array
115+-- type. However, it has huge performance cost, so we do not use it.
116+--
117+-- XXX Another way to not require the Storable constraint in array operations
118+-- is to store the elemSize in the array at construction and use that instead
119+-- of using sizeOf. Need to charaterize perf cost of this.
120+--
121+-- XXX add reverse flag to reverse the contents without physically reversing.
122+data Array a =
123+#ifdef DEVBUILD
124+ Storable a =>
125+#endif
126+ Array
127+ { aStart :: {-# UNPACK #-} !(ForeignPtr a) -- first address
128+ , aEnd :: {-# UNPACK #-} !(Ptr a) -- first unused address
129+ , aBound :: {-# UNPACK #-} !(Ptr a) -- first address beyond allocated memory
130+ }
131+
132+-------------------------------------------------------------------------------
133+-- Utility functions
134+-------------------------------------------------------------------------------
135+
136+foreign import ccall unsafe "string.h memcpy" c_memcpy
137+ :: Ptr Word8 -> Ptr Word8 -> CSize -> IO (Ptr Word8)
138+
139+foreign import ccall unsafe "string.h strlen" c_strlen
140+ :: CString -> IO CSize
141+
142+-- XXX we are converting Int to CSize
143+memcpy :: Ptr Word8 -> Ptr Word8 -> Int -> IO ()
144+memcpy dst src len = c_memcpy dst src (fromIntegral len) >> return ()
145+
146+foreign import ccall unsafe "string.h memcmp" c_memcmp
147+ :: Ptr Word8 -> Ptr Word8 -> CSize -> IO CInt
148+
149+-- XXX we are converting Int to CSize
150+-- return True if the memory locations have identical contents
151+{-# INLINE memcmp #-}
152+memcmp :: Ptr Word8 -> Ptr Word8 -> Int -> IO Bool
153+memcmp p1 p2 len = do
154+ r <- c_memcmp p1 p2 (fromIntegral len)
155+ return $ r == 0
156+
157+{-# INLINE unsafeInlineIO #-}
158+unsafeInlineIO :: IO a -> a
159+unsafeInlineIO (IO m) = case m realWorld# of (# _, r #) -> r
160+
161+-------------------------------------------------------------------------------
162+-- Construction
163+-------------------------------------------------------------------------------
164+
165+-- | Allocate an array that can hold 'count' items. The memory of the array is
166+-- uninitialized.
167+--
168+-- Note that this is internal routine, the reference to this array cannot be
169+-- given out until the array has been written to and frozen.
170+{-# INLINE unsafeNew #-}
171+unsafeNew :: forall a. Storable a => Int -> IO (Array a)
172+unsafeNew count = do
173+ let size = count * sizeOf (undefined :: a)
174+ fptr <- mallocPlainForeignPtrAlignedBytes size (alignment (undefined :: a))
175+ let p = unsafeForeignPtrToPtr fptr
176+ return $ Array
177+ { aStart = fptr
178+ , aEnd = p
179+ , aBound = p `plusPtr` size
180+ }
181+
182+-- | Allocate an Array of the given size and run an IO action passing the array
183+-- start pointer.
184+{-# INLINE withNewArray #-}
185+withNewArray :: forall a. Storable a => Int -> (Ptr a -> IO ()) -> IO (Array a)
186+withNewArray count f = do
187+ arr <- unsafeNew count
188+ withForeignPtr (aStart arr) $ \p -> f p >> return arr
189+
190+-- XXX grow the array when we are beyond bound.
191+--
192+-- Internal routine for when the array is being created. Appends one item at
193+-- the end of the array. Useful when sequentially writing a stream to the
194+-- array. DOES NOT CHECK THE ARRAY BOUNDS.
195+{-# INLINE unsafeAppend #-}
196+unsafeAppend :: forall a. Storable a => Array a -> a -> IO (Array a)
197+unsafeAppend arr@Array{..} x = do
198+ when (aEnd == aBound) $
199+ error "BUG: unsafeAppend: writing beyond array bounds"
200+ poke aEnd x
201+ touchForeignPtr aStart
202+ return $ arr {aEnd = aEnd `plusPtr` (sizeOf (undefined :: a))}
203+
204+-- | Remove the free space from an Array.
205+shrinkToFit :: forall a. Storable a => Array a -> IO (Array a)
206+shrinkToFit arr@Array{..} = do
207+ assert (aEnd <= aBound) (return ())
208+ if aEnd /= aBound
209+ then do
210+ let oldStart = unsafeForeignPtrToPtr aStart
211+ let size = aEnd `minusPtr` oldStart
212+ newPtr <- mallocPlainForeignPtrAlignedBytes
213+ size (alignment (undefined :: a))
214+ withForeignPtr newPtr $ \pNew -> do
215+ memcpy (castPtr pNew) (castPtr oldStart) size
216+ touchForeignPtr aStart
217+ let end = pNew `plusPtr` size
218+ return $ Array
219+ { aStart = newPtr
220+ , aEnd = end
221+ , aBound = end
222+ }
223+ else return arr
224+
225+-- Note that the address must be a read-only address (meant to be used for
226+-- read-only string literals) because we are sharing it, any modification to
227+-- the original address would change our array. That's why this function is
228+-- unsafe.
229+{-# INLINE fromCStringAddrUnsafe #-}
230+fromCStringAddrUnsafe :: Addr# -> IO (Array Word8)
231+fromCStringAddrUnsafe addr# = do
232+ ptr <- newForeignPtr_ (castPtr cstr)
233+ len <- c_strlen cstr
234+ let n = fromIntegral len
235+ let p = unsafeForeignPtrToPtr ptr
236+ let end = p `plusPtr` n
237+ return $ Array
238+ { aStart = ptr
239+ , aEnd = end
240+ , aBound = end
241+ }
242+ where
243+ cstr :: CString
244+ cstr = Ptr addr#
245+
246+-------------------------------------------------------------------------------
247+-- Elimination
248+-------------------------------------------------------------------------------
249+
250+-- | Return element at the specified index without checking the bounds.
251+{-# INLINE_NORMAL unsafeIndex #-}
252+unsafeIndex :: forall a. Storable a => Array a -> Int -> a
253+unsafeIndex Array {..} i =
254+ let !r = unsafeInlineIO $
255+ withForeignPtr aStart $ \p -> do
256+ let elemSize = sizeOf (undefined :: a)
257+ elemOff = p `plusPtr` (elemSize * i)
258+ assert (i >= 0 && elemOff `plusPtr` elemSize <= aEnd)
259+ (return ())
260+ peek elemOff
261+ in r
262+
263+-- | /O(1)/ Get the length of the array.
264+--
265+-- @since 0.7.0
266+{-# INLINE length #-}
267+length :: forall a. Storable a => Array a -> Int
268+length Array{..} =
269+ let p = unsafeForeignPtrToPtr aStart
270+ aLen = aEnd `minusPtr` p
271+ in assert (aLen >= 0) (aLen `div` sizeOf (undefined :: a))
272+
273+{-# INLINE_NORMAL toStreamD #-}
274+toStreamD :: forall m a. (Monad m, Storable a) => Array a -> D.Stream m a
275+toStreamD Array{..} =
276+ let p = unsafeForeignPtrToPtr aStart
277+ in D.Stream step p
278+
279+ where
280+
281+ {-# INLINE_LATE step #-}
282+ step _ p | p == aEnd = return D.Stop
283+ step _ p = do
284+ let !x = unsafeInlineIO $ do
285+ r <- peek p
286+ touchForeignPtr aStart
287+ return r
288+ return $ D.Yield x (p `plusPtr` (sizeOf (undefined :: a)))
289+
290+{-# INLINE_NORMAL foldl' #-}
291+foldl' :: forall a b. Storable a => (b -> a -> b) -> b -> Array a -> b
292+foldl' f z arr = runIdentity $ D.foldl' f z $ toStreamD arr
293+
294+{-# INLINE_NORMAL foldr #-}
295+foldr :: Storable a => (a -> b -> b) -> b -> Array a -> b
296+foldr f z arr = runIdentity $ D.foldr f z $ toStreamD arr
297+
298+-------------------------------------------------------------------------------
299+-- Instances
300+-------------------------------------------------------------------------------
301+
302+{-# INLINE_NORMAL fromStreamDN #-}
303+fromStreamDN :: forall m a. (Monad m, Storable a)
304+ => Int -> D.Stream m a -> m (Array a)
305+fromStreamDN limit str = do
306+ let !arr = unsafeDupablePerformIO $ unsafeNew limit
307+ end <- D.foldl' write (aEnd arr) $ D.take limit str
308+ return $ arr {aEnd = end}
309+
310+ where
311+
312+ write ptr x =
313+ let !_ = unsafeInlineIO $ poke ptr x
314+ in ptr `plusPtr` sizeOf (undefined :: a)
315+
316+-- | Convert an 'Array' into a list.
317+--
318+-- @since 0.7.0
319+{-# INLINABLE toList #-}
320+toList :: Storable a => Array a -> [a]
321+toList = foldr (:) []
322+
323+instance (Show a, Storable a) => Show (Array a) where
324+ {-# INLINE showsPrec #-}
325+ showsPrec _ = shows . toList
326+
327+-- | Create an 'Array' from the first N elements of a list. The array is
328+-- allocated to size N, if the list terminates before N elements then the
329+-- array may hold less than N elements.
330+--
331+-- @since 0.7.0
332+{-# INLINABLE fromListN #-}
333+fromListN :: Storable a => Int -> [a] -> Array a
334+fromListN n xs = runIdentity $ fromStreamDN n $ D.fromList xs
335+
336+-- | Create an 'Array' from a list. The list must be of finite size.
337+--
338+-- @since 0.7.0
339+{-# INLINABLE fromList #-}
340+fromList :: Storable a => [a] -> Array a
341+fromList xs = fromListN (Prelude.length xs) xs
342+
343+instance (Storable a, Read a, Show a) => Read (Array a) where
344+ {-# INLINE readPrec #-}
345+ readPrec = do
346+ xs <- readPrec
347+ return (fromList xs)
348+ readListPrec = readListPrecDefault
349+
350+instance (a ~ Char) => IsString (Array a) where
351+ {-# INLINE fromString #-}
352+ fromString = fromList
353+
354+-- GHC versions 8.0 and below cannot derive IsList
355+instance Storable a => IsList (Array a) where
356+ type (Item (Array a)) = a
357+ {-# INLINE fromList #-}
358+ fromList = fromList
359+ {-# INLINE fromListN #-}
360+ fromListN = fromListN
361+ {-# INLINE toList #-}
362+ toList = toList
363+
364+{-# INLINE arrcmp #-}
365+arrcmp :: Array a -> Array a -> Bool
366+arrcmp arr1 arr2 =
367+ let !res = unsafeInlineIO $ do
368+ let ptr1 = unsafeForeignPtrToPtr $ aStart arr1
369+ let ptr2 = unsafeForeignPtrToPtr $ aStart arr2
370+ let len1 = aEnd arr1 `minusPtr` ptr1
371+ let len2 = aEnd arr2 `minusPtr` ptr2
372+
373+ if len1 == len2
374+ then do
375+ r <- memcmp (castPtr ptr1) (castPtr ptr2) len1
376+ touchForeignPtr $ aStart arr1
377+ touchForeignPtr $ aStart arr2
378+ return r
379+ else return False
380+ in res
381+
382+-- XXX we are assuming that Storable equality means element equality. This may
383+-- or may not be correct? arrcmp is 40% faster compared to stream equality.
384+instance (Storable a, Eq a) => Eq (Array a) where
385+ {-# INLINE (==) #-}
386+ (==) = arrcmp
387+ -- arr1 == arr2 = runIdentity $ D.eqBy (==) (toStreamD arr1) (toStreamD arr2)
388+
389+instance (Storable a, NFData a) => NFData (Array a) where
390+ {-# INLINE rnf #-}
391+ rnf = foldl' (\_ x -> rnf x) ()
392+
393+instance (Storable a, Ord a) => Ord (Array a) where
394+ {-# INLINE compare #-}
395+ compare arr1 arr2 = runIdentity $
396+ D.cmpBy compare (toStreamD arr1) (toStreamD arr2)
397+
398+ -- Default definitions defined in base do not have an INLINE on them, so we
399+ -- replicate them here with an INLINE.
400+ {-# INLINE (<) #-}
401+ x < y = case compare x y of { LT -> True; _ -> False }
402+
403+ {-# INLINE (<=) #-}
404+ x <= y = case compare x y of { GT -> False; _ -> True }
405+
406+ {-# INLINE (>) #-}
407+ x > y = case compare x y of { GT -> True; _ -> False }
408+
409+ {-# INLINE (>=) #-}
410+ x >= y = case compare x y of { LT -> False; _ -> True }
411+
412+ -- These two default methods use '<=' rather than 'compare'
413+ -- because the latter is often more expensive
414+ {-# INLINE max #-}
415+ max x y = if x <= y then y else x
416+
417+ {-# INLINE min #-}
418+ min x y = if x <= y then x else y
419+
420+#ifdef DEVBUILD
421+-- Definitions using the Storable constraint from the Array type. These are to
422+-- make the Foldable instance possible though it is much slower (7x slower).
423+--
424+{-# INLINE_NORMAL toStreamD_ #-}
425+toStreamD_ :: forall m a. Monad m => Int -> Array a -> D.Stream m a
426+toStreamD_ size Array{..} =
427+ let p = unsafeForeignPtrToPtr aStart
428+ in D.Stream step p
429+
430+ where
431+
432+ {-# INLINE_LATE step #-}
433+ step _ p | p == aEnd = return D.Stop
434+ step _ p = do
435+ let !x = unsafeInlineIO $ do
436+ r <- peek p
437+ touchForeignPtr aStart
438+ return r
439+ return $ D.Yield x (p `plusPtr` size)
440+
441+{-# INLINE_NORMAL _foldr #-}
442+_foldr :: forall a b. (a -> b -> b) -> b -> Array a -> b
443+_foldr f z arr@Array {..} =
444+ let !n = sizeOf (undefined :: a)
445+ in runIdentity $ D.foldr f z $ toStreamD_ n arr
446+
447+-- | Note that the 'Foldable' instance is 7x slower than the direct
448+-- operations.
449+instance Foldable Array where
450+ foldr = _foldr
451+#endif
452+
453+-------------------------------------------------------------------------------
454+-- Semigroup and Monoid
455+-------------------------------------------------------------------------------
456+
457+-------------------------------------------------------------------------------
458+-- IO
459+-------------------------------------------------------------------------------
460+
461+-- | GHC memory management allocation header overhead
462+allocOverhead :: Int
463+allocOverhead = 2 * sizeOf (undefined :: Int)
464+
465+-- | Default maximum buffer size in bytes, for reading from and writing to IO
466+-- devices, the value is 32KB minus GHC allocation overhead, which is a few
467+-- bytes, so that the actual allocation is 32KB.
468+defaultChunkSize :: Int
469+defaultChunkSize = 32 * k - allocOverhead
470+ where k = 1024
src/Streamly/Prelude.hs+19−9
@@ -102,6 +102,7 @@ module Streamly.Prelude
102102 -- these can be expressed in terms of primitives.
103103 , P.fromList
104104 , fromListM
105+ -- , P.fromArray
105106 , K.fromFoldable
106107 , fromFoldableM
107108
@@ -176,6 +177,7 @@ module Streamly.Prelude
176177 , foldrM
177178 , foldrS
178179 , foldrT
180+ , foldr
179181
180182 , foldl'
181183 , foldl1'
@@ -211,6 +213,7 @@ module Streamly.Prelude
211213 -- -- | Convert or divert a stream into an output structure, container or
212214 -- sink.
213215 , toList
216+ -- , toArrayN
214217 , toHandle
215218
216219 -- ** Partial Folds
@@ -456,16 +459,16 @@ module Streamly.Prelude
456459 , K.once
457460 , each
458461 , scanx
459− , foldr
460− , foldr1
461462 , foldx
462463 , foldxM
464+ , foldr1
463465 )
464466where
465467
466468import Control.Monad.IO.Class (MonadIO(..))
467469import Control.Monad.Trans (MonadTrans(..))
468470import Data.Maybe (isJust, fromJust)
471+import Foreign.Storable (Storable)
469472import Prelude
470473 hiding (filter, drop, dropWhile, take, takeWhile, zipWith, foldr,
471474 foldl, mapM, mapM_, sequence, all, any, sum, product, elem,
@@ -490,6 +493,7 @@ import qualified Streamly.Streams.Prelude as P
490493import qualified Streamly.Streams.StreamK as K
491494import qualified Streamly.Streams.StreamD as D
492495import qualified Streamly.Streams.Zip as Z
496+import qualified Streamly.Array.Types as A
493497
494498#ifdef USE_STREAMK_ONLY
495499import qualified Streamly.Streams.StreamK as S
@@ -994,15 +998,17 @@ foldrT :: (IsStream t, Monad m, Monad (s m), MonadTrans s)
994998 => (a -> s m b -> s m b) -> s m b -> t m a -> s m b
995999foldrT f z s = S.foldrT f z (toStreamS s)
9961000
997−-- | Note that with this signature we cannot implement a lazy foldr when the
998−-- monad @m@ is strict. In that case it would be strict in its accumulator and
999−-- therefore would necessarily consume all its input. For this reason we have
1000−-- deprecated this API. Though this can be useful for lazy monads, but we can
1001−-- always achieve the same thing using foldrM directly.
1001+-- | Right fold for lazy monads or pure streams.
1002+--
1003+-- Please avoid using this routine in strict monads like IO unless you need a
1004+-- strict right fold. This is provided only for use in lazy monads (e.g.
1005+-- Identity) or pure streams. Note that with this signature it is not possible
1006+-- to implement a lazy foldr when the monad @m@ is strict. In that case it
1007+-- would be strict in its accumulator and therefore would necessarily consume
1008+-- all its input.
10021009--
10031010-- @since 0.1.0
10041011{-# INLINE foldr #-}
1005−{-# DEPRECATED foldr "This is unnecessarily strict for strict monads. Use foldrM instead." #-}
10061012foldr :: Monad m => (a -> b -> b) -> b -> SerialT m a -> m b
10071013foldr = P.foldr
10081014
@@ -1014,7 +1020,7 @@ foldr = P.foldr
10141020--
10151021-- @since 0.5.0
10161022{-# INLINE foldr1 #-}
1017−{-# DEPRECATED foldr1 "This is unnecessarily strict for strict monads. Use foldrM instead." #-}
1023+{-# DEPRECATED foldr1 "Use foldrM instead." #-}
10181024foldr1 :: Monad m => (a -> a -> a) -> SerialT m a -> m (Maybe a)
10191025foldr1 f m = S.foldr1 f (toStreamS m)
10201026
@@ -1481,6 +1487,10 @@ mapM_ f m = S.mapM_ f $ toStreamS m
14811487toList :: Monad m => SerialT m a -> m [a]
14821488toList = P.toList
14831489
1490+{-# INLINE _toArrayN #-}
1491+_toArrayN :: (Monad m, Storable a) => Int -> SerialT m a -> m (A.Array a)
1492+_toArrayN = P.toArrayN
1493+
14841494-- |
14851495-- @
14861496-- toRevList = S.foldl' (flip (:)) []
src/Streamly/Streams/Instances.hs+3
@@ -105,6 +105,9 @@ instance NFData1 (STREAM Identity) where { \
105105-- Foldable
106106-------------------------------------------------------------------------------
107107
108+-- XXX the foldable instance seems to be quit slow. We can try writing
109+-- custom implementations of foldr and foldl'. If nothing works we can also try
110+-- writing a Foldable for Identity monad rather than for "Foldable m".
108111#define FOLDABLE_INSTANCE(STREAM) \
109112instance (Foldable m, Monad m) => Foldable (STREAM m) where { \
110113 {-# INLINE foldMap #-}; \
src/Streamly/Streams/Prelude.hs+18
@@ -27,7 +27,9 @@ module Streamly.Streams.Prelude
2727
2828 -- * Conversion operations
2929 , fromList
30+ , fromArray
3031 , toList
32+ , toArrayN
3133
3234 -- * Fold operations
3335 , foldrM
@@ -59,6 +61,7 @@ module Streamly.Streams.Prelude
5961where
6062
6163import Control.Monad.Trans (MonadTrans(..))
64+import Foreign.Storable (Storable)
6265import Prelude hiding (foldr)
6366import qualified Prelude
6467
@@ -71,6 +74,7 @@ import qualified Streamly.Streams.StreamD as S
7174import Streamly.Streams.StreamK (IsStream(..))
7275import qualified Streamly.Streams.StreamK as K
7376import qualified Streamly.Streams.StreamD as D
77+import qualified Streamly.Array.Types as A
7478
7579------------------------------------------------------------------------------
7680-- Conversion to and from direct style stream
@@ -114,6 +118,16 @@ fromList = fromStreamS . S.fromList
114118{-# RULES "fromList fallback to StreamK" [1]
115119 forall a. S.toStreamK (S.fromList a) = K.fromFoldable a #-}
116120
121+-- XXX add fallback to StreamK rule
122+--
123+-- | Construct a stream from an 'Array'.
124+--
125+{-# INLINE_EARLY fromArray #-}
126+fromArray :: (IsStream t, Monad m, Storable a) => A.Array a -> t m a
127+fromArray = D.fromStreamD . D.fromArray
128+-- {-# RULES "fromArray fallback to StreamK" [1]
129+-- forall a. S.toStreamK (S.fromArray a) = K.fromArray a #-}
130+
117131-- | Convert a stream into a list in the underlying monad.
118132--
119133-- @since 0.1.0
@@ -121,6 +135,10 @@ fromList = fromStreamS . S.fromList
121135toList :: (Monad m, IsStream t) => t m a -> m [a]
122136toList m = S.toList $ toStreamS m
123137
138+{-# INLINE toArrayN #-}
139+toArrayN :: (Monad m, IsStream t, Storable a) => Int -> t m a -> m (A.Array a)
140+toArrayN n m = D.toArrayN n $ D.toStreamD m
141+
124142------------------------------------------------------------------------------
125143-- Folds
126144------------------------------------------------------------------------------
src/Streamly/Streams/StreamD.hs+66−102
@@ -83,6 +83,7 @@ module Streamly.Streams.StreamD
8383 , yieldM
8484 , fromList
8585 , fromListM
86+ , fromArray
8687 , fromStreamK
8788 , fromStreamD
8889
@@ -126,6 +127,7 @@ module Streamly.Streams.StreamD
126127 -- ** Flattening nested streams
127128 , concatMapM
128129 , concatMap
130+ , flattenArrays
129131
130132 -- ** Substreams
131133 , isPrefixOf
@@ -141,6 +143,7 @@ module Streamly.Streams.StreamD
141143 , toRevList
142144 , toStreamK
143145 , toStreamD
146+ , toArrayN
144147
145148 -- * Transformation
146149 -- ** By folding (scans)
@@ -213,6 +216,10 @@ where
213216
214217import Control.Monad.Trans (MonadTrans(lift))
215218import Data.Maybe (fromJust, isJust)
219+import Foreign.ForeignPtr (ForeignPtr, touchForeignPtr)
220+import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
221+import Foreign.Ptr (Ptr, plusPtr)
222+import Foreign.Storable (Storable(..))
216223import GHC.Types (SPEC(..))
217224import Prelude
218225 hiding (map, mapM, mapM_, repeat, foldr, last, take, filter,
@@ -221,10 +228,13 @@ import Prelude
221228 (!!), scanl, scanl1, concatMap, replicate, enumFromTo, concat,
222229 reverse)
223230
231+import Streamly.Array.Types
232+ (Array(..), Array, unsafeInlineIO)
224233import Streamly.SVar (MonadAsync, defState, adaptState)
225234
226235import Streamly.Streams.StreamD.Type
227236import qualified Streamly.Streams.StreamK as K
237+import qualified Streamly.Array.Types as A
228238
229239------------------------------------------------------------------------------
230240-- Construction
@@ -520,14 +530,9 @@ fromListM = Stream step
520530 step _ (m:ms) = m >>= \x -> return $ Yield x ms
521531 step _ [] = return Stop
522532
523−-- | Convert a list of pure values to a 'Stream'
524−{-# INLINE_LATE fromList #-}
525−fromList :: Monad m => [a] -> Stream m a
526−fromList = Stream step
527− where
528− {-# INLINE_LATE step #-}
529− step _ (x:xs) = return $ Yield x xs
530− step _ [] = return Stop
533+{-# INLINE_NORMAL fromArray #-}
534+fromArray :: (Monad m, Storable a) => Array a -> Stream m a
535+fromArray = A.toStreamD
531536
532537{-# INLINE_LATE fromStreamK #-}
533538fromStreamK :: Monad m => K.Stream m a -> Stream m a
@@ -584,20 +589,6 @@ foldlx' :: Monad m => (x -> a -> x) -> x -> (x -> b) -> Stream m a -> m b
584589foldlx' fstep begin done m =
585590 foldlMx' (\b a -> return (fstep b a)) (return begin) (return . done) m
586591
587−-- XXX implement in terms of foldlMx'
588−{-# INLINE_NORMAL foldlM' #-}
589−foldlM' :: Monad m => (b -> a -> m b) -> b -> Stream m a -> m b
590−foldlM' fstep begin (Stream step state) = go SPEC begin state
591− where
592− go !_ acc st = acc `seq` do
593− r <- step defState st
594− case r of
595− Yield x s -> do
596− acc' <- fstep acc x
597− go SPEC acc' s
598− Skip s -> go SPEC acc s
599− Stop -> return acc
600−
601592{-# INLINE_NORMAL foldlT #-}
602593foldlT :: (Monad m, Monad (s m), MonadTrans s)
603594 => (s m b -> a -> s m b) -> s m b -> Stream m a -> s m b
@@ -632,10 +623,6 @@ foldlS fstep begin (Stream step state) = Stream step' (Left (state, begin))
632623 Skip s -> Skip (Right (Stream stp s))
633624 Stop -> Stop
634625
635−{-# INLINE foldl' #-}
636−foldl' :: Monad m => (b -> a -> b) -> b -> Stream m a -> m b
637−foldl' fstep = foldlM' (\b a -> return (fstep b a))
638−
639626------------------------------------------------------------------------------
640627-- Specialized Folds
641628------------------------------------------------------------------------------
@@ -863,6 +850,11 @@ reverse m = Stream step Nothing
863850 step _ (Just (x:xs)) = return $ Yield x (Just xs)
864851 step _ (Just []) = return Stop
865852
853+{-# INLINE_NORMAL toArrayN #-}
854+toArrayN :: forall m a. (Monad m, Storable a)
855+ => Int -> Stream m a -> m (Array a)
856+toArrayN = A.fromStreamDN
857+
866858------------------------------------------------------------------------------
867859-- concatMap
868860------------------------------------------------------------------------------
@@ -881,11 +873,15 @@ concatMapM f (Stream step state) = Stream step' (Left state)
881873 Skip s -> return $ Skip (Left s)
882874 Stop -> return Stop
883875
876+ -- XXX flattenArrays is 5x faster than "concatMap fromArray". if somehow we
877+ -- can get inner_step to inline and fuse here we can perhaps get the same
878+ -- performance using "concatMap fromArray".
879+ --
884880 -- XXX using the pattern synonym Stream causes a major performance issue
885881 -- here even if the synonym does not include a adaptState call. Need to
886882 -- find out why. Is that something to be fixed in GHC?
887− step' _ (Right (UnStream inner_step inner_st, st)) = do
888− r <- inner_step defState inner_st
883+ step' gst (Right (UnStream inner_step inner_st, st)) = do
884+ r <- inner_step (adaptState gst) inner_st
889885 case r of
890886 Yield b inner_s ->
891887 return $ Yield b (Right (Stream inner_step inner_s, st))
@@ -893,10 +889,52 @@ concatMapM f (Stream step state) = Stream step' (Left state)
893889 return $ Skip (Right (Stream inner_step inner_s, st))
894890 Stop -> return $ Skip (Left st)
895891
892+-- XXX concatMap does not seem to have the best possible performance so we have
893+-- a custom way to concat arrays.
894+data CAState s a = CAParent s | CANested s (ForeignPtr a) (Ptr a) (Ptr a)
895+
896+{-# INLINE_NORMAL flattenArrays #-}
897+flattenArrays :: forall m a. (Monad m, Storable a)
898+ => Stream m (Array a) -> Stream m a
899+flattenArrays (Stream step state) = Stream step' (CAParent state)
900+
901+ where
902+
903+ {-# INLINE_LATE step' #-}
904+ step' gst (CAParent st) = do
905+ r <- step (adaptState gst) st
906+ return $ case r of
907+ Yield Array{..} s ->
908+ let p = unsafeForeignPtrToPtr aStart
909+ in Skip (CANested s aStart p aEnd)
910+ Skip s -> Skip (CAParent s)
911+ Stop -> Stop
912+
913+ step' _ (CANested st _ p end) | p == end =
914+ return $ Skip $ CAParent st
915+
916+ step' _ (CANested st startf p end) = do
917+ let !x = unsafeInlineIO $ do
918+ r <- peek p
919+ touchForeignPtr startf
920+ return r
921+ return $ Yield x (CANested st startf
922+ (p `plusPtr` (sizeOf (undefined :: a))) end)
923+
896924{-# INLINE concatMap #-}
897925concatMap :: Monad m => (a -> Stream m b) -> Stream m a -> Stream m b
898926concatMap f = concatMapM (return . f)
899927
928+-- XXX The idea behind this rule is to rewrite any calls to "concatMap
929+-- fromArray" automatically to flattenArrays which is much faster. However, we
930+-- need an INLINE_EARLY on concatMap for this rule to fire. But if we use
931+-- INLINE_EARLY on concatMap or fromArray then direct uses of
932+-- "concatMap fromArray" (without the RULE) become much slower, this means
933+-- "concatMap f" in general would become slower. Need to find a solution to
934+-- this.
935+--
936+-- {-# RULES "concatMap fromArray" concatMap fromArray = flattenArrays #-}
937+
900938------------------------------------------------------------------------------
901939-- Substreams
902940------------------------------------------------------------------------------
@@ -1204,19 +1242,6 @@ scanl1' f = scanl1M' (\x y -> return (f x y))
12041242-- Filtering
12051243-------------------------------------------------------------------------------
12061244
1207−{-# INLINE_NORMAL take #-}
1208−take :: Monad m => Int -> Stream m a -> Stream m a
1209−take n (Stream step state) = n `seq` Stream step' (state, 0)
1210− where
1211− {-# INLINE_LATE step' #-}
1212− step' gst (st, i) | i < n = do
1213− r <- step gst st
1214− return $ case r of
1215− Yield x s -> Yield x (s, i + 1)
1216− Skip s -> Skip (s, i)
1217− Stop -> Stop
1218− step' _ (_, _) = return Stop
1219−
12201245{-# INLINE_NORMAL takeWhileM #-}
12211246takeWhileM :: Monad m => (a -> m Bool) -> Stream m a -> Stream m a
12221247takeWhileM f (Stream step state) = Stream step' state
@@ -1470,67 +1495,6 @@ zipWithM f (Stream stepa ta) (Stream stepb tb) = Stream step (ta, tb, Nothing)
14701495zipWith :: Monad m => (a -> b -> c) -> Stream m a -> Stream m b -> Stream m c
14711496zipWith f = zipWithM (\a b -> return (f a b))
14721497
1473−------------------------------------------------------------------------------
1474−-- Comparisions
1475−------------------------------------------------------------------------------
1476−
1477−{-# INLINE_NORMAL eqBy #-}
1478−eqBy :: Monad m => (a -> b -> Bool) -> Stream m a -> Stream m b -> m Bool
1479−eqBy eq (Stream step1 t1) (Stream step2 t2) = eq_loop0 SPEC t1 t2
1480− where
1481− eq_loop0 !_ s1 s2 = do
1482− r <- step1 defState s1
1483− case r of
1484− Yield x s1' -> eq_loop1 SPEC x s1' s2
1485− Skip s1' -> eq_loop0 SPEC s1' s2
1486− Stop -> eq_null s2
1487−
1488− eq_loop1 !_ x s1 s2 = do
1489− r <- step2 defState s2
1490− case r of
1491− Yield y s2'
1492− | eq x y -> eq_loop0 SPEC s1 s2'
1493− | otherwise -> return False
1494− Skip s2' -> eq_loop1 SPEC x s1 s2'
1495− Stop -> return False
1496−
1497− eq_null s2 = do
1498− r <- step2 defState s2
1499− case r of
1500− Yield _ _ -> return False
1501− Skip s2' -> eq_null s2'
1502− Stop -> return True
1503−
1504−-- | Compare two streams lexicographically
1505−{-# INLINE_NORMAL cmpBy #-}
1506−cmpBy
1507− :: Monad m
1508− => (a -> b -> Ordering) -> Stream m a -> Stream m b -> m Ordering
1509−cmpBy cmp (Stream step1 t1) (Stream step2 t2) = cmp_loop0 SPEC t1 t2
1510− where
1511− cmp_loop0 !_ s1 s2 = do
1512− r <- step1 defState s1
1513− case r of
1514− Yield x s1' -> cmp_loop1 SPEC x s1' s2
1515− Skip s1' -> cmp_loop0 SPEC s1' s2
1516− Stop -> cmp_null s2
1517−
1518− cmp_loop1 !_ x s1 s2 = do
1519− r <- step2 defState s2
1520− case r of
1521− Yield y s2' -> case x `cmp` y of
1522− EQ -> cmp_loop0 SPEC s1 s2'
1523− c -> return c
1524− Skip s2' -> cmp_loop1 SPEC x s1 s2'
1525− Stop -> return GT
1526−
1527− cmp_null s2 = do
1528− r <- step2 defState s2
1529− case r of
1530− Yield _ _ -> return LT
1531− Skip s2' -> cmp_null s2'
1532− Stop -> return EQ
1533−
15341498------------------------------------------------------------------------------
15351499-- Merging
15361500------------------------------------------------------------------------------
src/Streamly/Streams/StreamD/Type.hs+112−1
@@ -34,18 +34,28 @@ module Streamly.Streams.StreamD.Type
3434#endif
3535 , map
3636 , mapM
37+
3738 , foldrT
3839 , foldrM
3940 , foldrMx
4041 , foldr
42+
43+ , foldl'
44+ , foldlM'
45+
4146 , toList
47+ , fromList
48+
49+ , eqBy
50+ , cmpBy
51+ , take
4252 )
4353where
4454
4555import Control.Applicative (liftA2)
4656import Control.Monad.Trans (lift, MonadTrans)
4757import GHC.Types (SPEC(..))
48−import Prelude hiding (map, mapM, foldr)
58+import Prelude hiding (map, mapM, foldr, take)
4959import Streamly.SVar (State(..), adaptState, defState)
5060
5161import qualified Streamly.Streams.StreamK as K
@@ -179,3 +189,104 @@ foldrT f final (Stream step state) = go SPEC state
179189{-# INLINE toList #-}
180190toList :: Monad m => Stream m a -> m [a]
181191toList = foldr (:) []
192+
193+-- XXX implement in terms of foldlMx'?
194+{-# INLINE_NORMAL foldlM' #-}
195+foldlM' :: Monad m => (b -> a -> m b) -> b -> Stream m a -> m b
196+foldlM' fstep begin (Stream step state) = go SPEC begin state
197+ where
198+ go !_ acc st = acc `seq` do
199+ r <- step defState st
200+ case r of
201+ Yield x s -> do
202+ acc' <- fstep acc x
203+ go SPEC acc' s
204+ Skip s -> go SPEC acc s
205+ Stop -> return acc
206+
207+{-# INLINE foldl' #-}
208+foldl' :: Monad m => (b -> a -> b) -> b -> Stream m a -> m b
209+foldl' fstep = foldlM' (\b a -> return (fstep b a))
210+
211+-- | Convert a list of pure values to a 'Stream'
212+{-# INLINE_LATE fromList #-}
213+fromList :: Monad m => [a] -> Stream m a
214+fromList = Stream step
215+ where
216+ {-# INLINE_LATE step #-}
217+ step _ (x:xs) = return $ Yield x xs
218+ step _ [] = return Stop
219+
220+------------------------------------------------------------------------------
221+-- Comparisons
222+------------------------------------------------------------------------------
223+
224+{-# INLINE_NORMAL eqBy #-}
225+eqBy :: Monad m => (a -> b -> Bool) -> Stream m a -> Stream m b -> m Bool
226+eqBy eq (Stream step1 t1) (Stream step2 t2) = eq_loop0 SPEC t1 t2
227+ where
228+ eq_loop0 !_ s1 s2 = do
229+ r <- step1 defState s1
230+ case r of
231+ Yield x s1' -> eq_loop1 SPEC x s1' s2
232+ Skip s1' -> eq_loop0 SPEC s1' s2
233+ Stop -> eq_null s2
234+
235+ eq_loop1 !_ x s1 s2 = do
236+ r <- step2 defState s2
237+ case r of
238+ Yield y s2'
239+ | eq x y -> eq_loop0 SPEC s1 s2'
240+ | otherwise -> return False
241+ Skip s2' -> eq_loop1 SPEC x s1 s2'
242+ Stop -> return False
243+
244+ eq_null s2 = do
245+ r <- step2 defState s2
246+ case r of
247+ Yield _ _ -> return False
248+ Skip s2' -> eq_null s2'
249+ Stop -> return True
250+
251+-- | Compare two streams lexicographically
252+{-# INLINE_NORMAL cmpBy #-}
253+cmpBy
254+ :: Monad m
255+ => (a -> b -> Ordering) -> Stream m a -> Stream m b -> m Ordering
256+cmpBy cmp (Stream step1 t1) (Stream step2 t2) = cmp_loop0 SPEC t1 t2
257+ where
258+ cmp_loop0 !_ s1 s2 = do
259+ r <- step1 defState s1
260+ case r of
261+ Yield x s1' -> cmp_loop1 SPEC x s1' s2
262+ Skip s1' -> cmp_loop0 SPEC s1' s2
263+ Stop -> cmp_null s2
264+
265+ cmp_loop1 !_ x s1 s2 = do
266+ r <- step2 defState s2
267+ case r of
268+ Yield y s2' -> case x `cmp` y of
269+ EQ -> cmp_loop0 SPEC s1 s2'
270+ c -> return c
271+ Skip s2' -> cmp_loop1 SPEC x s1 s2'
272+ Stop -> return GT
273+
274+ cmp_null s2 = do
275+ r <- step2 defState s2
276+ case r of
277+ Yield _ _ -> return LT
278+ Skip s2' -> cmp_null s2'
279+ Stop -> return EQ
280+
281+{-# INLINE_NORMAL take #-}
282+take :: Monad m => Int -> Stream m a -> Stream m a
283+take n (Stream step state) = n `seq` Stream step' (state, 0)
284+ where
285+ {-# INLINE_LATE step' #-}
286+ step' gst (st, i) | i < n = do
287+ r <- step gst st
288+ return $ case r of
289+ Yield x s -> Yield x (s, i + 1)
290+ Skip s -> Skip (s, i)
291+ Stop -> Stop
292+ step' _ (_, _) = return Stop
streamly.cabal+69−7
@@ -171,8 +171,10 @@ library
171171 , Streamly.List
172172 , Streamly.String
173173 , Streamly.Enumeration
174+ , Streamly.Array.Types
174175
175176 exposed-modules: Streamly.Prelude
177+ , Streamly.Array
176178 , Streamly.Time
177179 , Streamly
178180 , Streamly.Tutorial
@@ -188,6 +190,7 @@ library
188190 cpp-options: -DDISABLE_FUSION
189191
190192 if flag(dev)
193+ cpp-options: -DDEVBUILD
191194 ghc-options: -Wmissed-specialisations
192195 -Wall-missed-specialisations
193196 -fno-ignore-asserts
@@ -335,6 +338,35 @@ test-suite properties
335338 transformers >= 0.4 && < 0.6
336339 default-language: Haskell2010
337340
341+test-suite arrays
342+ type: exitcode-stdio-1.0
343+ main-is: Arrays.hs
344+ js-sources: jsbits/clock.js
345+ hs-source-dirs: test
346+ ghc-options: -fno-ignore-asserts -Wall -O0 -threaded -with-rtsopts=-N
347+ if flag(dev)
348+ cpp-options: -DDEVBUILD
349+ ghc-options: -Wmissed-specialisations
350+ -Wall-missed-specialisations
351+ if impl(ghc >= 8.0)
352+ ghc-options: -Wcompat
353+ -Wunrecognised-warning-flags
354+ -Widentities
355+ -Wincomplete-record-updates
356+ -Wincomplete-uni-patterns
357+ -Wredundant-constraints
358+ -Wnoncanonical-monad-instances
359+ -Wnoncanonical-monadfail-instances
360+ build-depends:
361+ streamly
362+ , base >= 4.8 && < 5
363+ , QuickCheck >= 2.10 && < 2.14
364+ , hspec >= 2.0 && < 3
365+ if impl(ghc < 8.0)
366+ build-depends:
367+ transformers >= 0.4 && < 0.6
368+ default-language: Haskell2010
369+
338370test-suite maxrate
339371 type: exitcode-stdio-1.0
340372 default-language: Haskell2010
@@ -395,7 +427,7 @@ benchmark linear
395427 main-is: Linear.hs
396428 other-modules: LinearOps
397429 default-language: Haskell2010
398− ghc-options: -O2 -Wall -fspec-constr-recursive=10
430+ ghc-options: -O2 -Wall -with-rtsopts "-T" -fspec-constr-recursive=10
399431 if flag(dev)
400432 ghc-options: -Wmissed-specialisations
401433 -Wall-missed-specialisations
@@ -422,7 +454,7 @@ benchmark linear-async
422454 main-is: LinearAsync.hs
423455 other-modules: LinearOps
424456 default-language: Haskell2010
425− ghc-options: -O2 -Wall -fspec-constr-recursive=10
457+ ghc-options: -O2 -Wall -fspec-constr-recursive=10 -with-rtsopts "-T"
426458 cpp-options: -DLINEAR_ASYNC
427459 if flag(dev)
428460 ghc-options: -Wmissed-specialisations
@@ -450,7 +482,7 @@ benchmark linear-rate
450482 main-is: LinearRate.hs
451483 other-modules: LinearOps
452484 default-language: Haskell2010
453− ghc-options: -O2 -Wall -fspec-constr-recursive=10
485+ ghc-options: -O2 -Wall -fspec-constr-recursive=10 -with-rtsopts "-T"
454486 if flag(dev)
455487 ghc-options: -Wmissed-specialisations
456488 -Wall-missed-specialisations
@@ -477,7 +509,7 @@ benchmark nested
477509 main-is: Nested.hs
478510 other-modules: NestedOps
479511 default-language: Haskell2010
480− ghc-options: -O2 -Wall -fspec-constr-recursive=10
512+ ghc-options: -O2 -Wall -fspec-constr-recursive=10 -with-rtsopts "-T"
481513 if flag(dev)
482514 ghc-options: -Wmissed-specialisations
483515 -Wall-missed-specialisations
@@ -498,6 +530,34 @@ benchmark nested
498530 , random >= 1.0 && < 2.0
499531 , gauge >= 0.2.4 && < 0.3
500532
533+benchmark array
534+ type: exitcode-stdio-1.0
535+ hs-source-dirs: benchmark
536+ main-is: Array.hs
537+ other-modules: ArrayOps
538+ default-language: Haskell2010
539+ ghc-options: -O2 -Wall -with-rtsopts "-T" -fspec-constr-recursive=10
540+ if flag(dev)
541+ cpp-options: -DDEVBUILD
542+ ghc-options: -Wmissed-specialisations
543+ -Wall-missed-specialisations
544+ -fno-ignore-asserts
545+ if impl(ghc >= 8.0)
546+ ghc-options: -Wcompat
547+ -Wunrecognised-warning-flags
548+ -Widentities
549+ -Wincomplete-record-updates
550+ -Wincomplete-uni-patterns
551+ -Wredundant-constraints
552+ -Wnoncanonical-monad-instances
553+ -Wnoncanonical-monadfail-instances
554+ build-depends:
555+ streamly
556+ , base >= 4.8 && < 5
557+ , deepseq >= 1.4.3 && < 1.5
558+ , random >= 1.0 && < 2.0
559+ , gauge >= 0.2.4 && < 0.3
560+
501561-------------------------------------------------------------------------------
502562-- Internal benchmarks for unexposed modules
503563-------------------------------------------------------------------------------
@@ -529,7 +589,7 @@ benchmark base
529589 , StreamKOps
530590
531591 default-language: Haskell2010
532− ghc-options: -O2 -Wall -fspec-constr-recursive=10
592+ ghc-options: -O2 -Wall -fspec-constr-recursive=10 -with-rtsopts "-T"
533593 if flag(dev)
534594 ghc-options: -Wmissed-specialisations
535595 -Wall-missed-specialisations
@@ -590,7 +650,7 @@ executable nano-bench
590650 , Streamly.Streams.StreamD.Type
591651 , Streamly.Streams.StreamD
592652 default-language: Haskell2010
593− ghc-options: -O2 -Wall
653+ ghc-options: -O2 -Wall -with-rtsopts "-T"
594654
595655 if flag(dev)
596656 buildable: True
@@ -599,6 +659,7 @@ executable nano-bench
599659 , gauge >= 0.2.4 && < 0.3
600660 , ghc-prim >= 0.2 && < 0.6
601661 , containers >= 0.5 && < 0.7
662+ , deepseq >= 1.4.3 && < 1.5
602663 , heaps >= 0.3 && < 0.4
603664 , random >= 1.0 && < 2.0
604665
@@ -610,6 +671,7 @@ executable nano-bench
610671 , monad-control >= 1.0 && < 2
611672 , mtl >= 2.2 && < 3
612673 , transformers >= 0.4 && < 0.6
674+ , transformers-base >= 0.4 && < 0.5
613675 else
614676 buildable: False
615677
@@ -617,7 +679,7 @@ executable adaptive
617679 hs-source-dirs: benchmark
618680 main-is: Adaptive.hs
619681 default-language: Haskell2010
620− ghc-options: -O2 -Wall
682+ ghc-options: -O2 -Wall -with-rtsopts "-T"
621683
622684 if flag(dev)
623685 buildable: True
test/Arrays.hsnew file+46
@@ -0,0 +1,46 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE FlexibleContexts #-}
3+
4+module Main (main) where
5+
6+import Data.Functor.Identity (runIdentity)
7+import GHC.Word (Word8)
8+
9+import Test.Hspec.QuickCheck
10+import Test.QuickCheck (Property, forAll, Gen, vectorOf, arbitrary, (===))
11+
12+import Test.Hspec as H
13+
14+import qualified Streamly.Prelude as S
15+import qualified Streamly.Array as A
16+
17+-- Coverage build takes too long with default number of tests
18+maxTestCount :: Int
19+#ifdef DEVBUILD
20+maxTestCount = 100
21+#else
22+maxTestCount = 10
23+#endif
24+
25+randomLengthArray :: Word8 -> Property
26+randomLengthArray len =
27+ forAll (vectorOf (fromIntegral len) (arbitrary :: Gen Int)) $ \list ->
28+ let arr = runIdentity $ A.fromStreamN (fromIntegral len)
29+ $ S.fromList list
30+ in A.length arr === fromIntegral len
31+
32+randomArrayRoundTrip :: Word8 -> Property
33+randomArrayRoundTrip len =
34+ forAll (vectorOf (fromIntegral len) (arbitrary :: Gen Int)) $ \list ->
35+ let arr = runIdentity $ A.fromStreamN (fromIntegral len)
36+ $ S.fromList list
37+ in runIdentity (S.toList (A.toStream arr)) === list
38+
39+main :: IO ()
40+main = hspec
41+ $ H.parallel
42+ $ modifyMaxSuccess (const maxTestCount)
43+ $ do
44+ describe "Construction" $ do
45+ prop "length . fromStreamN n === n" $ randomLengthArray
46+ prop "fromStreamN n . toStream === id" $ randomArrayRoundTrip