on master help sign in
move stream transformation operations to Prelude
a740d4dHarendra Kumar
README.md+4−4
@@ -454,8 +454,8 @@ main = withArg wcl >>= print
454
454
455``` haskell
455``` haskell
456avgll =
456avgll =
457− FL.foldl' avg
457+ S.runFold avg
458− . FL.splitBySuffix (== 10) FL.length
458+ . S.splitBySuffix (== 10) FL.length
459 . FH.read
459 . FH.read
460
460
461 where avg = (/) <$> toDouble FL.sum <*> toDouble FL.length
461 where avg = (/) <$> toDouble FL.sum <*> toDouble FL.length
@@ -468,9 +468,9 @@ main = withArg avgll >>= print
468
468
469``` haskell
469``` haskell
470llhisto =
470llhisto =
471− FL.foldl' (FL.classify FL.length)
471+ S.runFold (FL.classify FL.length)
472 . S.map bucket
472 . S.map bucket
473− . FL.splitBySuffix (== 10) FL.length
473+ . S.splitBySuffix (== 10) FL.length
474 . FH.read
474 . FH.read
475
475
476 where
476 where
benchmark/FileIO.hs+21−21
@@ -170,18 +170,18 @@ main = do
170 , bgroup "grouping"
170 , bgroup "grouping"
171 [ mkBench "chunksOf 1 (toArray)" href $ do
171 [ mkBench "chunksOf 1 (toArray)" href $ do
172 Handles inh _ <- readIORef href
172 Handles inh _ <- readIORef href
173− S.length $ FL.chunksOf fileSize (A.toArrayN fileSize)
173+ S.length $ S.chunksOf fileSize (A.toArrayN fileSize)
174 (FH.read inh)
174 (FH.read inh)
175
175
176 , mkBench "chunksOf 1" href $ do
176 , mkBench "chunksOf 1" href $ do
177 Handles inh _ <- readIORef href
177 Handles inh _ <- readIORef href
178− S.length $ FL.chunksOf 1 FL.drain (FH.read inh)
178+ S.length $ S.chunksOf 1 FL.drain (FH.read inh)
179 , mkBench "chunksOf 10" href $ do
179 , mkBench "chunksOf 10" href $ do
180 Handles inh _ <- readIORef href
180 Handles inh _ <- readIORef href
181− S.length $ FL.chunksOf 10 FL.drain (FH.read inh)
181+ S.length $ S.chunksOf 10 FL.drain (FH.read inh)
182 , mkBench "chunksOf 1000" href $ do
182 , mkBench "chunksOf 1000" href $ do
183 Handles inh _ <- readIORef href
183 Handles inh _ <- readIORef href
184− S.length $ FL.chunksOf 1000 FL.drain (FH.read inh)
184+ S.length $ S.chunksOf 1000 FL.drain (FH.read inh)
185 ]
185 ]
186 , bgroup "group-ungroup"
186 , bgroup "group-ungroup"
187 [ mkBench "lines-unlines" href $ do
187 [ mkBench "lines-unlines" href $ do
@@ -218,15 +218,15 @@ main = do
218 [ bgroup "predicate"
218 [ bgroup "predicate"
219 [ mkBench "splitOn \\n (line count)" href $ do
219 [ mkBench "splitOn \\n (line count)" href $ do
220 Handles inh _ <- readIORef href
220 Handles inh _ <- readIORef href
221− (S.length $ FL.splitOn (== lf) FL.drain
221+ (S.length $ S.splitOn (== lf) FL.drain
222 $ FH.read inh) -- >>= print
222 $ FH.read inh) -- >>= print
223 , mkBench "splitOnSuffix \\n (line count)" href $ do
223 , mkBench "splitOnSuffix \\n (line count)" href $ do
224 Handles inh _ <- readIORef href
224 Handles inh _ <- readIORef href
225− (S.length $ FL.splitOnSuffix (== lf) FL.drain
225+ (S.length $ S.splitOnSuffix (== lf) FL.drain
226 $ FH.read inh) -- >>= print
226 $ FH.read inh) -- >>= print
227 , mkBench "wordsBy isSpace (word count)" href $ do
227 , mkBench "wordsBy isSpace (word count)" href $ do
228 Handles inh _ <- readIORef href
228 Handles inh _ <- readIORef href
229− (S.length $ FL.wordsBy isSp FL.drain
229+ (S.length $ S.wordsBy isSp FL.drain
230 $ FH.read inh) -- >>= print
230 $ FH.read inh) -- >>= print
231 ]
231 ]
232
232
@@ -234,65 +234,65 @@ main = do
234 , bgroup "empty-pattern"
234 , bgroup "empty-pattern"
235 [ mkBench "splitOnSeq \"\"" href $ do
235 [ mkBench "splitOnSeq \"\"" href $ do
236 Handles inh _ <- readIORef href
236 Handles inh _ <- readIORef href
237− (S.length $ FL.splitOnSeq (A.fromList []) FL.drain
237+ (S.length $ S.splitOnSeq (A.fromList []) FL.drain
238 $ FH.read inh) -- >>= print
238 $ FH.read inh) -- >>= print
239 , mkBench "splitOnSuffixSeq \"\"" href $ do
239 , mkBench "splitOnSuffixSeq \"\"" href $ do
240 Handles inh _ <- readIORef href
240 Handles inh _ <- readIORef href
241− (S.length $ FL.splitOnSuffixSeq (A.fromList []) FL.drain
241+ (S.length $ S.splitOnSuffixSeq (A.fromList []) FL.drain
242 $ FH.read inh) -- >>= print
242 $ FH.read inh) -- >>= print
243 ]
243 ]
244 , bgroup "short-pattern"
244 , bgroup "short-pattern"
245 [ mkBench "splitOnSeq \\n (line count)" href $ do
245 [ mkBench "splitOnSeq \\n (line count)" href $ do
246 Handles inh _ <- readIORef href
246 Handles inh _ <- readIORef href
247− (S.length $ FL.splitOn lfarr FL.drain
247+ (S.length $ S.splitOn lfarr FL.drain
248 $ FH.read inh) -- >>= print
248 $ FH.read inh) -- >>= print
249 , mkBench "splitOnSuffixSeq \\n (line count)" href $ do
249 , mkBench "splitOnSuffixSeq \\n (line count)" href $ do
250 Handles inh _ <- readIORef href
250 Handles inh _ <- readIORef href
251− (S.length $ FL.splitOnSuffixSeq lfarr FL.drain
251+ (S.length $ S.splitOnSuffixSeq lfarr FL.drain
252 $ FH.read inh) -- >>= print
252 $ FH.read inh) -- >>= print
253 , mkBench "splitOnSeq a" href $ do
253 , mkBench "splitOnSeq a" href $ do
254 Handles inh _ <- readIORef href
254 Handles inh _ <- readIORef href
255− (S.length $ FL.splitOn (toarr "a") FL.drain
255+ (S.length $ S.splitOnSeq (toarr "a") FL.drain
256 $ FH.read inh) -- >>= print
256 $ FH.read inh) -- >>= print
257 , mkBench "splitOnSeq \\r\\n" href $ do
257 , mkBench "splitOnSeq \\r\\n" href $ do
258 Handles inh _ <- readIORef href
258 Handles inh _ <- readIORef href
259− (S.length $ FL.splitOn (toarr "\r\n") FL.drain
259+ (S.length $ S.splitOnSeq (toarr "\r\n") FL.drain
260 $ FH.read inh) -- >>= print
260 $ FH.read inh) -- >>= print
261 , mkBench "splitOnSuffixSeq \\r\\n)" href $ do
261 , mkBench "splitOnSuffixSeq \\r\\n)" href $ do
262 Handles inh _ <- readIORef href
262 Handles inh _ <- readIORef href
263− (S.length $ FL.splitOnSuffixSeq (toarr "\r\n") FL.drain
263+ (S.length $ S.splitOnSuffixSeq (toarr "\r\n") FL.drain
264 $ FH.read inh) -- >>= print
264 $ FH.read inh) -- >>= print
265 , mkBench "splitOnSeq aa" href $ do
265 , mkBench "splitOnSeq aa" href $ do
266 Handles inh _ <- readIORef href
266 Handles inh _ <- readIORef href
267− (S.length $ FL.splitOnSeq (toarr "aa") FL.drain
267+ (S.length $ S.splitOnSeq (toarr "aa") FL.drain
268 $ FH.read inh) -- >>= print
268 $ FH.read inh) -- >>= print
269 , mkBench "splitOnSeq aaaa" href $ do
269 , mkBench "splitOnSeq aaaa" href $ do
270 Handles inh _ <- readIORef href
270 Handles inh _ <- readIORef href
271− (S.length $ FL.splitOnSeq (toarr "aaaa") FL.drain
271+ (S.length $ S.splitOnSeq (toarr "aaaa") FL.drain
272 $ FH.read inh) -- >>= print
272 $ FH.read inh) -- >>= print
273 , mkBench "splitOnSeq abcdefgh" href $ do
273 , mkBench "splitOnSeq abcdefgh" href $ do
274 Handles inh _ <- readIORef href
274 Handles inh _ <- readIORef href
275− (S.length $ FL.splitOnSeq (toarr "abcdefgh") FL.drain
275+ (S.length $ S.splitOnSeq (toarr "abcdefgh") FL.drain
276 $ FH.read inh) -- >>= print
276 $ FH.read inh) -- >>= print
277 ]
277 ]
278 , bgroup "long-pattern"
278 , bgroup "long-pattern"
279 [ mkBench "splitOnSeq abcdefghi" href $ do
279 [ mkBench "splitOnSeq abcdefghi" href $ do
280 Handles inh _ <- readIORef href
280 Handles inh _ <- readIORef href
281− (S.length $ FL.splitOnSeq (toarr "abcdefghi") FL.drain
281+ (S.length $ S.splitOnSeq (toarr "abcdefghi") FL.drain
282 $ FH.read inh) -- >>= print
282 $ FH.read inh) -- >>= print
283 , mkBench "splitOnSeq catcatcatcatcat" href $ do
283 , mkBench "splitOnSeq catcatcatcatcat" href $ do
284 Handles inh _ <- readIORef href
284 Handles inh _ <- readIORef href
285− (S.length $ FL.splitOnSeq (toarr "catcatcatcatcat") FL.drain
285+ (S.length $ S.splitOnSeq (toarr "catcatcatcatcat") FL.drain
286 $ FH.read inh) -- >>= print
286 $ FH.read inh) -- >>= print
287 , mkBench "splitOnSeq abc...xyz" href $ do
287 , mkBench "splitOnSeq abc...xyz" href $ do
288 Handles inh _ <- readIORef href
288 Handles inh _ <- readIORef href
289− (S.length $ FL.splitOnSeq
289+ (S.length $ S.splitOnSeq
290 (toarr "abcdefghijklmnopqrstuvwxyz")
290 (toarr "abcdefghijklmnopqrstuvwxyz")
291 FL.drain
291 FL.drain
292 $ FH.read inh) -- >>= print
292 $ FH.read inh) -- >>= print
293 , mkBench "splitOnSuffixSeq abc...xyz" href $ do
293 , mkBench "splitOnSuffixSeq abc...xyz" href $ do
294 Handles inh _ <- readIORef href
294 Handles inh _ <- readIORef href
295− (S.length $ FL.splitOnSuffixSeq
295+ (S.length $ S.splitOnSuffixSeq
296 (toarr "abcdefghijklmnopqrstuvwxyz")
296 (toarr "abcdefghijklmnopqrstuvwxyz")
297 FL.drain
297 FL.drain
298 $ FH.read inh) -- >>= print
298 $ FH.read inh) -- >>= print
benchmark/Linear.hs+36−36
@@ -202,54 +202,54 @@ main =
202 -- , benchIOSink "toListRev" Ops.toListRev
202 -- , benchIOSink "toListRev" Ops.toListRev
203 ]
203 ]
204 , bgroup "folds"
204 , bgroup "folds"
205− [ benchIOSink "drain" (FL.foldl' FL.drain)
205+ [ benchIOSink "drain" (S.runFold FL.drain)
206 -- , benchIOSink "sink" (Sink.sink Sink.drain)
206 -- , benchIOSink "sink" (Sink.sink Sink.drain)
207− , benchIOSink "last" (FL.foldl' FL.last)
207+ , benchIOSink "last" (S.runFold FL.last)
208− , benchIOSink "length" (FL.foldl' FL.length)
208+ , benchIOSink "length" (S.runFold FL.length)
209− , benchIOSink "sum" (FL.foldl' FL.sum)
209+ , benchIOSink "sum" (S.runFold FL.sum)
210− , benchIOSink "product" (FL.foldl' FL.product)
210+ , benchIOSink "product" (S.runFold FL.product)
211− , benchIOSink "maximumBy" (FL.foldl' (FL.maximumBy compare))
211+ , benchIOSink "maximumBy" (S.runFold (FL.maximumBy compare))
212− , benchIOSink "maximum" (FL.foldl' FL.maximum)
212+ , benchIOSink "maximum" (S.runFold FL.maximum)
213− , benchIOSink "minimumBy" (FL.foldl' (FL.minimumBy compare))
213+ , benchIOSink "minimumBy" (S.runFold (FL.minimumBy compare))
214− , benchIOSink "minimum" (FL.foldl' FL.minimum)
214+ , benchIOSink "minimum" (S.runFold FL.minimum)
215− , benchIOSink "mean" (\s -> FL.foldl' FL.mean (S.map (fromIntegral :: Int -> Double) s))
215+ , benchIOSink "mean" (\s -> S.runFold FL.mean (S.map (fromIntegral :: Int -> Double) s))
216− , benchIOSink "variance" (\s -> FL.foldl' FL.variance (S.map (fromIntegral :: Int -> Double) s))
216+ , benchIOSink "variance" (\s -> S.runFold FL.variance (S.map (fromIntegral :: Int -> Double) s))
217− , benchIOSink "stdDev" (\s -> FL.foldl' FL.stdDev (S.map (fromIntegral :: Int -> Double) s))
217+ , benchIOSink "stdDev" (\s -> S.runFold FL.stdDev (S.map (fromIntegral :: Int -> Double) s))
218
218
219− , benchIOSink "mconcat" (FL.foldl' FL.mconcat . (S.map (Last . Just)))
219+ , benchIOSink "mconcat" (S.runFold FL.mconcat . (S.map (Last . Just)))
220− , benchIOSink "foldMap" (FL.foldl' (FL.foldMap (Last . Just)))
220+ , benchIOSink "foldMap" (S.runFold (FL.foldMap (Last . Just)))
221
221
222− , benchIOSink "toList" (FL.foldl' FL.toList)
222+ , benchIOSink "toList" (S.runFold FL.toList)
223− -- , benchIOSink "toListRev" (FL.foldl' FL.toListRev)
223+ -- , benchIOSink "toListRev" (S.runFold FL.toListRev)
224− -- , benchIOSink "toStream" (FL.foldl' FL.toStream)
224+ -- , benchIOSink "toStream" (S.runFold FL.toStream)
225− -- , benchIOSink "toStreamRev" (FL.foldl' FL.toStreamRev)
225+ -- , benchIOSink "toStreamRev" (S.runFold FL.toStreamRev)
226− , benchIOSink "toArrayN" (FL.foldl' (A.toArrayN Ops.value))
226+ , benchIOSink "toArrayN" (S.runFold (A.toArrayN Ops.value))
227
227
228− , benchIOSink "index" (FL.foldl' (FL.index Ops.maxValue))
228+ , benchIOSink "index" (S.runFold (FL.index Ops.maxValue))
229− , benchIOSink "head" (FL.foldl' FL.head)
229+ , benchIOSink "head" (S.runFold FL.head)
230− , benchIOSink "find" (FL.foldl' (FL.find (== Ops.maxValue)))
230+ , benchIOSink "find" (S.runFold (FL.find (== Ops.maxValue)))
231− , benchIOSink "findIndex" (FL.foldl' (FL.findIndex (== Ops.maxValue)))
231+ , benchIOSink "findIndex" (S.runFold (FL.findIndex (== Ops.maxValue)))
232− , benchIOSink "elemIndex" (FL.foldl' (FL.elemIndex Ops.maxValue))
232+ , benchIOSink "elemIndex" (S.runFold (FL.elemIndex Ops.maxValue))
233
233
234− , benchIOSink "null" (FL.foldl' FL.null)
234+ , benchIOSink "null" (S.runFold FL.null)
235− , benchIOSink "elem" (FL.foldl' (FL.elem Ops.maxValue))
235+ , benchIOSink "elem" (S.runFold (FL.elem Ops.maxValue))
236− , benchIOSink "notElem" (FL.foldl' (FL.notElem Ops.maxValue))
236+ , benchIOSink "notElem" (S.runFold (FL.notElem Ops.maxValue))
237− , benchIOSink "all" (FL.foldl' (FL.all (<= Ops.maxValue)))
237+ , benchIOSink "all" (S.runFold (FL.all (<= Ops.maxValue)))
238− , benchIOSink "any" (FL.foldl' (FL.any (> Ops.maxValue)))
238+ , benchIOSink "any" (S.runFold (FL.any (> Ops.maxValue)))
239− , benchIOSink "and" (\s -> FL.foldl' FL.and (S.map (<= Ops.maxValue) s))
239+ , benchIOSink "and" (\s -> S.runFold FL.and (S.map (<= Ops.maxValue) s))
240− , benchIOSink "or" (\s -> FL.foldl' FL.or (S.map (> Ops.maxValue) s))
240+ , benchIOSink "or" (\s -> S.runFold FL.or (S.map (> Ops.maxValue) s))
241 ]
241 ]
242 , bgroup "folds-transforms"
242 , bgroup "folds-transforms"
243− [ benchIOSink "drain" (FL.foldl' FL.drain)
243+ [ benchIOSink "drain" (S.runFold FL.drain)
244− , benchIOSink "lmap" (FL.foldl' (FL.lmap (+1) FL.drain))
244+ , benchIOSink "lmap" (S.runFold (FL.lmap (+1) FL.drain))
245 {-, benchIOSink "pipe-mapM"
245 {-, benchIOSink "pipe-mapM"
246− (FL.foldl' (FL.transform (Pipe.mapM (\x -> return $ x + 1)) FL.drain))-}
246+ (S.runFold (FL.transform (Pipe.mapM (\x -> return $ x + 1)) FL.drain))-}
247 ]
247 ]
248 , bgroup "folds-compositions" -- Applicative
248 , bgroup "folds-compositions" -- Applicative
249 [
249 [
250− benchIOSink "all,any" (FL.foldl' ((,) <$> FL.all (<= Ops.maxValue)
250+ benchIOSink "all,any" (S.runFold ((,) <$> FL.all (<= Ops.maxValue)
251 <*> FL.any (> Ops.maxValue)))
251 <*> FL.any (> Ops.maxValue)))
252− , benchIOSink "sum,length" (FL.foldl' ((,) <$> FL.sum <*> FL.length))
252+ , benchIOSink "sum,length" (S.runFold ((,) <$> FL.sum <*> FL.length))
253 ] {-
253 ] {-
254 , bgroup "pipes"
254 , bgroup "pipes"
255 [ benchIOSink "mapM" (Ops.transformMapM serially 1)
255 [ benchIOSink "mapM" (Ops.transformMapM serially 1)
examples/FileSinkServer.hs+1−2
@@ -10,7 +10,6 @@ import System.Environment (getArgs)
10import Streamly
10import Streamly
11import Streamly.String
11import Streamly.String
12import qualified Streamly.FileSystem.Handle as FH
12import qualified Streamly.FileSystem.Handle as FH
13−import qualified Streamly.Fold as FL
14import qualified Streamly.Mem.Array as A
13import qualified Streamly.Mem.Array as A
15import qualified Streamly.Network.Socket as NS
14import qualified Streamly.Network.Socket as NS
16import qualified Streamly.Network.Server as NS
15import qualified Streamly.Network.Server as NS
@@ -31,6 +30,6 @@ main = do
31 where
30 where
32
31
33 recv =
32 recv =
34− FL.splitBySuffix (== '\n') A.toArray
33+ S.splitBySuffix (== '\n') A.toArray
35 . decodeChar8
34 . decodeChar8
36 . NS.read
35 . NS.read
examples/HandleIO.hs+4−4
@@ -44,16 +44,16 @@ grepc pat src = print . (subtract 1) =<< (S.length
44-}
44-}
45
45
46avgll :: FH.Handle -> IO ()
46avgll :: FH.Handle -> IO ()
47−avgll src = print =<< (FL.foldl' avg
47+avgll src = print =<< (S.runFold avg
48− $ FL.splitBySuffix (== ord' '\n') FL.length
48+ $ S.splitBySuffix (== ord' '\n') FL.length
49 $ FH.read src)
49 $ FH.read src)
50 where avg = (/) <$> toDouble FL.sum <*> toDouble FL.length
50 where avg = (/) <$> toDouble FL.sum <*> toDouble FL.length
51 toDouble = fmap (fromIntegral :: Int -> Double)
51 toDouble = fmap (fromIntegral :: Int -> Double)
52
52
53llhisto :: FH.Handle -> IO ()
53llhisto :: FH.Handle -> IO ()
54−llhisto src = print =<< (FL.foldl' (FL.classify FL.length)
54+llhisto src = print =<< (S.runFold (FL.classify FL.length)
55 $ S.map bucket
55 $ S.map bucket
56− $ FL.splitBySuffix (== ord' '\n') FL.length
56+ $ S.splitBySuffix (== ord' '\n') FL.length
57 $ FH.read src)
57 $ FH.read src)
58 where
58 where
59 bucket n = let i = n `div` 10 in if i > 9 then (9,n) else (i,n)
59 bucket n = let i = n `div` 10 in if i > 9 then (9,n) else (i,n)
src/Streamly/Fold.hs+4−1356
@@ -55,11 +55,6 @@ module Streamly.Fold
55 -- * Fold Type
55 -- * Fold Type
56 Fold -- (..)
56 Fold -- (..)
57
57
58− -- * Folding
59− -- $termination
60−
61− , foldl'
62−
63 -- , tail
58 -- , tail
64 -- , init
59 -- , init
65
60
@@ -144,22 +139,8 @@ module Streamly.Fold
144 --, lsequence
139 --, lsequence
145 , lmapM
140 , lmapM
146
141
147− -- ** Scanning
148− , scanl'
149− , postscanl'
150−
151− -- , lscanl'
152− -- , lscanlM'
153− -- , lscanl1'
154− -- , lscanl1M'
155− --
156− -- , lpostscanl'
157− -- , lpostscanlM'
158− -- , lprescanl'
159− -- , lprescanlM'
160−
161 -- -- ** Filtering
142 -- -- ** Filtering
162− -- , lfilter
143+ , lfilter
163 -- , lfilterM
144 -- , lfilterM
164 -- , ldeleteBy
145 -- , ldeleteBy
165 -- , luniq
146 -- , luniq
@@ -195,76 +176,6 @@ module Streamly.Fold
195 , ldropWhileM
176 , ldropWhileM
196 -}
177 -}
197
178
198− -- ** Breaking
199−
200− -- By chunks
201− -- , splitAt -- spanN
202− -- , splitIn -- sessionN
203−
204− -- By elements
205− -- , span -- spanWhile
206− -- , break -- breakBefore
207− -- , breakAfter
208− -- , breakOn
209− -- , breakAround
210− -- , spanBy
211− -- , spanByRolling
212−
213− -- By sequences
214− -- breakOnSeq
215−
216− -- ** Splitting
217− -- | Streams can be split into segments in space or in time. We use the
218− -- term @chunk@ to refer to a spatial length of the stream (spatial window)
219− -- and the term @session@ to refer to a length in time (time window).
220−
221− -- In imperative terms, grouped folding can be considered as a nested loop
222− -- where we loop over the stream to group elements and then loop over
223− -- individual groups to fold them to a single value that is yielded in the
224− -- output stream.
225−
226− -- , groupScan
227−
228− -- *** Chunks
229− , chunksOf
230− , sessionsOf
231−
232− -- , lchunksOf
233− -- , lsessionsOf
234−
235− -- *** Using Element Separators
236− -- On == Dropping the separator
237− , splitOn
238− , splitOnSuffix
239− -- , splitOnPrefix
240−
241− -- By == Keeping the separator
242− -- , splitBy
243− , splitBySuffix
244− -- , splitByPrefix
245− , wordsBy -- stripAndCompactBy
246−
247− -- -- *** Using Sequence Separators
248− -- , splitOnSeq
249− -- , splitOnSuffixSeq
250− -- , splitOnPrefixSeq
251−
252− -- Keeping the delimiters
253− -- , splitBySeq
254− -- , splitBySeqSuffix
255− -- , splitBySeqPrefix
256− -- , wordsBySeq
257−
258− -- Splitting using multiple sequence separators
259− -- , splitOnAnySeq
260− -- , splitOnAnySuffixSeq
261− -- , splitOnAnyPrefixSeq
262−
263− -- ** Grouping
264− , groups
265− , groupsBy
266− , groupsByRolling
267−
268 -- * Distributing
179 -- * Distributing
269 -- |
180 -- |
270 -- The 'Applicative' instance of a distributing 'Fold' distributes one copy
181 -- The 'Applicative' instance of a distributing 'Fold' distributes one copy
@@ -334,40 +245,6 @@ module Streamly.Fold
334 -- , concatMap
245 -- , concatMap
335 -- , chunksOf
246 -- , chunksOf
336 -- , duplicate -- experimental
247 -- , duplicate -- experimental
337−
338− {-
339− -- * Windowed Classification
340− -- | Split the stream into windows or chunks in space or time. Each window
341− -- can be associated with a key, all events associated with a particular
342− -- key in the window can be folded to a single result. The stream is split
343− -- into windows of specified size, the window can be terminated early if
344− -- the closing flag is specified in the input stream.
345− --
346− -- The term "chunk" is used for a space window and the term "session" is
347− -- used for a time window.
348−
349− -- ** Tumbling Windows
350− -- | A new window starts after the previous window is finished.
351− -- , classifyChunksOf
352− -- , classifySessionsOf
353−
354− -- ** Keep Alive Windows
355− -- | The window size is extended if an event arrives within the specified
356− -- window size. This can represent sessions with idle or inactive timeout.
357− -- , classifyKeepAliveChunks
358− -- , classifyKeepAliveSessions
359−
360− {-
361− -- ** Sliding Windows
362− -- | A new window starts after the specified slide from the previous
363− -- window. Therefore windows can overlap.
364− , classifySlidingChunks
365− , classifySlidingSessions
366− -}
367− -- ** Sliding Window Buffers
368− -- , slidingChunkBuffer
369− -- , slidingSessionBuffer
370− -}
371 )
248 )
372where
249where
373
250
@@ -378,12 +255,8 @@ import Control.Monad (void)
378import Control.Monad.Catch (throwM)
255import Control.Monad.Catch (throwM)
379import Control.Monad.IO.Class (MonadIO(..))
256import Control.Monad.IO.Class (MonadIO(..))
380import Control.Monad.Trans.Control (control)
257import Control.Monad.Trans.Control (control)
381−import Data.Functor.Identity (Identity)
382−import Data.Heap (Entry(..))
383import Data.Map.Strict (Map)
258import Data.Map.Strict (Map)
384−import Data.Maybe (fromJust, isJust, isNothing)
385
259
386−import Foreign.Storable (Storable(..))
387import Prelude
260import Prelude
388 hiding (filter, drop, dropWhile, take, takeWhile, zipWith, foldr,
261 hiding (filter, drop, dropWhile, take, takeWhile, zipWith, foldr,
389 foldl, map, mapM_, sequence, all, any, sum, product, elem,
262 foldl, map, mapM_, sequence, all, any, sum, product, elem,
@@ -392,92 +265,15 @@ import Prelude
392 scanl, scanl1, replicate, concatMap, mconcat, foldMap, unzip,
265 scanl, scanl1, replicate, concatMap, mconcat, foldMap, unzip,
393 span, splitAt, break, mapM)
266 span, splitAt, break, mapM)
394
267
395−import qualified Data.Heap as H
396import qualified Data.Map.Strict as Map
268import qualified Data.Map.Strict as Map
397import qualified Prelude
269import qualified Prelude
398
270
399−import Streamly (MonadAsync, parallel)
271+import Streamly.SVar (MonadAsync)
400import Streamly.Fold.Types (Fold(..))
272import Streamly.Fold.Types (Fold(..))
401import Streamly.Pipe.Types (Pipe (..), PipeState(..))
273import Streamly.Pipe.Types (Pipe (..), PipeState(..))
402−import Streamly.Mem.Array.Types (Array)
403−-- import Streamly.Mem.Ring (Ring)
404−import Streamly.Streams.Serial (SerialT)
405−import Streamly.Streams.StreamK (IsStream())
406−import Streamly.Time.Units
407− (AbsTime, MilliSecond64(..), addToAbsTime, diffAbsTime, toRelTime,
408− toAbsTime)
409−
410import Streamly.Strict
274import Streamly.Strict
411
275
412import qualified Streamly.Pipe.Types as Pipe
276import qualified Streamly.Pipe.Types as Pipe
413−import qualified Streamly.Mem.Array.Types as A
414−import qualified Streamly.Prelude as S
415−import qualified Streamly.Streams.StreamD as D
416−import qualified Streamly.Streams.StreamK as K
417−import qualified Streamly.Streams.Prelude as P
418−import qualified Streamly.Streams.Parallel as Par
419−
420−-- $termination
421−--
422−-- We can use the left folds in this module instead of the folds in
423−-- "Streamly.Prelude". For example the following two ways of folding are
424−-- equivalent in functionality and performance,
425−--
426−-- >>> FL.foldl' FL.sum (S.enumerateFromTo 1 100)
427−-- 5050
428−-- >>> S.sum (S.enumerateFromTo 1 100)
429−-- 5050
430−--
431−-- However, left folds are push type folds. That means we push the entire input
432−-- to a fold before we can get the output. Therefore, the performance is
433−-- equivalent only for full folds like 'sum' and 'length'. For partial folds
434−-- like 'head' or 'any' the folds in "Streamly.Prelude" may be much more
435−-- efficient because they are implemented as right folds that terminate as soon
436−-- as we get the result. Note that when a full fold is composed with a partial
437−-- fold in parallel the performance is not impacted as we anyway have to
438−-- consume the whole stream due to the full fold.
439−--
440−-- >>> S.head (1 `S.cons` undefined)
441−-- Just 1
442−-- >>> FL.foldl' FL.head (1 `S.cons` undefined)
443−-- *** Exception: Prelude.undefined
444−--
445−-- However, we can wrap the fold in a scan to convert it into a lazy stream of
446−-- fold steps. We can then terminate the stream whenever we want. For example,
447−--
448−-- >>> S.toList $ S.take 1 $ FL.scanl' FL.head (1 `S.cons` undefined)
449−-- [Nothing]
450−--
451−-- The following example extracts the input stream up to a point where the
452−-- running average of elements is no more than 10:
453−--
454−-- @
455−-- > S.toList
456−-- $ S.map (fromJust . fst)
457−-- $ S.takeWhile (\\(_,x) -> x <= 10)
458−-- $ FL.postscanl' ((,) \<$> FL.last \<*> avg) (S.enumerateFromTo 1.0 100.0)
459−-- @
460−-- @
461−-- [1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0,13.0,14.0,15.0,16.0,17.0,18.0,19.0]
462−-- @
463−
464−------------------------------------------------------------------------------
465−-- Running a Fold
466−------------------------------------------------------------------------------
467−
468−-- XXX rename to foldStream to be consistent with foldArray for arrays? Should
469−-- we move it in the stream module (Prelude as of now) or should we move
470−-- foldArray as well here?
471−
472−-- | Fold a stream using the supplied monadic fold.
473−--
474−-- >>> FL.foldl' FL.sum (S.enumerateFromTo 1 100)
475−-- 5050
476−--
477−-- @since 0.7.0
478−{-# INLINE foldl' #-}
479−foldl' :: Monad m => Fold m a b -> SerialT m a -> m b
480−foldl' (Fold step begin done) = P.foldlMx' step begin done
481
277
482------------------------------------------------------------------------------
278------------------------------------------------------------------------------
483-- Transformations on fold inputs
279-- Transformations on fold inputs
@@ -505,6 +301,8 @@ mapM f = sequence . fmap f
505-- Transformations on fold inputs
301-- Transformations on fold inputs
506------------------------------------------------------------------------------
302------------------------------------------------------------------------------
507
303
304+-- rename to lpipe?
305+--
508-- | Apply a transformation on a 'Fold' using a 'Pipe'.
306-- | Apply a transformation on a 'Fold' using a 'Pipe'.
509--
307--
510-- @since 0.7.0
308-- @since 0.7.0
@@ -821,34 +619,6 @@ toList = Fold (\f x -> return $ f . (x :))
821_toListRev :: Monad m => Fold m a [a]
619_toListRev :: Monad m => Fold m a [a]
822_toListRev = Fold (\xs x -> return $ x:xs) (return []) return
620_toListRev = Fold (\xs x -> return $ x:xs) (return []) return
823
621
824−-- | A fold that buffers its input to a pure stream.
825−--
826−-- /Warning!/ working on large streams accumulated as buffers in memory could
827−-- be very inefficient, consider using "Streamly.Array" instead.
828−--
829−-- @since 0.7.0
830−{-# INLINE _toStream #-}
831−_toStream :: Monad m => Fold m a (SerialT Identity a)
832−_toStream = Fold (\f x -> return $ f . (x `K.cons`))
833− (return id)
834− (return . ($ K.nil))
835−
836−-- This is more efficient than 'toStream'. toStream is exactly the same as
837−-- reversing the stream after toStreamRev.
838−--
839−-- | Buffers the input stream to a pure stream in the reverse order of the
840−-- input.
841−--
842−-- /Warning!/ working on large streams accumulated as buffers in memory could
843−-- be very inefficient, consider using "Streamly.Array" instead.
844−--
845−-- @since 0.7.0
846−
847−-- xn : ... : x2 : x1 : []
848−{-# INLINABLE _toStreamRev #-}
849−_toStreamRev :: Monad m => Fold m a (SerialT Identity a)
850−_toStreamRev = Fold (\xs x -> return $ x `S.cons` xs) (return S.nil) return
851−
852------------------------------------------------------------------------------
622------------------------------------------------------------------------------
853-- Partial Folds
623-- Partial Folds
854------------------------------------------------------------------------------
624------------------------------------------------------------------------------
@@ -1005,26 +775,6 @@ and = Fold (\x a -> return $ x && a) (return True) return
1005or :: Monad m => Fold m Bool Bool
775or :: Monad m => Fold m Bool Bool
1006or = Fold (\x a -> return $ x || a) (return False) return
776or = Fold (\x a -> return $ x || a) (return False) return
1007
777
1008−------------------------------------------------------------------------------
1009−-- Scanning with a Fold
1010−------------------------------------------------------------------------------
1011−
1012−-- | Scan a stream using the given monadic fold.
1013−--
1014−-- @since 0.7.0
1015−{-# INLINE scanl' #-}
1016−scanl' :: Monad m => Fold m a b -> SerialT m a -> SerialT m b
1017−scanl' (Fold step begin done) = P.scanlMx' step begin done
1018−
1019−-- | Postscan a stream using the given monadic fold.
1020−--
1021−-- @since 0.7.0
1022−{-# INLINE postscanl' #-}
1023−postscanl' :: Monad m => Fold m a b -> SerialT m a -> SerialT m b
1024−postscanl' (Fold step begin done) = P.postscanlMx' step begin done
1025−
1026−-- XXX toPrescanl
1027−
1028------------------------------------------------------------------------------
778------------------------------------------------------------------------------
1029-- Filtering
779-- Filtering
1030------------------------------------------------------------------------------
780------------------------------------------------------------------------------
@@ -1084,793 +834,6 @@ ltakeWhile predicate (Fold step initial done) = Fold step' initial' done'
1084 done' (Left' r) = done r
834 done' (Left' r) = done r
1085 done' (Right' r) = done r
835 done' (Right' r) = done r
1086
836
1087−------------------------------------------------------------------------------
1088−-- Grouping/Splitting
1089−------------------------------------------------------------------------------
1090−
1091−------------------------------------------------------------------------------
1092−-- Grouping without looking at elements
1093−------------------------------------------------------------------------------
1094−--
1095−------------------------------------------------------------------------------
1096−-- Binary APIs
1097−------------------------------------------------------------------------------
1098−--
1099−
1100−-- | @splitAt n f1 f2@ composes folds @f1@ and @f2@ such that first @n@
1101−-- elements of its input are consumed by fold @f1@ and the rest of the stream
1102−-- is consumed by fold @f2@.
1103−--
1104−-- > let splitAt_ n xs = FL.foldl' (FL.splitAt n FL.toList FL.toList) $ S.fromList xs
1105−--
1106−-- >>> splitAt_ 6 "Hello World!"
1107−-- > ("Hello ","World!")
1108−--
1109−-- >>> splitAt_ (-1) [1,2,3]
1110−-- > ([],[1,2,3])
1111−--
1112−-- >>> splitAt_ 0 [1,2,3]
1113−-- > ([],[1,2,3])
1114−--
1115−-- >>> splitAt_ 1 [1,2,3]
1116−-- > ([1],[2,3])
1117−--
1118−-- >>> splitAt_ 3 [1,2,3]
1119−-- > ([1,2,3],[])
1120−--
1121−-- >>> splitAt_ 4 [1,2,3]
1122−-- > ([1,2,3],[])
1123−--
1124−-- This can be considered as a two-fold version of 'ltake' where we take both
1125−-- the segments instead of discarding the leftover.
1126−--
1127−-- @since 0.7.0
1128−{-# INLINE _splitAt #-}
1129−_splitAt
1130− :: Monad m
1131− => Int
1132− -> Fold m a b
1133− -> Fold m a c
1134− -> Fold m a (b, c)
1135−_splitAt n (Fold stepL initialL extractL) (Fold stepR initialR extractR) =
1136− Fold step init extract
1137− where
1138− init = Tuple3' <$> return n <*> initialL <*> initialR
1139−
1140− step (Tuple3' i xL xR) input =
1141− if i > 0
1142− then stepL xL input >>= (\a -> return (Tuple3' (i - 1) a xR))
1143− else stepR xR input >>= (\b -> return (Tuple3' i xL b))
1144−
1145− extract (Tuple3' _ a b) = (,) <$> extractL a <*> extractR b
1146−
1147−------------------------------------------------------------------------------
1148−-- N-ary APIs
1149−------------------------------------------------------------------------------
1150−
1151−------------------------------------------------------------------------------
1152−-- Generalized grouping
1153−------------------------------------------------------------------------------
1154−
1155−-- This combinator is the most general grouping combinator and can be used to
1156−-- implement all other grouping combinators.
1157−--
1158−-- XXX check if this can implement the splitOn combinator i.e. we can slide in
1159−-- new elements, slide out old elements and incrementally compute the hash.
1160−-- Also, can we implement the windowed classification combinators using this?
1161−--
1162−-- In fact this is a parse. Instead of using a special return value in the fold
1163−-- we are using a mapping function.
1164−--
1165−-- Note that 'scanl'' (usually followed by a map to extract the desired value
1166−-- from the accumulator) can be used to realize many implementations e.g. a
1167−-- sliding window implementation. A scan followed by a mapMaybe is also a good
1168−-- pattern to express many problems where we want to emit a filtered output and
1169−-- not emit an output on every input.
1170−--
1171−-- Passing on of the initial accumulator value to the next fold is equivalent
1172−-- to returning the leftover concept.
1173−
1174−{-
1175−-- | @groupScan splitter fold stream@ folds the input stream using @fold@.
1176−-- @splitter@ is applied on the accumulator of the fold every time an item is
1177−-- consumed by the fold. The fold continues until @splitter@ returns a 'Just'
1178−-- value. A 'Just' result from the @splitter@ specifies a result to be emitted
1179−-- in the output stream and the initial value of the accumulator for the next
1180−-- group's fold. This allows us to control whether to start fresh for the next
1181−-- fold or to continue from the previous fold's output.
1182−--
1183−{-# INLINE groupScan #-}
1184−groupScan
1185− :: (IsStream t, Monad m)
1186− => (x -> m (Maybe (b, x))) -> Fold m a x -> t m a -> t m b
1187−groupScan split fold m = undefined
1188−-}
1189−
1190−-- | Group the input stream into groups of @n@ elements each and then fold each
1191−-- group using the provided fold function.
1192−--
1193−-- >> S.toList $ FL.chunksOf 2 FL.sum (S.enumerateFromTo 1 10)
1194−-- > [3,7,11,15,19]
1195−--
1196−-- This can be considered as an n-fold version of 'ltake' where we apply
1197−-- 'ltake' repeatedly on the leftover stream until the stream exhausts.
1198−--
1199−-- @since 0.7.0
1200−{-# INLINE chunksOf #-}
1201−chunksOf
1202− :: (IsStream t, Monad m)
1203− => Int -> Fold m a b -> t m a -> t m b
1204−chunksOf n f m = D.fromStreamD $ D.groupsOf n f (D.toStreamD m)
1205−
1206−-- | Transform a fold from a pure input to a 'Maybe' input, consuming only
1207−-- 'Just' values.
1208−{-# INLINE lcatMaybes #-}
1209−lcatMaybes :: Monad m => Fold m a b -> Fold m (Maybe a) b
1210−lcatMaybes = lfilter isJust . lmap fromJust
1211−
1212−-- XXX we can implement this by repeatedly applying the 'lrunFor' fold.
1213−-- XXX add this example after fixing the serial stream rate control
1214−-- >>> S.toList $ S.take 5 $ sessionsOf 1 FL.sum $ constRate 2 $ S.enumerateFrom 1
1215−-- > [3,7,11,15,19]
1216−--
1217−-- | Group the input stream into windows of @n@ second each and then fold each
1218−-- group using the provided fold function.
1219−--
1220−-- @since 0.7.0
1221−{-# INLINE sessionsOf #-}
1222−sessionsOf
1223− :: (IsStream t, MonadAsync m)
1224− => Double -> Fold m a b -> t m a -> t m b
1225−sessionsOf n f xs =
1226− splitBySuffix isNothing (lcatMaybes f)
1227− (intersperseByTime n (return Nothing) (S.map Just xs))
1228− where
1229− intersperseByTime n' f' xs' = xs' `Par.parallelEndByFirst` S.repeatM timed
1230− where timed = liftIO (threadDelay (round $ n' * 1000000)) >> f'
1231−
1232−------------------------------------------------------------------------------
1233−-- Element Aware APIs
1234−------------------------------------------------------------------------------
1235−--
1236−------------------------------------------------------------------------------
1237−-- Binary APIs
1238−------------------------------------------------------------------------------
1239−
1240−-- | Break the input stream into two groups, the first group takes the input as
1241−-- long as the predicate applied to the first element of the stream and next
1242−-- input element holds 'True', the second group takes the rest of the input.
1243−--
1244−_spanBy
1245− :: Monad m
1246− => (a -> a -> Bool)
1247− -> Fold m a b
1248− -> Fold m a c
1249− -> Fold m a (b, c)
1250−_spanBy cmp (Fold stepL initialL extractL) (Fold stepR initialR extractR) =
1251− Fold step init extract
1252−
1253− where
1254− init = Tuple3' <$> initialL <*> initialR <*> return (Tuple' Nothing True)
1255−
1256− step (Tuple3' a b (Tuple' (Just frst) isFirstG)) input =
1257− if cmp frst input && isFirstG
1258− then stepL a input
1259− >>= (\a' -> return (Tuple3' a' b (Tuple' (Just frst) isFirstG)))
1260− else stepR b input
1261− >>= (\a' -> return (Tuple3' a a' (Tuple' Nothing False)))
1262−
1263− step (Tuple3' a b (Tuple' Nothing isFirstG)) input =
1264− if isFirstG
1265− then stepL a input
1266− >>= (\a' -> return (Tuple3' a' b (Tuple' (Just input) isFirstG)))
1267− else stepR b input
1268− >>= (\a' -> return (Tuple3' a a' (Tuple' Nothing False)))
1269−
1270− extract (Tuple3' a b _) = (,) <$> extractL a <*> extractR b
1271−
1272−-- | @span p f1 f2@ composes folds @f1@ and @f2@ such that @f1@ consumes the
1273−-- input as long as the predicate @p@ is 'True'. @f2@ consumes the rest of the
1274−-- input.
1275−--
1276−-- > let span_ p xs = FL.foldl' (FL.span p FL.toList FL.toList) $ S.fromList xs
1277−--
1278−-- >>> span_ (< 1) [1,2,3]
1279−-- > ([],[1,2,3])
1280−--
1281−-- >>> span_ (< 2) [1,2,3]
1282−-- > ([1],[2,3])
1283−--
1284−-- >>> span_ (< 4) [1,2,3]
1285−-- > ([1,2,3],[])
1286−--
1287−-- @since 0.7.0
1288−
1289−-- This can be considered as a two-fold version of 'ltakeWhile' where we take
1290−-- both the segments instead of discarding the leftover.
1291−{-# INLINE span #-}
1292−span
1293− :: Monad m
1294− => (a -> Bool)
1295− -> Fold m a b
1296− -> Fold m a c
1297− -> Fold m a (b, c)
1298−span p (Fold stepL initialL extractL) (Fold stepR initialR extractR) =
1299− Fold step init extract
1300−
1301− where
1302− init = Tuple3' <$> initialL <*> initialR <*> return True
1303−
1304− step (Tuple3' a b isFirstG) input =
1305− if isFirstG && p input
1306− then stepL a input >>= (\a' -> return (Tuple3' a' b True))
1307− else stepR b input >>= (\a' -> return (Tuple3' a a' False))
1308−
1309− extract (Tuple3' a b _) = (,) <$> extractL a <*> extractR b
1310−
1311−-- |
1312−-- > break p = span (not . p)
1313−--
1314−-- Break as soon as the predicate becomes 'True'. @break p f1 f2@ composes
1315−-- folds @f1@ and @f2@ such that @f1@ stops consuming input as soon as the
1316−-- predicate @p@ becomes 'True'. The rest of the input is consumed @f2@.
1317−--
1318−-- This is the binary version of 'splitBy'.
1319−--
1320−-- > let break_ p xs = FL.foldl' (FL.break p FL.toList FL.toList) $ S.fromList xs
1321−--
1322−-- >>> break_ (< 1) [3,2,1]
1323−-- > ([3,2,1],[])
1324−--
1325−-- >>> break_ (< 2) [3,2,1]
1326−-- > ([3,2],[1])
1327−--
1328−-- >>> break_ (< 4) [3,2,1]
1329−-- > ([],[3,2,1])
1330−--
1331−-- @since 0.7.0
1332−{-# INLINE _break #-}
1333−_break
1334− :: Monad m
1335− => (a -> Bool)
1336− -> Fold m a b
1337− -> Fold m a c
1338− -> Fold m a (b, c)
1339−_break p = span (not . p)
1340−
1341−-- | Like 'spanBy' but applies the predicate in a rolling fashion i.e.
1342−-- predicate is applied to the previous and the next input elements.
1343−{-# INLINE _spanRollingBy #-}
1344−_spanRollingBy
1345− :: Monad m
1346− => (a -> a -> Bool)
1347− -> Fold m a b
1348− -> Fold m a c
1349− -> Fold m a (b, c)
1350−_spanRollingBy cmp (Fold stepL initialL extractL) (Fold stepR initialR extractR) =
1351− Fold step init extract
1352−
1353− where
1354− init = Tuple3' <$> initialL <*> initialR <*> return Nothing
1355−
1356− step (Tuple3' a b (Just frst)) input =
1357− if cmp input frst
1358− then stepL a input >>= (\a' -> return (Tuple3' a' b (Just input)))
1359− else stepR b input >>= (\b' -> return (Tuple3' a b' (Just input)))
1360−
1361− step (Tuple3' a b Nothing) input =
1362− stepL a input >>= (\a' -> return (Tuple3' a' b (Just input)))
1363−
1364− extract (Tuple3' a b _) = (,) <$> extractL a <*> extractR b
1365−
1366−------------------------------------------------------------------------------
1367−-- N-ary APIs
1368−------------------------------------------------------------------------------
1369−--
1370−-- | @groupsBy cmp f $ S.fromList [a,b,c,...]@ assigns the element @a@ to the
1371−-- first group, if @a \`cmp` b@ is 'True' then @b@ is also assigned to the same
1372−-- group. If @a \`cmp` c@ is 'True' then @c@ is also assigned to the same
1373−-- group and so on. When the comparison fails a new group is started. Each
1374−-- group is folded using the fold @f@ and the result of the fold is emitted in
1375−-- the output stream.
1376−--
1377−-- >>> S.toList $ FL.groupsBy (>) FL.toList $ S.fromList [1,3,7,0,2,5]
1378−-- > [[1,3,7],[0,2,5]]
1379−--
1380−-- @since 0.7.0
1381−{-# INLINE groupsBy #-}
1382−groupsBy
1383− :: (IsStream t, Monad m)
1384− => (a -> a -> Bool)
1385− -> Fold m a b
1386− -> t m a
1387− -> t m b
1388−groupsBy cmp f m = D.fromStreamD $ D.groupsBy cmp f (D.toStreamD m)
1389−
1390−-- | Unlike @groupsBy@ this function performs a rolling comparison of two
1391−-- successive elements in the input stream. @groupsByRolling cmp f $ S.fromList
1392−-- [a,b,c,...]@ assigns the element @a@ to the first group, if @a \`cmp` b@ is
1393−-- 'True' then @b@ is also assigned to the same group. If @b \`cmp` c@ is
1394−-- 'True' then @c@ is also assigned to the same group and so on. When the
1395−-- comparison fails a new group is started. Each group is folded using the fold
1396−-- @f@.
1397−--
1398−-- >>> S.toList $ FL.groupsByRolling (\a b -> a + 1 == b) FL.toList $ S.fromList [1,2,3,7,8,9]
1399−-- > [[1,2,3],[7,8,9]]
1400−--
1401−-- @since 0.7.0
1402−{-# INLINE groupsByRolling #-}
1403−groupsByRolling
1404− :: (IsStream t, Monad m)
1405− => (a -> a -> Bool)
1406− -> Fold m a b
1407− -> t m a
1408− -> t m b
1409−groupsByRolling cmp f m = D.fromStreamD $ D.groupsRollingBy cmp f (D.toStreamD m)
1410−
1411−-- |
1412−-- > groups = groupsBy (==)
1413−-- > groups = groupsByRolling (==)
1414−--
1415−-- Groups contiguous spans of equal elements together in individual groups.
1416−--
1417−-- >>> S.toList $ FL.groups FL.toList $ S.fromList [1,1,2,2]
1418−-- > [[1,1],[2,2]]
1419−--
1420−-- @since 0.7.0
1421−groups :: (IsStream t, Monad m, Eq a) => Fold m a b -> t m a -> t m b
1422−groups = groupsBy (==)
1423−
1424−------------------------------------------------------------------------------
1425−-- Binary splitting on a separator
1426−------------------------------------------------------------------------------
1427−
1428−{-
1429−-- | Find the first occurrence of the specified sequence in the input stream
1430−-- and break the input stream into two parts, the first part consisting of the
1431−-- stream before the sequence and the second part consisting of the sequence
1432−-- and the rest of the stream.
1433−--
1434−-- > let breakOn_ pat xs = FL.foldl' (FL.breakOn pat FL.toList FL.toList) $ S.fromList xs
1435−--
1436−-- >>> breakOn_ "dear" "Hello dear world!"
1437−-- > ("Hello ","dear world!")
1438−--
1439−{-# INLINE breakOn #-}
1440−breakOn :: Monad m => Array a -> Fold m a b -> Fold m a c -> Fold m a (b,c)
1441−breakOn pat f m = undefined
1442−-}
1443−
1444−------------------------------------------------------------------------------
1445−-- N-ary split on a predicate
1446−------------------------------------------------------------------------------
1447−
1448−-- TODO: Use a Splitter configuration similar to the "split" package to make it
1449−-- possible to express all splitting combinations. In general, we can have
1450−-- infix/suffix/prefix/condensing of separators, dropping both leading/trailing
1451−-- separators. We can have a single split operation taking the splitter config
1452−-- as argument.
1453−
1454−-- | Split a stream on separator elements determined by a predicate, dropping
1455−-- the separator. Separator is considered as infixed between two segments, if
1456−-- one side of the separator is missing then it is parsed as an empty stream.
1457−-- With '-' representing elements and '.' as separator, 'splitOn' splits as
1458−-- follows:
1459−--
1460−-- @
1461−-- "--.--" => "--" "--"
1462−-- "--." => "--" ""
1463−-- ".--" => "" "--"
1464−-- @
1465−--
1466−-- @splitOn (== x)@ is an inverse of @intercalate (S.yield x)@
1467−--
1468−-- Let's use the following definition for illustration:
1469−--
1470−-- > splitOn' p xs = S.toList $ FL.splitOn p (FL.toList) (S.fromList xs)
1471−--
1472−-- >>> splitOn' (== '.') ""
1473−-- [""]
1474−--
1475−-- >>> splitOn' (== '.') "."
1476−-- ["",""]
1477−--
1478−-- >>> splitOn' (== '.') ".a"
1479−-- > ["","a"]
1480−--
1481−-- >>> splitOn' (== '.') "a."
1482−-- > ["a",""]
1483−--
1484−-- >>> splitOn' (== '.') "a.b"
1485−-- > ["a","b"]
1486−--
1487−-- >>> splitOn' (== '.') "a..b"
1488−-- > ["a","","b"]
1489−--
1490−-- @since 0.7.0
1491−
1492−-- This can be considered as an n-fold version of 'breakOn' where we apply
1493−-- 'breakOn' successively on the input stream, dropping the first element
1494−-- of the second segment after each break.
1495−--
1496−{-# INLINE splitOn #-}
1497−splitOn
1498− :: (IsStream t, Monad m)
1499− => (a -> Bool) -> Fold m a b -> t m a -> t m b
1500−splitOn predicate f m =
1501− D.fromStreamD $ D.splitBy predicate f (D.toStreamD m)
1502−
1503−-- | Like 'splitOn' but the separator is considered as suffixed to the segments
1504−-- in the stream. A missing suffix at the end is allowed. A separator at the
1505−-- beginning is parsed as empty segment. With '-' representing elements and
1506−-- '.' as separator, 'splitOnSuffix' splits as follows:
1507−--
1508−-- @
1509−-- "--.--." => "--" "--"
1510−-- "--.--" => "--" "--"
1511−-- ".--." => "" "--"
1512−-- @
1513−--
1514−-- > splitOnSuffix' p xs = S.toList $ FL.splitSuffixBy p (FL.toList) (S.fromList xs)
1515−--
1516−-- >>> splitOnSuffix' (== '.') ""
1517−-- []
1518−--
1519−-- >>> splitOnSuffix' (== '.') "."
1520−-- [""]
1521−--
1522−-- >>> splitOnSuffix' (== '.') "a"
1523−-- ["a"]
1524−--
1525−-- >>> splitOnSuffix' (== '.') ".a"
1526−-- > ["","a"]
1527−--
1528−-- >>> splitOnSuffix' (== '.') "a."
1529−-- > ["a"]
1530−--
1531−-- >>> splitOnSuffix' (== '.') "a.b"
1532−-- > ["a","b"]
1533−--
1534−-- >>> splitOnSuffix' (== '.') "a.b."
1535−-- > ["a","b"]
1536−--
1537−-- >>> splitOnSuffix' (== '.') "a..b.."
1538−-- > ["a","","b",""]
1539−--
1540−-- > lines = splitOnSuffix (== '\n')
1541−--
1542−-- @since 0.7.0
1543−
1544−-- This can be considered as an n-fold version of 'breakPost' where we apply
1545−-- 'breakPost' successively on the input stream, dropping the first element
1546−-- of the second segment after each break.
1547−--
1548−{-# INLINE splitOnSuffix #-}
1549−splitOnSuffix
1550− :: (IsStream t, Monad m)
1551− => (a -> Bool) -> Fold m a b -> t m a -> t m b
1552−splitOnSuffix predicate f m =
1553− D.fromStreamD $ D.splitSuffixBy predicate f (D.toStreamD m)
1554−
1555−-- | Like 'splitBy' but ignores repeated separators or separators in leading
1556−-- or trailing position. Therefore, @"..a..b.."@ would be parsed as
1557−-- @["a","b"]@. In other words, it treats the input like words separated by
1558−-- whitespace elements determined by the predicate.
1559−--
1560−-- > wordsBy' p xs = S.toList $ FL.wordsBy p (FL.toList) (S.fromList xs)
1561−--
1562−-- >>> wordsBy' (== ',') ""
1563−-- > []
1564−--
1565−-- >>> wordsBy' (== ',') ","
1566−-- > []
1567−--
1568−-- >>> wordsBy' (== ',') ",a,,b,"
1569−-- > ["a","b"]
1570−--
1571−-- > words = wordsBy isSpace
1572−--
1573−-- @since 0.7.0
1574−{-# INLINE wordsBy #-}
1575−wordsBy
1576− :: (IsStream t, Monad m)
1577− => (a -> Bool) -> Fold m a b -> t m a -> t m b
1578−wordsBy predicate f m =
1579− D.fromStreamD $ D.wordsBy predicate f (D.toStreamD m)
1580−
1581−-- | Like 'splitOnSuffix' but keeps the suffix attached to the resulting
1582−-- splits.
1583−--
1584−-- > splitBySuffix' p xs = S.toList $ FL.splitBySuffix p (FL.toList) (S.fromList xs)
1585−--
1586−-- >>> splitBySuffix' (== '.') ""
1587−-- []
1588−--
1589−-- >>> splitBySuffix' (== '.') "."
1590−-- ["."]
1591−--
1592−-- >>> splitBySuffix' (== '.') "a"
1593−-- ["a"]
1594−--
1595−-- >>> splitBySuffix' (== '.') ".a"
1596−-- > [".","a"]
1597−--
1598−-- >>> splitBySuffix' (== '.') "a."
1599−-- > ["a."]
1600−--
1601−-- >>> splitBySuffix' (== '.') "a.b"
1602−-- > ["a.","b"]
1603−--
1604−-- >>> splitBySuffix' (== '.') "a.b."
1605−-- > ["a.","b."]
1606−--
1607−-- >>> splitBySuffix' (== '.') "a..b.."
1608−-- > ["a.",".","b.","."]
1609−--
1610−-- @since 0.7.0
1611−
1612−-- This can be considered as an n-fold version of 'breakPost' where we apply
1613−-- 'breakPost' successively on the input stream.
1614−--
1615−{-# INLINE splitBySuffix #-}
1616−splitBySuffix
1617− :: (IsStream t, Monad m)
1618− => (a -> Bool) -> Fold m a b -> t m a -> t m b
1619−splitBySuffix predicate f m =
1620− D.fromStreamD $ D.splitSuffixBy' predicate f (D.toStreamD m)
1621−
1622−------------------------------------------------------------------------------
1623−-- Split on a delimiter sequence
1624−------------------------------------------------------------------------------
1625−
1626−-- Int list examples for splitOn:
1627−--
1628−-- >>> splitList [] [1,2,3,3,4]
1629−-- > [[1],[2],[3],[3],[4]]
1630−--
1631−-- >>> splitList [5] [1,2,3,3,4]
1632−-- > [[1,2,3,3,4]]
1633−--
1634−-- >>> splitList [1] [1,2,3,3,4]
1635−-- > [[],[2,3,3,4]]
1636−--
1637−-- >>> splitList [4] [1,2,3,3,4]
1638−-- > [[1,2,3,3],[]]
1639−--
1640−-- >>> splitList [2] [1,2,3,3,4]
1641−-- > [[1],[3,3,4]]
1642−--
1643−-- >>> splitList [3] [1,2,3,3,4]
1644−-- > [[1,2],[],[4]]
1645−--
1646−-- >>> splitList [3,3] [1,2,3,3,4]
1647−-- > [[1,2],[4]]
1648−--
1649−-- >>> splitList [1,2,3,3,4] [1,2,3,3,4]
1650−-- > [[],[]]
1651−
1652−-- | Like 'splitOn' but the separator is a sequence of elements instead of a
1653−-- single element.
1654−--
1655−-- For illustration, let's define a function that operates on pure lists:
1656−--
1657−-- @
1658−-- splitOnSeq' pat xs = S.toList $ FL.splitOnSeq (A.fromList pat) (FL.toList) (S.fromList xs)
1659−-- @
1660−--
1661−-- >>> splitOnSeq' "" "hello"
1662−-- > ["h","e","l","l","o"]
1663−--
1664−-- >>> splitOnSeq' "hello" ""
1665−-- > [""]
1666−--
1667−-- >>> splitOnSeq' "hello" "hello"
1668−-- > ["",""]
1669−--
1670−-- >>> splitOnSeq' "x" "hello"
1671−-- > ["hello"]
1672−--
1673−-- >>> splitOnSeq' "h" "hello"
1674−-- > ["","ello"]
1675−--
1676−-- >>> splitOnSeq' "o" "hello"
1677−-- > ["hell",""]
1678−--
1679−-- >>> splitOnSeq' "e" "hello"
1680−-- > ["h","llo"]
1681−--
1682−-- >>> splitOnSeq' "l" "hello"
1683−-- > ["he","","o"]
1684−--
1685−-- >>> splitOnSeq' "ll" "hello"
1686−-- > ["he","o"]
1687−--
1688−-- 'splitOnSeq' is an inverse of 'intercalate'. The following law always holds:
1689−--
1690−-- > intercalate . splitOn == id
1691−--
1692−-- The following law holds when the separator is non-empty and contains none of
1693−-- the elements present in the input lists:
1694−--
1695−-- > splitOn . intercalate == id
1696−--
1697−-- @since 0.7.0
1698−
1699−-- XXX We can use a polymorphic vector implemented by Array# to represent the
1700−-- sequence, that way we can avoid the Storable constraint. If we still need
1701−-- Storable Array for performance, we can use a separate splitOnArray API for
1702−-- that. We can also have an API where the sequence itself is a lazy stream, so
1703−-- that we can search files in files for example.
1704−{-# INLINE splitOnSeq #-}
1705−splitOnSeq
1706− :: (IsStream t, MonadIO m, Storable a, Enum a, Eq a)
1707− => Array a -> Fold m a b -> t m a -> t m b
1708−splitOnSeq patt f m = D.fromStreamD $ D.splitOn patt f (D.toStreamD m)
1709−
1710−{-
1711−-- This can be implemented easily using Rabin Karp
1712−-- | Split on any one of the given patterns.
1713−{-# INLINE splitOnAny #-}
1714−splitOnAny
1715− :: (IsStream t, Monad m, Storable a, Integral a)
1716− => [Array a] -> Fold m a b -> t m a -> t m b
1717−splitOnAny subseq f m = undefined -- D.fromStreamD $ D.splitOnAny f subseq (D.toStreamD m)
1718−-}
1719−
1720−-- | Like 'splitSuffixBy' but the separator is a sequence of elements, instead
1721−-- of a predicate for a single element.
1722−--
1723−-- > splitSuffixOn_ pat xs = S.toList $ FL.splitSuffixOn (A.fromList pat) (FL.toList) (S.fromList xs)
1724−--
1725−-- >>> splitSuffixOn_ "." ""
1726−-- [""]
1727−--
1728−-- >>> splitSuffixOn_ "." "."
1729−-- [""]
1730−--
1731−-- >>> splitSuffixOn_ "." "a"
1732−-- ["a"]
1733−--
1734−-- >>> splitSuffixOn_ "." ".a"
1735−-- > ["","a"]
1736−--
1737−-- >>> splitSuffixOn_ "." "a."
1738−-- > ["a"]
1739−--
1740−-- >>> splitSuffixOn_ "." "a.b"
1741−-- > ["a","b"]
1742−--
1743−-- >>> splitSuffixOn_ "." "a.b."
1744−-- > ["a","b"]
1745−--
1746−-- >>> splitSuffixOn_ "." "a..b.."
1747−-- > ["a","","b",""]
1748−--
1749−-- > lines = splitSuffixOn "\n"
1750−--
1751−-- @since 0.7.0
1752−{-# INLINE _splitOnSuffixSeq #-}
1753−_splitOnSuffixSeq
1754− :: (IsStream t, MonadIO m, Storable a, Enum a, Eq a)
1755− => Array a -> Fold m a b -> t m a -> t m b
1756−_splitOnSuffixSeq patt f m =
1757− D.fromStreamD $ D.splitSuffixOn False patt f (D.toStreamD m)
1758−
1759−{-
1760−-- | Like 'splitOn' but drops any empty splits.
1761−--
1762−{-# INLINE wordsOn #-}
1763−wordsOn
1764− :: (IsStream t, Monad m, Storable a, Eq a)
1765− => Array a -> Fold m a b -> t m a -> t m b
1766−wordsOn subseq f m = undefined -- D.fromStreamD $ D.wordsOn f subseq (D.toStreamD m)
1767−-}
1768−
1769−-- XXX use a non-monadic intersperse to remove the MonadAsync constraint.
1770−--
1771−-- | Like 'splitOnSeq' but splits the separator as well, as an infix token.
1772−--
1773−-- > splitOn'_ pat xs = S.toList $ FL.splitOn' (A.fromList pat) (FL.toList) (S.fromList xs)
1774−--
1775−-- >>> splitOn'_ "" "hello"
1776−-- > ["h","","e","","l","","l","","o"]
1777−--
1778−-- >>> splitOn'_ "hello" ""
1779−-- > [""]
1780−--
1781−-- >>> splitOn'_ "hello" "hello"
1782−-- > ["","hello",""]
1783−--
1784−-- >>> splitOn'_ "x" "hello"
1785−-- > ["hello"]
1786−--
1787−-- >>> splitOn'_ "h" "hello"
1788−-- > ["","h","ello"]
1789−--
1790−-- >>> splitOn'_ "o" "hello"
1791−-- > ["hell","o",""]
1792−--
1793−-- >>> splitOn'_ "e" "hello"
1794−-- > ["h","e","llo"]
1795−--
1796−-- >>> splitOn'_ "l" "hello"
1797−-- > ["he","l","","l","o"]
1798−--
1799−-- >>> splitOn'_ "ll" "hello"
1800−-- > ["he","ll","o"]
1801−--
1802−-- @since 0.7.0
1803−{-# INLINE _splitBySeq #-}
1804−_splitBySeq
1805− :: (IsStream t, MonadAsync m, Storable a, Enum a, Eq a)
1806− => Array a -> Fold m a b -> t m a -> t m b
1807−_splitBySeq patt f m = S.intersperseM (foldl' f (A.read patt)) $ splitOnSeq patt f m
1808−
1809−-- | Like 'splitSuffixOn' but keeps the suffix intact in the splits.
1810−--
1811−-- > splitSuffixOn'_ pat xs = S.toList $ FL.splitSuffixOn' (A.fromList pat) (FL.toList) (S.fromList xs)
1812−--
1813−-- >>> splitSuffixOn'_ "." ""
1814−-- [""]
1815−--
1816−-- >>> splitSuffixOn'_ "." "."
1817−-- ["."]
1818−--
1819−-- >>> splitSuffixOn'_ "." "a"
1820−-- ["a"]
1821−--
1822−-- >>> splitSuffixOn'_ "." ".a"
1823−-- > [".","a"]
1824−--
1825−-- >>> splitSuffixOn'_ "." "a."
1826−-- > ["a."]
1827−--
1828−-- >>> splitSuffixOn'_ "." "a.b"
1829−-- > ["a.","b"]
1830−--
1831−-- >>> splitSuffixOn'_ "." "a.b."
1832−-- > ["a.","b."]
1833−--
1834−-- >>> splitSuffixOn'_ "." "a..b.."
1835−-- > ["a.",".","b.","."]
1836−--
1837−-- @since 0.7.0
1838−{-# INLINE _splitBySuffixSeq #-}
1839−_splitBySuffixSeq
1840− :: (IsStream t, MonadIO m, Storable a, Enum a, Eq a)
1841− => Array a -> Fold m a b -> t m a -> t m b
1842−_splitBySuffixSeq patt f m =
1843− D.fromStreamD $ D.splitSuffixOn True patt f (D.toStreamD m)
1844−
1845−{-
1846−-- This can be implemented easily using Rabin Karp
1847−-- | Split post any one of the given patterns.
1848−{-# INLINE splitSuffixOnAny #-}
1849−splitSuffixOnAny
1850− :: (IsStream t, Monad m, Storable a, Integral a)
1851− => [Array a] -> Fold m a b -> t m a -> t m b
1852−splitSuffixOnAny subseq f m = undefined
1853− -- D.fromStreamD $ D.splitPostAny f subseq (D.toStreamD m)
1854−-}
1855−
1856−------------------------------------------------------------------------------
1857−-- Reorder in sequence
1858−------------------------------------------------------------------------------
1859−
1860−{-
1861−-- Buffer until the next element in sequence arrives. The function argument
1862−-- determines the difference in sequence numbers. This could be useful in
1863−-- implementing sequenced streams, for example, TCP reassembly.
1864−{-# INLINE reassembleBy #-}
1865−reassembleBy
1866− :: (IsStream t, Monad m)
1867− => Fold m a b
1868− -> (a -> a -> Int)
1869− -> t m a
1870− -> t m b
1871−reassembleBy = undefined
1872−-}
1873−
1874------------------------------------------------------------------------------
837------------------------------------------------------------------------------
1875-- Distributing
838-- Distributing
1876------------------------------------------------------------------------------
839------------------------------------------------------------------------------
@@ -2417,318 +1380,3 @@ _lsessionsOf n (Fold step1 initial1 extract1) (Fold step2 initial2 extract2) =
2417 Left _ -> r2
1380 Left _ -> r2
2418 Right _ -> Left e
1381 Right _ -> Left e
2419 putMVar mv2 r
1382 putMVar mv2 r
2420−
2421−------------------------------------------------------------------------------
2422−-- Windowed classification
2423−------------------------------------------------------------------------------
2424−
2425−-- We divide the stream into windows or chunks in space or time and each window
2426−-- can be associated with a key, all events associated with a particular key in
2427−-- the window can be folded to a single result. The stream can be split into
2428−-- windows by size or by using a split predicate on the elements in the stream.
2429−-- For example, when we receive a closing flag, we can close the window.
2430−--
2431−-- A "chunk" is a space window and a "session" is a time window. Are there any
2432−-- other better short words to describe them. An alternative is to use
2433−-- "swindow" and "twindow". Another word for "session" could be "spell".
2434−--
2435−-- TODO: To mark the position in space or time we can have Indexed or
2436−-- TimeStamped types. That can make it easy to deal with the position indices
2437−-- or timestamps.
2438−
2439−------------------------------------------------------------------------------
2440−-- Keyed Sliding Windows
2441−------------------------------------------------------------------------------
2442−
2443−{-
2444−{-# INLINABLE classifySlidingChunks #-}
2445−classifySlidingChunks
2446− :: (IsStream t, MonadAsync m, Ord k)
2447− => Int -- ^ window size
2448− -> Int -- ^ window slide
2449− -> Fold m a b -- ^ Fold to be applied to window events
2450− -> t m (k, a, Bool) -- ^ window key, data, close event
2451− -> t m (k, b)
2452−classifySlidingChunks wsize wslide (Fold step initial extract) str
2453− = undefined
2454−
2455−-- XXX Another variant could be to slide the window on an event, e.g. in TCP we
2456−-- slide the send window when an ack is received and we slide the receive
2457−-- window when a sequence is complete. Sliding is stateful in case of TCP,
2458−-- sliding releases the send buffer or makes data available to the user from
2459−-- the receive buffer.
2460−{-# INLINABLE classifySlidingSessions #-}
2461−classifySlidingSessions
2462− :: (IsStream t, MonadAsync m, Ord k)
2463− => Double -- ^ timer tick in seconds
2464− -> Double -- ^ time window size
2465− -> Double -- ^ window slide
2466− -> Fold m a b -- ^ Fold to be applied to window events
2467− -> t m (k, a, Bool, AbsTime) -- ^ window key, data, close flag, timestamp
2468− -> t m (k, b)
2469−classifySlidingSessions tick interval slide (Fold step initial extract) str
2470− = undefined
2471−-}
2472−
2473−------------------------------------------------------------------------------
2474−-- Sliding Window Buffers
2475−------------------------------------------------------------------------------
2476−
2477−-- These buffered versions could be faster than concurrent incremental folds of
2478−-- all overlapping windows as in many cases we may not need all the values to
2479−-- compute the fold, we can just compute the result using the old value and new
2480−-- value. However, we may need the buffer once in a while, for example for
2481−-- string search we usually compute the hash incrementally but when the hash
2482−-- matches the hash of the pattern we need to compare the whole string.
2483−--
2484−-- XXX we should be able to implement sequence based splitting combinators
2485−-- using this combinator.
2486−
2487−{-
2488−-- | Buffer n elements of the input in a ring buffer. When t new elements are
2489−-- collected, slide the window to remove the same number of oldest elements,
2490−-- insert the new elements, and apply an incremental fold on the sliding
2491−-- window, supplying the outgoing elements, the new ring buffer as arguments.
2492−slidingChunkBuffer
2493− :: (IsStream t, Monad m, Ord a, Storable a)
2494− => Int -- window size
2495− -> Int -- window slide
2496− -> Fold m (Ring a, Array a) b
2497− -> t m a
2498− -> t m b
2499−slidingChunkBuffer = undefined
2500−
2501−-- Buffer n seconds worth of stream elements of the input in a radix tree.
2502−-- Every t seconds, remove the items that are older than n seconds, and apply
2503−-- an incremental fold on the sliding window, supplying the outgoing elements,
2504−-- and the new radix tree buffer as arguments.
2505−slidingSessionBuffer
2506− :: (IsStream t, Monad m, Ord a, Storable a)
2507− => Int -- window size
2508− -> Int -- tick size
2509− -> Fold m (RTree a, Array a) b
2510− -> t m a
2511− -> t m b
2512−slidingSessionBuffer = undefined
2513−-}
2514−
2515−------------------------------------------------------------------------------
2516−-- Keyed Session Windows
2517−------------------------------------------------------------------------------
2518−
2519−{-
2520−-- | Keyed variable size space windows. Close the window if we do not receive a
2521−-- window event in the next "spaceout" elements.
2522−{-# INLINABLE classifyChunksBy #-}
2523−classifyChunksBy
2524− :: (IsStream t, MonadAsync m, Ord k)
2525− => Int -- ^ window spaceout (spread)
2526− -> Bool -- ^ reset the spaceout when a chunk window element is received
2527− -> Fold m a b -- ^ Fold to be applied to chunk window elements
2528− -> t m (k, a, Bool) -- ^ chunk key, data, last element
2529− -> t m (k, b)
2530−classifyChunksBy spanout reset (Fold step initial extract) str = undefined
2531−
2532−-- | Like 'classifyChunksOf' but the chunk size is reset if an element is
2533−-- received within the chunk size window. The chunk gets closed only if no
2534−-- element is received within the chunk window.
2535−--
2536−{-# INLINABLE classifyKeepAliveChunks #-}
2537−classifyKeepAliveChunks
2538− :: (IsStream t, MonadAsync m, Ord k)
2539− => Int -- ^ window spaceout (spread)
2540− -> Fold m a b -- ^ Fold to be applied to chunk window elements
2541− -> t m (k, a, Bool) -- ^ chunk key, data, last element
2542− -> t m (k, b)
2543−classifyKeepAliveChunks spanout = classifyChunksBy spanout True
2544−-}
2545−
2546−-- | @classifySessionsBy tick timeout reset f stream@ groups together all input
2547−-- stream elements that belong to the same session. @timeout@ is the maximum
2548−-- lifetime of a session in seconds. All elements belonging to a session are
2549−-- purged after this duration. If "reset" is 'Ture' then the timeout is reset
2550−-- after every event received in the session. Session duration is measured
2551−-- using the timestamp of the first element seen for that session. To detect
2552−-- session timeouts, a monotonic event time clock is maintained using the
2553−-- timestamps seen in the inputs and a timer with a tick duration specified by
2554−-- @tick@.
2555−--
2556−-- @session key@ is a key that uniquely identifies the session for the given
2557−-- element, @timestamp@ characterizes the time when the input element was
2558−-- generated, this is an absolute time measured from some @Epoch@. @session
2559−-- close@ is a boolean indicating whether this element marks the closing of the
2560−-- session. When an input element with @session close@ set to @True@ is seen
2561−-- the session is purged immediately.
2562−--
2563−-- All the input elements belonging to a session are collected using the fold
2564−-- @f@. The session key and the fold result are emitted in the output stream
2565−-- when the session is purged either via the session close event or via the
2566−-- session liftime timeout.
2567−--
2568−-- @since 0.7.0
2569−{-# INLINABLE classifySessionsBy #-}
2570−classifySessionsBy
2571− :: (IsStream t, MonadAsync m, Ord k)
2572− => Double -- ^ timer tick in seconds
2573− -> Double -- ^ session timeout
2574− -> Bool -- ^ reset the timeout when an event is received
2575− -> Fold m a b -- ^ Fold to be applied to session events
2576− -> t m (k, a, Bool, AbsTime) -- ^ session key, timestamp, close event, data
2577− -> t m (k, b)
2578−classifySessionsBy tick timeout reset (Fold step initial extract) str =
2579− S.concatMap (\(Tuple4' _ _ _ s) -> s) $ S.scanlM' sstep szero stream
2580−
2581− where
2582−
2583− timeoutMs = toRelTime (round (timeout * 1000) :: MilliSecond64)
2584− tickMs = toRelTime (round (tick * 1000) :: MilliSecond64)
2585− szero = Tuple4' (toAbsTime (0 :: MilliSecond64)) H.empty Map.empty S.nil
2586−
2587− -- Got a new stream input element
2588− sstep (Tuple4' evTime hp mp _) (Just (key, a, closing, ts)) =
2589− -- XXX we should use a heap in pinned memory to scale it to a large
2590− -- size
2591− --
2592− -- deleting a key from the heap is expensive, so we never delete a
2593− -- key, we just purge it from the Map and it gets purged from the
2594− -- heap on timeout. We just need an extra lookup in the Map when
2595− -- the key is purged from the heap, that should not be expensive.
2596− --
2597− -- To detect session inactivity we keep a timestamp of the latest event
2598− -- in the Map along with the fold result. When we purge the session
2599− -- from the heap we match the timestamp in the heap with the timestamp
2600− -- in the Map, if the latest timestamp is newer and has not expired we
2601− -- reinsert the key in the heap.
2602− --
2603− -- XXX if the key is an Int, we can also use an IntMap for slightly
2604− -- better performance.
2605− --
2606− let accumulate v = do
2607− Tuple' _ old <- maybe (initial >>= return . Tuple' ts) return v
2608− new <- step old a
2609− return $ Tuple' ts new
2610− in if closing
2611− then do
2612− let (r, mp') = Map.updateLookupWithKey (\_ _ -> Nothing) key mp
2613− Tuple' _ acc <- accumulate r
2614− res <- extract acc
2615− return $ Tuple4' evTime hp mp' (S.yield (key, res))
2616− else do
2617− let r = Map.lookup key mp
2618− acc <- accumulate r
2619− let mp' = Map.insert key acc mp
2620− let hp' =
2621− case r of
2622− Nothing ->
2623− let expiry = addToAbsTime ts timeoutMs
2624− in H.insert (Entry expiry key) hp
2625− Just _ -> hp
2626− -- Event time is maintained as monotonically increasing
2627− -- time. If we have lagged behind any of the timestamps
2628− -- seen then we increase it to match the latest time seen
2629− -- in the timestamps. We also increase it on timer ticks.
2630− return $ Tuple4' (max evTime ts) hp' mp' S.nil
2631−
2632− -- Got a timer tick event
2633− -- XXX can we yield the entries without accumulating them?
2634− sstep (Tuple4' evTime heap sessions _) Nothing = do
2635− (hp', mp', out) <- go heap sessions S.nil
2636− return $ Tuple4' curTime hp' mp' out
2637−
2638− where
2639−
2640− curTime = addToAbsTime evTime tickMs
2641− go hp mp out = do
2642− let hres = H.uncons hp
2643− case hres of
2644− Just (Entry ts key, hp') -> do
2645− let duration = diffAbsTime curTime ts
2646− if duration >= timeoutMs
2647− then do
2648− let (r, mp') = Map.updateLookupWithKey
2649− (\_ _ -> Nothing) key mp
2650− case r of
2651− Nothing -> go hp' mp' out
2652− Just (Tuple' latestTS acc) -> do
2653− let dur = diffAbsTime curTime latestTS
2654− if dur >= timeoutMs || not reset
2655− then do
2656− sess <- extract acc
2657− go hp' mp' ((key, sess) `S.cons` out)
2658− else
2659− -- reset the session timeout
2660− let expiry = addToAbsTime latestTS timeoutMs
2661− hp'' = H.insert (Entry expiry key) hp'
2662− mp'' = Map.insert key (Tuple' latestTS acc) mp'
2663− in go hp'' mp'' out
2664− else return (hp, mp, out)
2665− Nothing -> return (hp, mp, out)
2666−
2667− -- merge timer events in the stream
2668− stream = S.map Just str `parallel` S.repeatM timer
2669− timer = do
2670− liftIO $ threadDelay (round $ tick * 1000000)
2671− return Nothing
2672−
2673−-- | Like 'classifySessionsOf' but the session is kept alive if an event is
2674−-- received within the session window. The session times out and gets closed
2675−-- only if no event is received within the specified session window size.
2676−--
2677−-- @since 0.7.0
2678−{-# INLINABLE _classifyKeepAliveSessions #-}
2679−_classifyKeepAliveSessions
2680− :: (IsStream t, MonadAsync m, Ord k)
2681− => Double -- ^ session inactive timeout
2682− -> Fold m a b -- ^ Fold to be applied to session payload data
2683− -> t m (k, a, Bool, AbsTime) -- ^ session key, data, close flag, timestamp
2684− -> t m (k, b)
2685−_classifyKeepAliveSessions timeout = classifySessionsBy 1 timeout True
2686−
2687−------------------------------------------------------------------------------
2688−-- Keyed tumbling windows
2689−------------------------------------------------------------------------------
2690−
2691−-- Tumbling windows is a special case of sliding windows where the window slide
2692−-- is the same as the window size. Or it can be a special case of session
2693−-- windows where the reset flag is set to False.
2694−
2695−-- XXX instead of using the early termination flag in the stream, we can use an
2696−-- early terminating fold instead.
2697−
2698−{-
2699−-- | Split the stream into fixed size chunks of specified size. Within each
2700−-- such chunk fold the elements in buckets identified by the keys. A particular
2701−-- bucket fold can be terminated early if a closing flag is encountered in an
2702−-- element for that key.
2703−--
2704−-- @since 0.7.0
2705−{-# INLINABLE classifyChunksOf #-}
2706−classifyChunksOf
2707− :: (IsStream t, MonadAsync m, Ord k)
2708− => Int -- ^ window size
2709− -> Fold m a b -- ^ Fold to be applied to window events
2710− -> t m (k, a, Bool) -- ^ window key, data, close event
2711− -> t m (k, b)
2712−classifyChunksOf wsize = classifyChunksBy wsize False
2713−-}
2714−
2715−-- | Split the stream into fixed size time windows of specified interval in
2716−-- seconds. Within each such window, fold the elements in buckets identified by
2717−-- the keys. A particular bucket fold can be terminated early if a closing flag
2718−-- is encountered in an element for that key. Once a fold is terminated the key
2719−-- and value for that bucket are emitted in the output stream.
2720−--
2721−-- Session @timestamp@ in the input stream is an absolute time from some epoch,
2722−-- characterizing the time when the input element was generated. To detect
2723−-- session window end, a monotonic event time clock is maintained synced with
2724−-- the timestamps with a clock resolution of 1 second.
2725−--
2726−-- @since 0.7.0
2727−{-# INLINABLE _classifySessionsOf #-}
2728−_classifySessionsOf
2729− :: (IsStream t, MonadAsync m, Ord k)
2730− => Double -- ^ time window size
2731− -> Fold m a b -- ^ Fold to be applied to window events
2732− -> t m (k, a, Bool, AbsTime) -- ^ window key, data, close flag, timestamp
2733− -> t m (k, b)
2734−_classifySessionsOf interval = classifySessionsBy 1 interval False
src/Streamly/Mem/Array.hs+26−14
@@ -129,11 +129,11 @@ module Streamly.Mem.Array
129 -}
129 -}
130
130
131 -- * Immutable Transformations
131 -- * Immutable Transformations
132− , transformWith
132+ , runTransform
133
133
134 -- * Folding Arrays
134 -- * Folding Arrays
135− -- , foldWith
135+ -- , runStreamFold
136− , foldArray
136+ , runFold
137 )
137 )
138where
138where
139
139
@@ -151,8 +151,9 @@ import Streamly.Streams.Serial (SerialT)
151import Streamly.Streams.StreamK.Type (IsStream)
151import Streamly.Streams.StreamK.Type (IsStream)
152import Streamly.Fold.Types (Fold(..))
152import Streamly.Fold.Types (Fold(..))
153
153
154−import qualified Streamly.Fold as FL
155import qualified Streamly.Mem.Array.Types as A
154import qualified Streamly.Mem.Array.Types as A
155+import qualified Streamly.Streams.Prelude as P
156+import qualified Streamly.Streams.Serial as Serial
156import qualified Streamly.Streams.StreamD as D
157import qualified Streamly.Streams.StreamD as D
157
158
158-------------------------------------------------------------------------------
159-------------------------------------------------------------------------------
@@ -452,7 +453,7 @@ toArray =
452--
453--
453{-# INLINE write #-}
454{-# INLINE write #-}
454write :: (MonadIO m, Storable a) => SerialT m a -> m (Array a)
455write :: (MonadIO m, Storable a) => SerialT m a -> m (Array a)
455−write = FL.foldl' toArray
456+write = P.runFold toArray
456-- write m = A.fromStreamD $ D.toStreamD m
457-- write m = A.fromStreamD $ D.toStreamD m
457
458
458-- XXX efficiently compare two streams of arrays. Two streams can have chunks
459-- XXX efficiently compare two streams of arrays. Two streams can have chunks
@@ -467,25 +468,36 @@ write = FL.foldl' toArray
467-- allocation. If we can predict the length then we can use the prediction for
468-- allocation. If we can predict the length then we can use the prediction for
468-- new allocation. Otherwise we can use a hint and adjust dynamically.
469-- new allocation. Otherwise we can use a hint and adjust dynamically.
469
470
471+{-
472+-- | Transform an array into another array using a pipe transformation
473+-- operation.
474+--
475+-- @since 0.7.0
476+{-# INLINE runPipe #-}
477+runPipe :: (MonadIO m, Storable a, Storable b)
478+ => Pipe m a b -> Array a -> m (Array b)
479+runPipe f arr = P.runPipe (toArrayMinChunk (length arr)) $ f (A.read arr)
480+-}
481+
470-- | Transform an array into another array using a stream transformation
482-- | Transform an array into another array using a stream transformation
471-- operation.
483-- operation.
472--
484--
473-- @since 0.7.0
485-- @since 0.7.0
474−{-# INLINE transformWith #-}
486+{-# INLINE runTransform #-}
475−transformWith :: (MonadIO m, Storable a, Storable b)
487+runTransform :: (MonadIO m, Storable a, Storable b)
476 => (SerialT m a -> SerialT m b) -> Array a -> m (Array b)
488 => (SerialT m a -> SerialT m b) -> Array a -> m (Array b)
477−transformWith f arr = FL.foldl' (toArrayMinChunk (length arr)) $ f (A.read arr)
489+runTransform f arr = P.runFold (toArrayMinChunk (length arr)) $ f (A.read arr)
478
490
479-- | Fold an array using a 'Fold'.
491-- | Fold an array using a 'Fold'.
480--
492--
481-- @since 0.7.0
493-- @since 0.7.0
482−{-# INLINE foldArray #-}
494+{-# INLINE runFold #-}
483−foldArray :: (MonadIO m, Storable a) => Fold m a b -> Array a -> m b
495+runFold :: forall m a b. (MonadIO m, Storable a) => Fold m a b -> Array a -> m b
484−foldArray f arr = FL.foldl' f (A.read arr)
496+runFold f arr = P.runFold f $ (A.read arr :: Serial.SerialT m a)
485
497
486-- | Fold an array using a stream fold operation.
498-- | Fold an array using a stream fold operation.
487--
499--
488-- @since 0.7.0
500-- @since 0.7.0
489−{-# INLINE _foldWith #-}
501+{-# INLINE _runStreamFold #-}
490−_foldWith :: (MonadIO m, Storable a) => (SerialT m a -> m b) -> Array a -> m b
502+_runStreamFold :: (MonadIO m, Storable a) => (SerialT m a -> m b) -> Array a -> m b
491−_foldWith f arr = f (A.read arr)
503+_runStreamFold f arr = f (A.read arr)
src/Streamly/Prelude.hs+1358−7
@@ -179,6 +179,11 @@ module Streamly.Prelude
179 , foldl1'
179 , foldl1'
180 , foldlM'
180 , foldlM'
181
181
182+ -- * Running Folds
183+ -- $runningfolds
184+
185+ , runFold
186+
182 -- ** Full Folds
187 -- ** Full Folds
183 -- | Folds that are guaranteed to evaluate the whole stream.
188 -- | Folds that are guaranteed to evaluate the whole stream.
184
189
@@ -320,6 +325,20 @@ module Streamly.Prelude
320 , scanl1'
325 , scanl1'
321 , scanl1M'
326 , scanl1M'
322
327
328+ -- ** Running Scans
329+ , runScan
330+ , runPostscan
331+
332+ -- , lscanl'
333+ -- , lscanlM'
334+ -- , lscanl1'
335+ -- , lscanl1M'
336+ --
337+ -- , lpostscanl'
338+ -- , lpostscanlM'
339+ -- , lprescanl'
340+ -- , lprescanlM'
341+
323 , indexed
342 , indexed
324 , indexedR
343 , indexedR
325 -- , timestamped
344 -- , timestamped
@@ -441,11 +460,115 @@ module Streamly.Prelude
441 -- , interposeBy
460 -- , interposeBy
442 -- , intercalate
461 -- , intercalate
443
462
463+ -- ** Breaking
464+
465+ -- By chunks
466+ -- , splitAt -- spanN
467+ -- , splitIn -- sessionN
468+
469+ -- By elements
470+ -- , span -- spanWhile
471+ -- , break -- breakBefore
472+ -- , breakAfter
473+ -- , breakOn
474+ -- , breakAround
475+ -- , spanBy
476+ -- , spanByRolling
477+
478+ -- By sequences
479+ -- breakOnSeq
480+
481+ -- ** Splitting
482+ -- | Streams can be split into segments in space or in time. We use the
483+ -- term @chunk@ to refer to a spatial length of the stream (spatial window)
484+ -- and the term @session@ to refer to a length in time (time window).
485+
486+ -- In imperative terms, grouped folding can be considered as a nested loop
487+ -- where we loop over the stream to group elements and then loop over
488+ -- individual groups to fold them to a single value that is yielded in the
489+ -- output stream.
490+
491+ -- , groupScan
492+
493+ -- *** Chunks
494+ , chunksOf
495+ , sessionsOf
496+
497+ -- , lchunksOf
498+ -- , lsessionsOf
499+
500+ -- *** Using Element Separators
501+ -- On == Dropping the separator
502+ , splitOn
503+ , splitOnSuffix
504+ -- , splitOnPrefix
505+
506+ -- By == Keeping the separator
507+ -- , splitBy
508+ , splitBySuffix
509+ -- , splitByPrefix
510+ , wordsBy -- stripAndCompactBy
511+
512+ -- -- *** Using Sequence Separators
513+ -- , splitOnSeq
514+ -- , splitOnSuffixSeq
515+ -- , splitOnPrefixSeq
516+
517+ -- Keeping the delimiters
518+ -- , splitBySeq
519+ -- , splitBySeqSuffix
520+ -- , splitBySeqPrefix
521+ -- , wordsBySeq
522+
523+ -- Splitting using multiple sequence separators
524+ -- , splitOnAnySeq
525+ -- , splitOnAnySuffixSeq
526+ -- , splitOnAnyPrefixSeq
527+
528+ -- ** Grouping
529+ , groups
530+ , groupsBy
531+ , groupsByRolling
532+
444 -- ** Distributing
533 -- ** Distributing
445 , trace
534 , trace
446 , tap
535 , tap
447 , Par.tee
536 , Par.tee
448
537
538+ {-
539+ -- * Windowed Classification
540+ -- | Split the stream into windows or chunks in space or time. Each window
541+ -- can be associated with a key, all events associated with a particular
542+ -- key in the window can be folded to a single result. The stream is split
543+ -- into windows of specified size, the window can be terminated early if
544+ -- the closing flag is specified in the input stream.
545+ --
546+ -- The term "chunk" is used for a space window and the term "session" is
547+ -- used for a time window.
548+
549+ -- ** Tumbling Windows
550+ -- | A new window starts after the previous window is finished.
551+ -- , classifyChunksOf
552+ -- , classifySessionsOf
553+
554+ -- ** Keep Alive Windows
555+ -- | The window size is extended if an event arrives within the specified
556+ -- window size. This can represent sessions with idle or inactive timeout.
557+ -- , classifyKeepAliveChunks
558+ -- , classifyKeepAliveSessions
559+
560+ {-
561+ -- ** Sliding Windows
562+ -- | A new window starts after the specified slide from the previous
563+ -- window. Therefore windows can overlap.
564+ , classifySlidingChunks
565+ , classifySlidingSessions
566+ -}
567+ -- ** Sliding Window Buffers
568+ -- , slidingChunkBuffer
569+ -- , slidingSessionBuffer
570+ -}
571+
449 -- ** Containers of Streams
572 -- ** Containers of Streams
450 -- | These are variants of standard 'Foldable' fold functions that use a
573 -- | These are variants of standard 'Foldable' fold functions that use a
451 -- polymorphic stream sum operation (e.g. 'async' or 'wSerial') to fold a
574 -- polymorphic stream sum operation (e.g. 'async' or 'wSerial') to fold a
@@ -495,19 +618,25 @@ import Control.Monad (void)
495import Control.Monad.Catch (MonadCatch)
618import Control.Monad.Catch (MonadCatch)
496import Control.Monad.IO.Class (MonadIO(..))
619import Control.Monad.IO.Class (MonadIO(..))
497import Control.Monad.Trans (MonadTrans(..))
620import Control.Monad.Trans (MonadTrans(..))
621+import Data.Functor.Identity (Identity)
622+import Data.Heap (Entry(..))
498−import Data.Maybe (isJust, fromJust)
623+import Data.Maybe (isJust, fromJust, isNothing)
499import Foreign.Storable (Storable)
624import Foreign.Storable (Storable)
500import Prelude
625import Prelude
501 hiding (filter, drop, dropWhile, take, takeWhile, zipWith, foldr,
626 hiding (filter, drop, dropWhile, take, takeWhile, zipWith, foldr,
502− foldl, mapM, mapM_, sequence, all, any, sum, product, elem,
627+ foldl, map, mapM, mapM_, sequence, all, any, sum, product, elem,
503 notElem, maximum, minimum, head, last, tail, length, null,
628 notElem, maximum, minimum, head, last, tail, length, null,
504 reverse, iterate, init, and, or, lookup, foldr1, (!!),
629 reverse, iterate, init, and, or, lookup, foldr1, (!!),
505− scanl, scanl1, replicate, concatMap)
630+ scanl, scanl1, replicate, concatMap, span)
506
631
632+import qualified Data.Heap as H
633+import qualified Data.Map.Strict as Map
507import qualified Prelude
634import qualified Prelude
508import qualified System.IO as IO
635import qualified System.IO as IO
509
636
510import Streamly.Enumeration (Enumerable(..), enumerate, enumerateTo)
637import Streamly.Enumeration (Enumerable(..), enumerate, enumerateTo)
638+import Streamly.Fold.Types (Fold (..))
639+import Streamly.Mem.Array (Array)
511import Streamly.SVar (MonadAsync, defState)
640import Streamly.SVar (MonadAsync, defState)
512import Streamly.Streams.Async (mkAsync')
641import Streamly.Streams.Async (mkAsync')
513import Streamly.Streams.Combinators (maxYields)
642import Streamly.Streams.Combinators (maxYields)
@@ -517,8 +646,14 @@ import Streamly.Streams.StreamD (fromStreamD, toStreamD)
517import Streamly.Streams.StreamK (IsStream(..))
646import Streamly.Streams.StreamK (IsStream(..))
518import Streamly.Streams.Serial (SerialT)
647import Streamly.Streams.Serial (SerialT)
519import Streamly.Pipe.Types (Pipe (..))
648import Streamly.Pipe.Types (Pipe (..))
649+import Streamly.Time.Units
650+ (AbsTime, MilliSecond64(..), addToAbsTime, diffAbsTime, toRelTime,
651+ toAbsTime)
652+
653+import Streamly.Strict
520
654
521−import qualified Streamly.Fold.Types as FL
655+import qualified Streamly.Mem.Array as A
656+import qualified Streamly.Fold as FL
522import qualified Streamly.Streams.Prelude as P
657import qualified Streamly.Streams.Prelude as P
523import qualified Streamly.Streams.StreamK as K
658import qualified Streamly.Streams.StreamK as K
524import qualified Streamly.Streams.StreamD as D
659import qualified Streamly.Streams.StreamD as D
@@ -1205,6 +1340,75 @@ foldxM = P.foldlMx'
1205foldlM' :: Monad m => (b -> a -> m b) -> b -> SerialT m a -> m b
1340foldlM' :: Monad m => (b -> a -> m b) -> b -> SerialT m a -> m b
1206foldlM' step begin m = S.foldlM' step begin $ toStreamS m
1341foldlM' step begin m = S.foldlM' step begin $ toStreamS m
1207
1342
1343+------------------------------------------------------------------------------
1344+-- Running a Fold
1345+------------------------------------------------------------------------------
1346+
1347+-- $runningfolds
1348+--
1349+-- We can use the left folds in this module instead of the folds in
1350+-- "Streamly.Prelude". For example the following two ways of folding are
1351+-- equivalent in functionality and performance,
1352+--
1353+-- >>> FL.foldl' FL.sum (S.enumerateFromTo 1 100)
1354+-- 5050
1355+-- >>> S.sum (S.enumerateFromTo 1 100)
1356+-- 5050
1357+--
1358+-- However, left folds are push type folds. That means we push the entire input
1359+-- to a fold before we can get the output. Therefore, the performance is
1360+-- equivalent only for full folds like 'sum' and 'length'. For partial folds
1361+-- like 'head' or 'any' the folds in "Streamly.Prelude" may be much more
1362+-- efficient because they are implemented as right folds that terminate as soon
1363+-- as we get the result. Note that when a full fold is composed with a partial
1364+-- fold in parallel the performance is not impacted as we anyway have to
1365+-- consume the whole stream due to the full fold.
1366+--
1367+-- >>> S.head (1 `S.cons` undefined)
1368+-- Just 1
1369+-- >>> FL.foldl' FL.head (1 `S.cons` undefined)
1370+-- *** Exception: Prelude.undefined
1371+--
1372+-- However, we can wrap the fold in a scan to convert it into a lazy stream of
1373+-- fold steps. We can then terminate the stream whenever we want. For example,
1374+--
1375+-- >>> S.toList $ S.take 1 $ FL.scanl' FL.head (1 `S.cons` undefined)
1376+-- [Nothing]
1377+--
1378+-- The following example extracts the input stream up to a point where the
1379+-- running average of elements is no more than 10:
1380+--
1381+-- @
1382+-- > S.toList
1383+-- $ S.map (fromJust . fst)
1384+-- $ S.takeWhile (\\(_,x) -> x <= 10)
1385+-- $ FL.postscanl' ((,) \<$> FL.last \<*> avg) (S.enumerateFromTo 1.0 100.0)
1386+-- @
1387+-- @
1388+-- [1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0,13.0,14.0,15.0,16.0,17.0,18.0,19.0]
1389+-- @
1390+
1391+-- | Fold a stream using the supplied monadic fold.
1392+--
1393+-- >>> S.foldlStream FL.sum (S.enumerateFromTo 1 100)
1394+-- 5050
1395+--
1396+-- @since 0.7.0
1397+{-# INLINE runFold #-}
1398+runFold :: Monad m => Fold m a b -> SerialT m a -> m b
1399+runFold = P.runFold
1400+
1401+------------------------------------------------------------------------------
1402+-- Running a sink
1403+------------------------------------------------------------------------------
1404+
1405+{-
1406+-- | Drain a stream to a 'Sink'.
1407+{-# INLINE runSink #-}
1408+runSink :: Monad m => Sink m a -> SerialT m a -> m ()
1409+runSink = runFold . toFold
1410+-}
1411+
1208------------------------------------------------------------------------------
1412------------------------------------------------------------------------------
1209-- Specialized folds
1413-- Specialized folds
1210------------------------------------------------------------------------------
1414------------------------------------------------------------------------------
@@ -1601,6 +1805,34 @@ toHandle h m = go m
1601 yieldk a r = liftIO (IO.hPutStrLn h a) >> go r
1805 yieldk a r = liftIO (IO.hPutStrLn h a) >> go r
1602 in K.foldStream defState yieldk single stop m1
1806 in K.foldStream defState yieldk single stop m1
1603
1807
1808+-- | A fold that buffers its input to a pure stream.
1809+--
1810+-- /Warning!/ working on large streams accumulated as buffers in memory could
1811+-- be very inefficient, consider using "Streamly.Array" instead.
1812+--
1813+-- @since 0.7.0
1814+{-# INLINE _toStream #-}
1815+_toStream :: Monad m => Fold m a (SerialT Identity a)
1816+_toStream = Fold (\f x -> return $ f . (x `K.cons`))
1817+ (return id)
1818+ (return . ($ K.nil))
1819+
1820+-- This is more efficient than 'toStream'. toStream is exactly the same as
1821+-- reversing the stream after toStreamRev.
1822+--
1823+-- | Buffers the input stream to a pure stream in the reverse order of the
1824+-- input.
1825+--
1826+-- /Warning!/ working on large streams accumulated as buffers in memory could
1827+-- be very inefficient, consider using "Streamly.Array" instead.
1828+--
1829+-- @since 0.7.0
1830+
1831+-- xn : ... : x2 : x1 : []
1832+{-# INLINABLE _toStreamRev #-}
1833+_toStreamRev :: Monad m => Fold m a (SerialT Identity a)
1834+_toStreamRev = Fold (\xs x -> return $ x `K.cons` xs) (return K.nil) return
1835+
1604------------------------------------------------------------------------------
1836------------------------------------------------------------------------------
1605-- General Transformation
1837-- General Transformation
1606------------------------------------------------------------------------------
1838------------------------------------------------------------------------------
@@ -1751,6 +1983,26 @@ scanl1M' step m = fromStreamD $ D.scanl1M' step $ toStreamD m
1751scanl1' :: (IsStream t, Monad m) => (a -> a -> a) -> t m a -> t m a
1983scanl1' :: (IsStream t, Monad m) => (a -> a -> a) -> t m a -> t m a
1752scanl1' step m = fromStreamD $ D.scanl1' step $ toStreamD m
1984scanl1' step m = fromStreamD $ D.scanl1' step $ toStreamD m
1753
1985
1986+------------------------------------------------------------------------------
1987+-- Scanning with a Fold
1988+------------------------------------------------------------------------------
1989+
1990+-- | Scan a stream using the given monadic fold.
1991+--
1992+-- @since 0.7.0
1993+{-# INLINE runScan #-}
1994+runScan :: Monad m => Fold m a b -> SerialT m a -> SerialT m b
1995+runScan (Fold step begin done) = P.scanlMx' step begin done
1996+
1997+-- | Postscan a stream using the given monadic fold.
1998+--
1999+-- @since 0.7.0
2000+{-# INLINE runPostscan #-}
2001+runPostscan :: Monad m => Fold m a b -> SerialT m a -> SerialT m b
2002+runPostscan (Fold step begin done) = P.postscanlMx' step begin done
2003+
2004+-- XXX runPrescan
2005+
1754------------------------------------------------------------------------------
2006------------------------------------------------------------------------------
1755-- Transformation by Filtering
2007-- Transformation by Filtering
1756------------------------------------------------------------------------------
2008------------------------------------------------------------------------------
@@ -2017,11 +2269,11 @@ intersperseBySpan _n _f _xs = undefined
2017-- @
2269-- @
2018--
2270--
2019-- @since 0.7.0
2271-- @since 0.7.0
2020−{-# INLINE _intersperseByTime #-}
2272+{-# INLINE intersperseByTime #-}
2021−_intersperseByTime
2273+intersperseByTime
2022 :: (IsStream t, MonadAsync m)
2274 :: (IsStream t, MonadAsync m)
2023 => Double -> m a -> t m a -> t m a
2275 => Double -> m a -> t m a -> t m a
2024−_intersperseByTime n f xs = xs `Par.parallelEndByFirst` repeatM timed
2276+intersperseByTime n f xs = xs `Par.parallelEndByFirst` repeatM timed
2025 where timed = liftIO (threadDelay (round $ n * 1000000)) >> f
2277 where timed = liftIO (threadDelay (round $ n * 1000000)) >> f
2026
2278
2027-- | @insertBy cmp elem stream@ inserts @elem@ before the first element in
2279-- | @insertBy cmp elem stream@ inserts @elem@ before the first element in
@@ -2298,6 +2550,791 @@ interposeBy :: (IsStream t, Monad m)
2298intercalate :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a
2550intercalate :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a
2299-}
2551-}
2300
2552
2553+------------------------------------------------------------------------------
2554+-- Grouping/Splitting
2555+------------------------------------------------------------------------------
2556+
2557+------------------------------------------------------------------------------
2558+-- Grouping without looking at elements
2559+------------------------------------------------------------------------------
2560+--
2561+------------------------------------------------------------------------------
2562+-- Binary APIs
2563+------------------------------------------------------------------------------
2564+--
2565+
2566+-- | @splitAt n f1 f2@ composes folds @f1@ and @f2@ such that first @n@
2567+-- elements of its input are consumed by fold @f1@ and the rest of the stream
2568+-- is consumed by fold @f2@.
2569+--
2570+-- > let splitAt_ n xs = FL.foldl' (FL.splitAt n FL.toList FL.toList) $ S.fromList xs
2571+--
2572+-- >>> splitAt_ 6 "Hello World!"
2573+-- > ("Hello ","World!")
2574+--
2575+-- >>> splitAt_ (-1) [1,2,3]
2576+-- > ([],[1,2,3])
2577+--
2578+-- >>> splitAt_ 0 [1,2,3]
2579+-- > ([],[1,2,3])
2580+--
2581+-- >>> splitAt_ 1 [1,2,3]
2582+-- > ([1],[2,3])
2583+--
2584+-- >>> splitAt_ 3 [1,2,3]
2585+-- > ([1,2,3],[])
2586+--
2587+-- >>> splitAt_ 4 [1,2,3]
2588+-- > ([1,2,3],[])
2589+--
2590+-- This can be considered as a two-fold version of 'ltake' where we take both
2591+-- the segments instead of discarding the leftover.
2592+--
2593+-- @since 0.7.0
2594+{-# INLINE _splitAt #-}
2595+_splitAt
2596+ :: Monad m
2597+ => Int
2598+ -> Fold m a b
2599+ -> Fold m a c
2600+ -> Fold m a (b, c)
2601+_splitAt n (Fold stepL initialL extractL) (Fold stepR initialR extractR) =
2602+ Fold step initial extract
2603+ where
2604+ initial = Tuple3' <$> return n <*> initialL <*> initialR
2605+
2606+ step (Tuple3' i xL xR) input =
2607+ if i > 0
2608+ then stepL xL input >>= (\a -> return (Tuple3' (i - 1) a xR))
2609+ else stepR xR input >>= (\b -> return (Tuple3' i xL b))
2610+
2611+ extract (Tuple3' _ a b) = (,) <$> extractL a <*> extractR b
2612+
2613+------------------------------------------------------------------------------
2614+-- N-ary APIs
2615+------------------------------------------------------------------------------
2616+
2617+------------------------------------------------------------------------------
2618+-- Generalized grouping
2619+------------------------------------------------------------------------------
2620+
2621+-- This combinator is the most general grouping combinator and can be used to
2622+-- implement all other grouping combinators.
2623+--
2624+-- XXX check if this can implement the splitOn combinator i.e. we can slide in
2625+-- new elements, slide out old elements and incrementally compute the hash.
2626+-- Also, can we implement the windowed classification combinators using this?
2627+--
2628+-- In fact this is a parse. Instead of using a special return value in the fold
2629+-- we are using a mapping function.
2630+--
2631+-- Note that 'scanl'' (usually followed by a map to extract the desired value
2632+-- from the accumulator) can be used to realize many implementations e.g. a
2633+-- sliding window implementation. A scan followed by a mapMaybe is also a good
2634+-- pattern to express many problems where we want to emit a filtered output and
2635+-- not emit an output on every input.
2636+--
2637+-- Passing on of the initial accumulator value to the next fold is equivalent
2638+-- to returning the leftover concept.
2639+
2640+{-
2641+-- | @groupScan splitter fold stream@ folds the input stream using @fold@.
2642+-- @splitter@ is applied on the accumulator of the fold every time an item is
2643+-- consumed by the fold. The fold continues until @splitter@ returns a 'Just'
2644+-- value. A 'Just' result from the @splitter@ specifies a result to be emitted
2645+-- in the output stream and the initial value of the accumulator for the next
2646+-- group's fold. This allows us to control whether to start fresh for the next
2647+-- fold or to continue from the previous fold's output.
2648+--
2649+{-# INLINE groupScan #-}
2650+groupScan
2651+ :: (IsStream t, Monad m)
2652+ => (x -> m (Maybe (b, x))) -> Fold m a x -> t m a -> t m b
2653+groupScan split fold m = undefined
2654+-}
2655+
2656+-- | Group the input stream into groups of @n@ elements each and then fold each
2657+-- group using the provided fold function.
2658+--
2659+-- >> S.toList $ FL.chunksOf 2 FL.sum (S.enumerateFromTo 1 10)
2660+-- > [3,7,11,15,19]
2661+--
2662+-- This can be considered as an n-fold version of 'ltake' where we apply
2663+-- 'ltake' repeatedly on the leftover stream until the stream exhausts.
2664+--
2665+-- @since 0.7.0
2666+{-# INLINE chunksOf #-}
2667+chunksOf
2668+ :: (IsStream t, Monad m)
2669+ => Int -> Fold m a b -> t m a -> t m b
2670+chunksOf n f m = D.fromStreamD $ D.groupsOf n f (D.toStreamD m)
2671+
2672+-- | Transform a fold from a pure input to a 'Maybe' input, consuming only
2673+-- 'Just' values.
2674+{-# INLINE lcatMaybes #-}
2675+lcatMaybes :: Monad m => Fold m a b -> Fold m (Maybe a) b
2676+lcatMaybes = FL.lfilter isJust . FL.lmap fromJust
2677+
2678+-- XXX we can implement this by repeatedly applying the 'lrunFor' fold.
2679+-- XXX add this example after fixing the serial stream rate control
2680+-- >>> S.toList $ S.take 5 $ sessionsOf 1 FL.sum $ constRate 2 $ S.enumerateFrom 1
2681+-- > [3,7,11,15,19]
2682+--
2683+-- | Group the input stream into windows of @n@ second each and then fold each
2684+-- group using the provided fold function.
2685+--
2686+-- @since 0.7.0
2687+{-# INLINE sessionsOf #-}
2688+sessionsOf
2689+ :: (IsStream t, MonadAsync m)
2690+ => Double -> Fold m a b -> t m a -> t m b
2691+sessionsOf n f xs =
2692+ splitBySuffix isNothing (lcatMaybes f)
2693+ (intersperseByTime n (return Nothing) (Serial.map Just xs))
2694+
2695+------------------------------------------------------------------------------
2696+-- Element Aware APIs
2697+------------------------------------------------------------------------------
2698+--
2699+------------------------------------------------------------------------------
2700+-- Binary APIs
2701+------------------------------------------------------------------------------
2702+
2703+-- | Break the input stream into two groups, the first group takes the input as
2704+-- long as the predicate applied to the first element of the stream and next
2705+-- input element holds 'True', the second group takes the rest of the input.
2706+--
2707+_spanBy
2708+ :: Monad m
2709+ => (a -> a -> Bool)
2710+ -> Fold m a b
2711+ -> Fold m a c
2712+ -> Fold m a (b, c)
2713+_spanBy cmp (Fold stepL initialL extractL) (Fold stepR initialR extractR) =
2714+ Fold step initial extract
2715+
2716+ where
2717+ initial = Tuple3' <$> initialL <*> initialR <*> return (Tuple' Nothing True)
2718+
2719+ step (Tuple3' a b (Tuple' (Just frst) isFirstG)) input =
2720+ if cmp frst input && isFirstG
2721+ then stepL a input
2722+ >>= (\a' -> return (Tuple3' a' b (Tuple' (Just frst) isFirstG)))
2723+ else stepR b input
2724+ >>= (\a' -> return (Tuple3' a a' (Tuple' Nothing False)))
2725+
2726+ step (Tuple3' a b (Tuple' Nothing isFirstG)) input =
2727+ if isFirstG
2728+ then stepL a input
2729+ >>= (\a' -> return (Tuple3' a' b (Tuple' (Just input) isFirstG)))
2730+ else stepR b input
2731+ >>= (\a' -> return (Tuple3' a a' (Tuple' Nothing False)))
2732+
2733+ extract (Tuple3' a b _) = (,) <$> extractL a <*> extractR b
2734+
2735+-- | @span p f1 f2@ composes folds @f1@ and @f2@ such that @f1@ consumes the
2736+-- input as long as the predicate @p@ is 'True'. @f2@ consumes the rest of the
2737+-- input.
2738+--
2739+-- > let span_ p xs = FL.foldl' (FL.span p FL.toList FL.toList) $ S.fromList xs
2740+--
2741+-- >>> span_ (< 1) [1,2,3]
2742+-- > ([],[1,2,3])
2743+--
2744+-- >>> span_ (< 2) [1,2,3]
2745+-- > ([1],[2,3])
2746+--
2747+-- >>> span_ (< 4) [1,2,3]
2748+-- > ([1,2,3],[])
2749+--
2750+-- @since 0.7.0
2751+
2752+-- This can be considered as a two-fold version of 'ltakeWhile' where we take
2753+-- both the segments instead of discarding the leftover.
2754+{-# INLINE span #-}
2755+span
2756+ :: Monad m
2757+ => (a -> Bool)
2758+ -> Fold m a b
2759+ -> Fold m a c
2760+ -> Fold m a (b, c)
2761+span p (Fold stepL initialL extractL) (Fold stepR initialR extractR) =
2762+ Fold step initial extract
2763+
2764+ where
2765+
2766+ initial = Tuple3' <$> initialL <*> initialR <*> return True
2767+
2768+ step (Tuple3' a b isFirstG) input =
2769+ if isFirstG && p input
2770+ then stepL a input >>= (\a' -> return (Tuple3' a' b True))
2771+ else stepR b input >>= (\a' -> return (Tuple3' a a' False))
2772+
2773+ extract (Tuple3' a b _) = (,) <$> extractL a <*> extractR b
2774+
2775+-- |
2776+-- > break p = span (not . p)
2777+--
2778+-- Break as soon as the predicate becomes 'True'. @break p f1 f2@ composes
2779+-- folds @f1@ and @f2@ such that @f1@ stops consuming input as soon as the
2780+-- predicate @p@ becomes 'True'. The rest of the input is consumed @f2@.
2781+--
2782+-- This is the binary version of 'splitBy'.
2783+--
2784+-- > let break_ p xs = FL.foldl' (FL.break p FL.toList FL.toList) $ S.fromList xs
2785+--
2786+-- >>> break_ (< 1) [3,2,1]
2787+-- > ([3,2,1],[])
2788+--
2789+-- >>> break_ (< 2) [3,2,1]
2790+-- > ([3,2],[1])
2791+--
2792+-- >>> break_ (< 4) [3,2,1]
2793+-- > ([],[3,2,1])
2794+--
2795+-- @since 0.7.0
2796+{-# INLINE _break #-}
2797+_break
2798+ :: Monad m
2799+ => (a -> Bool)
2800+ -> Fold m a b
2801+ -> Fold m a c
2802+ -> Fold m a (b, c)
2803+_break p = span (not . p)
2804+
2805+-- | Like 'spanBy' but applies the predicate in a rolling fashion i.e.
2806+-- predicate is applied to the previous and the next input elements.
2807+{-# INLINE _spanRollingBy #-}
2808+_spanRollingBy
2809+ :: Monad m
2810+ => (a -> a -> Bool)
2811+ -> Fold m a b
2812+ -> Fold m a c
2813+ -> Fold m a (b, c)
2814+_spanRollingBy cmp (Fold stepL initialL extractL) (Fold stepR initialR extractR) =
2815+ Fold step initial extract
2816+
2817+ where
2818+ initial = Tuple3' <$> initialL <*> initialR <*> return Nothing
2819+
2820+ step (Tuple3' a b (Just frst)) input =
2821+ if cmp input frst
2822+ then stepL a input >>= (\a' -> return (Tuple3' a' b (Just input)))
2823+ else stepR b input >>= (\b' -> return (Tuple3' a b' (Just input)))
2824+
2825+ step (Tuple3' a b Nothing) input =
2826+ stepL a input >>= (\a' -> return (Tuple3' a' b (Just input)))
2827+
2828+ extract (Tuple3' a b _) = (,) <$> extractL a <*> extractR b
2829+
2830+------------------------------------------------------------------------------
2831+-- N-ary APIs
2832+------------------------------------------------------------------------------
2833+--
2834+-- | @groupsBy cmp f $ S.fromList [a,b,c,...]@ assigns the element @a@ to the
2835+-- first group, if @a \`cmp` b@ is 'True' then @b@ is also assigned to the same
2836+-- group. If @a \`cmp` c@ is 'True' then @c@ is also assigned to the same
2837+-- group and so on. When the comparison fails a new group is started. Each
2838+-- group is folded using the fold @f@ and the result of the fold is emitted in
2839+-- the output stream.
2840+--
2841+-- >>> S.toList $ FL.groupsBy (>) FL.toList $ S.fromList [1,3,7,0,2,5]
2842+-- > [[1,3,7],[0,2,5]]
2843+--
2844+-- @since 0.7.0
2845+{-# INLINE groupsBy #-}
2846+groupsBy
2847+ :: (IsStream t, Monad m)
2848+ => (a -> a -> Bool)
2849+ -> Fold m a b
2850+ -> t m a
2851+ -> t m b
2852+groupsBy cmp f m = D.fromStreamD $ D.groupsBy cmp f (D.toStreamD m)
2853+
2854+-- | Unlike @groupsBy@ this function performs a rolling comparison of two
2855+-- successive elements in the input stream. @groupsByRolling cmp f $ S.fromList
2856+-- [a,b,c,...]@ assigns the element @a@ to the first group, if @a \`cmp` b@ is
2857+-- 'True' then @b@ is also assigned to the same group. If @b \`cmp` c@ is
2858+-- 'True' then @c@ is also assigned to the same group and so on. When the
2859+-- comparison fails a new group is started. Each group is folded using the fold
2860+-- @f@.
2861+--
2862+-- >>> S.toList $ FL.groupsByRolling (\a b -> a + 1 == b) FL.toList $ S.fromList [1,2,3,7,8,9]
2863+-- > [[1,2,3],[7,8,9]]
2864+--
2865+-- @since 0.7.0
2866+{-# INLINE groupsByRolling #-}
2867+groupsByRolling
2868+ :: (IsStream t, Monad m)
2869+ => (a -> a -> Bool)
2870+ -> Fold m a b
2871+ -> t m a
2872+ -> t m b
2873+groupsByRolling cmp f m = D.fromStreamD $ D.groupsRollingBy cmp f (D.toStreamD m)
2874+
2875+-- |
2876+-- > groups = groupsBy (==)
2877+-- > groups = groupsByRolling (==)
2878+--
2879+-- Groups contiguous spans of equal elements together in individual groups.
2880+--
2881+-- >>> S.toList $ FL.groups FL.toList $ S.fromList [1,1,2,2]
2882+-- > [[1,1],[2,2]]
2883+--
2884+-- @since 0.7.0
2885+groups :: (IsStream t, Monad m, Eq a) => Fold m a b -> t m a -> t m b
2886+groups = groupsBy (==)
2887+
2888+------------------------------------------------------------------------------
2889+-- Binary splitting on a separator
2890+------------------------------------------------------------------------------
2891+
2892+{-
2893+-- | Find the first occurrence of the specified sequence in the input stream
2894+-- and break the input stream into two parts, the first part consisting of the
2895+-- stream before the sequence and the second part consisting of the sequence
2896+-- and the rest of the stream.
2897+--
2898+-- > let breakOn_ pat xs = FL.foldl' (FL.breakOn pat FL.toList FL.toList) $ S.fromList xs
2899+--
2900+-- >>> breakOn_ "dear" "Hello dear world!"
2901+-- > ("Hello ","dear world!")
2902+--
2903+{-# INLINE breakOn #-}
2904+breakOn :: Monad m => Array a -> Fold m a b -> Fold m a c -> Fold m a (b,c)
2905+breakOn pat f m = undefined
2906+-}
2907+
2908+------------------------------------------------------------------------------
2909+-- N-ary split on a predicate
2910+------------------------------------------------------------------------------
2911+
2912+-- TODO: Use a Splitter configuration similar to the "split" package to make it
2913+-- possible to express all splitting combinations. In general, we can have
2914+-- infix/suffix/prefix/condensing of separators, dropping both leading/trailing
2915+-- separators. We can have a single split operation taking the splitter config
2916+-- as argument.
2917+
2918+-- | Split a stream on separator elements determined by a predicate, dropping
2919+-- the separator. Separator is considered as infixed between two segments, if
2920+-- one side of the separator is missing then it is parsed as an empty stream.
2921+-- With '-' representing elements and '.' as separator, 'splitOn' splits as
2922+-- follows:
2923+--
2924+-- @
2925+-- "--.--" => "--" "--"
2926+-- "--." => "--" ""
2927+-- ".--" => "" "--"
2928+-- @
2929+--
2930+-- @splitOn (== x)@ is an inverse of @intercalate (S.yield x)@
2931+--
2932+-- Let's use the following definition for illustration:
2933+--
2934+-- > splitOn' p xs = S.toList $ FL.splitOn p (FL.toList) (S.fromList xs)
2935+--
2936+-- >>> splitOn' (== '.') ""
2937+-- [""]
2938+--
2939+-- >>> splitOn' (== '.') "."
2940+-- ["",""]
2941+--
2942+-- >>> splitOn' (== '.') ".a"
2943+-- > ["","a"]
2944+--
2945+-- >>> splitOn' (== '.') "a."
2946+-- > ["a",""]
2947+--
2948+-- >>> splitOn' (== '.') "a.b"
2949+-- > ["a","b"]
2950+--
2951+-- >>> splitOn' (== '.') "a..b"
2952+-- > ["a","","b"]
2953+--
2954+-- @since 0.7.0
2955+
2956+-- This can be considered as an n-fold version of 'breakOn' where we apply
2957+-- 'breakOn' successively on the input stream, dropping the first element
2958+-- of the second segment after each break.
2959+--
2960+{-# INLINE splitOn #-}
2961+splitOn
2962+ :: (IsStream t, Monad m)
2963+ => (a -> Bool) -> Fold m a b -> t m a -> t m b
2964+splitOn predicate f m =
2965+ D.fromStreamD $ D.splitBy predicate f (D.toStreamD m)
2966+
2967+-- | Like 'splitOn' but the separator is considered as suffixed to the segments
2968+-- in the stream. A missing suffix at the end is allowed. A separator at the
2969+-- beginning is parsed as empty segment. With '-' representing elements and
2970+-- '.' as separator, 'splitOnSuffix' splits as follows:
2971+--
2972+-- @
2973+-- "--.--." => "--" "--"
2974+-- "--.--" => "--" "--"
2975+-- ".--." => "" "--"
2976+-- @
2977+--
2978+-- > splitOnSuffix' p xs = S.toList $ FL.splitSuffixBy p (FL.toList) (S.fromList xs)
2979+--
2980+-- >>> splitOnSuffix' (== '.') ""
2981+-- []
2982+--
2983+-- >>> splitOnSuffix' (== '.') "."
2984+-- [""]
2985+--
2986+-- >>> splitOnSuffix' (== '.') "a"
2987+-- ["a"]
2988+--
2989+-- >>> splitOnSuffix' (== '.') ".a"
2990+-- > ["","a"]
2991+--
2992+-- >>> splitOnSuffix' (== '.') "a."
2993+-- > ["a"]
2994+--
2995+-- >>> splitOnSuffix' (== '.') "a.b"
2996+-- > ["a","b"]
2997+--
2998+-- >>> splitOnSuffix' (== '.') "a.b."
2999+-- > ["a","b"]
3000+--
3001+-- >>> splitOnSuffix' (== '.') "a..b.."
3002+-- > ["a","","b",""]
3003+--
3004+-- > lines = splitOnSuffix (== '\n')
3005+--
3006+-- @since 0.7.0
3007+
3008+-- This can be considered as an n-fold version of 'breakPost' where we apply
3009+-- 'breakPost' successively on the input stream, dropping the first element
3010+-- of the second segment after each break.
3011+--
3012+{-# INLINE splitOnSuffix #-}
3013+splitOnSuffix
3014+ :: (IsStream t, Monad m)
3015+ => (a -> Bool) -> Fold m a b -> t m a -> t m b
3016+splitOnSuffix predicate f m =
3017+ D.fromStreamD $ D.splitSuffixBy predicate f (D.toStreamD m)
3018+
3019+-- | Like 'splitBy' but ignores repeated separators or separators in leading
3020+-- or trailing position. Therefore, @"..a..b.."@ would be parsed as
3021+-- @["a","b"]@. In other words, it treats the input like words separated by
3022+-- whitespace elements determined by the predicate.
3023+--
3024+-- > wordsBy' p xs = S.toList $ FL.wordsBy p (FL.toList) (S.fromList xs)
3025+--
3026+-- >>> wordsBy' (== ',') ""
3027+-- > []
3028+--
3029+-- >>> wordsBy' (== ',') ","
3030+-- > []
3031+--
3032+-- >>> wordsBy' (== ',') ",a,,b,"
3033+-- > ["a","b"]
3034+--
3035+-- > words = wordsBy isSpace
3036+--
3037+-- @since 0.7.0
3038+{-# INLINE wordsBy #-}
3039+wordsBy
3040+ :: (IsStream t, Monad m)
3041+ => (a -> Bool) -> Fold m a b -> t m a -> t m b
3042+wordsBy predicate f m =
3043+ D.fromStreamD $ D.wordsBy predicate f (D.toStreamD m)
3044+
3045+-- | Like 'splitOnSuffix' but keeps the suffix attached to the resulting
3046+-- splits.
3047+--
3048+-- > splitBySuffix' p xs = S.toList $ FL.splitBySuffix p (FL.toList) (S.fromList xs)
3049+--
3050+-- >>> splitBySuffix' (== '.') ""
3051+-- []
3052+--
3053+-- >>> splitBySuffix' (== '.') "."
3054+-- ["."]
3055+--
3056+-- >>> splitBySuffix' (== '.') "a"
3057+-- ["a"]
3058+--
3059+-- >>> splitBySuffix' (== '.') ".a"
3060+-- > [".","a"]
3061+--
3062+-- >>> splitBySuffix' (== '.') "a."
3063+-- > ["a."]
3064+--
3065+-- >>> splitBySuffix' (== '.') "a.b"
3066+-- > ["a.","b"]
3067+--
3068+-- >>> splitBySuffix' (== '.') "a.b."
3069+-- > ["a.","b."]
3070+--
3071+-- >>> splitBySuffix' (== '.') "a..b.."
3072+-- > ["a.",".","b.","."]
3073+--
3074+-- @since 0.7.0
3075+
3076+-- This can be considered as an n-fold version of 'breakPost' where we apply
3077+-- 'breakPost' successively on the input stream.
3078+--
3079+{-# INLINE splitBySuffix #-}
3080+splitBySuffix
3081+ :: (IsStream t, Monad m)
3082+ => (a -> Bool) -> Fold m a b -> t m a -> t m b
3083+splitBySuffix predicate f m =
3084+ D.fromStreamD $ D.splitSuffixBy' predicate f (D.toStreamD m)
3085+
3086+------------------------------------------------------------------------------
3087+-- Split on a delimiter sequence
3088+------------------------------------------------------------------------------
3089+
3090+-- Int list examples for splitOn:
3091+--
3092+-- >>> splitList [] [1,2,3,3,4]
3093+-- > [[1],[2],[3],[3],[4]]
3094+--
3095+-- >>> splitList [5] [1,2,3,3,4]
3096+-- > [[1,2,3,3,4]]
3097+--
3098+-- >>> splitList [1] [1,2,3,3,4]
3099+-- > [[],[2,3,3,4]]
3100+--
3101+-- >>> splitList [4] [1,2,3,3,4]
3102+-- > [[1,2,3,3],[]]
3103+--
3104+-- >>> splitList [2] [1,2,3,3,4]
3105+-- > [[1],[3,3,4]]
3106+--
3107+-- >>> splitList [3] [1,2,3,3,4]
3108+-- > [[1,2],[],[4]]
3109+--
3110+-- >>> splitList [3,3] [1,2,3,3,4]
3111+-- > [[1,2],[4]]
3112+--
3113+-- >>> splitList [1,2,3,3,4] [1,2,3,3,4]
3114+-- > [[],[]]
3115+
3116+-- | Like 'splitOn' but the separator is a sequence of elements instead of a
3117+-- single element.
3118+--
3119+-- For illustration, let's define a function that operates on pure lists:
3120+--
3121+-- @
3122+-- splitOnSeq' pat xs = S.toList $ FL.splitOnSeq (A.fromList pat) (FL.toList) (S.fromList xs)
3123+-- @
3124+--
3125+-- >>> splitOnSeq' "" "hello"
3126+-- > ["h","e","l","l","o"]
3127+--
3128+-- >>> splitOnSeq' "hello" ""
3129+-- > [""]
3130+--
3131+-- >>> splitOnSeq' "hello" "hello"
3132+-- > ["",""]
3133+--
3134+-- >>> splitOnSeq' "x" "hello"
3135+-- > ["hello"]
3136+--
3137+-- >>> splitOnSeq' "h" "hello"
3138+-- > ["","ello"]
3139+--
3140+-- >>> splitOnSeq' "o" "hello"
3141+-- > ["hell",""]
3142+--
3143+-- >>> splitOnSeq' "e" "hello"
3144+-- > ["h","llo"]
3145+--
3146+-- >>> splitOnSeq' "l" "hello"
3147+-- > ["he","","o"]
3148+--
3149+-- >>> splitOnSeq' "ll" "hello"
3150+-- > ["he","o"]
3151+--
3152+-- 'splitOnSeq' is an inverse of 'intercalate'. The following law always holds:
3153+--
3154+-- > intercalate . splitOn == id
3155+--
3156+-- The following law holds when the separator is non-empty and contains none of
3157+-- the elements present in the input lists:
3158+--
3159+-- > splitOn . intercalate == id
3160+--
3161+-- @since 0.7.0
3162+
3163+-- XXX We can use a polymorphic vector implemented by Array# to represent the
3164+-- sequence, that way we can avoid the Storable constraint. If we still need
3165+-- Storable Array for performance, we can use a separate splitOnArray API for
3166+-- that. We can also have an API where the sequence itself is a lazy stream, so
3167+-- that we can search files in files for example.
3168+{-# INLINE splitOnSeq #-}
3169+splitOnSeq
3170+ :: (IsStream t, MonadIO m, Storable a, Enum a, Eq a)
3171+ => Array a -> Fold m a b -> t m a -> t m b
3172+splitOnSeq patt f m = D.fromStreamD $ D.splitOn patt f (D.toStreamD m)
3173+
3174+{-
3175+-- This can be implemented easily using Rabin Karp
3176+-- | Split on any one of the given patterns.
3177+{-# INLINE splitOnAny #-}
3178+splitOnAny
3179+ :: (IsStream t, Monad m, Storable a, Integral a)
3180+ => [Array a] -> Fold m a b -> t m a -> t m b
3181+splitOnAny subseq f m = undefined -- D.fromStreamD $ D.splitOnAny f subseq (D.toStreamD m)
3182+-}
3183+
3184+-- | Like 'splitSuffixBy' but the separator is a sequence of elements, instead
3185+-- of a predicate for a single element.
3186+--
3187+-- > splitSuffixOn_ pat xs = S.toList $ FL.splitSuffixOn (A.fromList pat) (FL.toList) (S.fromList xs)
3188+--
3189+-- >>> splitSuffixOn_ "." ""
3190+-- [""]
3191+--
3192+-- >>> splitSuffixOn_ "." "."
3193+-- [""]
3194+--
3195+-- >>> splitSuffixOn_ "." "a"
3196+-- ["a"]
3197+--
3198+-- >>> splitSuffixOn_ "." ".a"
3199+-- > ["","a"]
3200+--
3201+-- >>> splitSuffixOn_ "." "a."
3202+-- > ["a"]
3203+--
3204+-- >>> splitSuffixOn_ "." "a.b"
3205+-- > ["a","b"]
3206+--
3207+-- >>> splitSuffixOn_ "." "a.b."
3208+-- > ["a","b"]
3209+--
3210+-- >>> splitSuffixOn_ "." "a..b.."
3211+-- > ["a","","b",""]
3212+--
3213+-- > lines = splitSuffixOn "\n"
3214+--
3215+-- @since 0.7.0
3216+{-# INLINE _splitOnSuffixSeq #-}
3217+_splitOnSuffixSeq
3218+ :: (IsStream t, MonadIO m, Storable a, Enum a, Eq a)
3219+ => Array a -> Fold m a b -> t m a -> t m b
3220+_splitOnSuffixSeq patt f m =
3221+ D.fromStreamD $ D.splitSuffixOn False patt f (D.toStreamD m)
3222+
3223+{-
3224+-- | Like 'splitOn' but drops any empty splits.
3225+--
3226+{-# INLINE wordsOn #-}
3227+wordsOn
3228+ :: (IsStream t, Monad m, Storable a, Eq a)
3229+ => Array a -> Fold m a b -> t m a -> t m b
3230+wordsOn subseq f m = undefined -- D.fromStreamD $ D.wordsOn f subseq (D.toStreamD m)
3231+-}
3232+
3233+-- XXX use a non-monadic intersperse to remove the MonadAsync constraint.
3234+--
3235+-- | Like 'splitOnSeq' but splits the separator as well, as an infix token.
3236+--
3237+-- > splitOn'_ pat xs = S.toList $ FL.splitOn' (A.fromList pat) (FL.toList) (S.fromList xs)
3238+--
3239+-- >>> splitOn'_ "" "hello"
3240+-- > ["h","","e","","l","","l","","o"]
3241+--
3242+-- >>> splitOn'_ "hello" ""
3243+-- > [""]
3244+--
3245+-- >>> splitOn'_ "hello" "hello"
3246+-- > ["","hello",""]
3247+--
3248+-- >>> splitOn'_ "x" "hello"
3249+-- > ["hello"]
3250+--
3251+-- >>> splitOn'_ "h" "hello"
3252+-- > ["","h","ello"]
3253+--
3254+-- >>> splitOn'_ "o" "hello"
3255+-- > ["hell","o",""]
3256+--
3257+-- >>> splitOn'_ "e" "hello"
3258+-- > ["h","e","llo"]
3259+--
3260+-- >>> splitOn'_ "l" "hello"
3261+-- > ["he","l","","l","o"]
3262+--
3263+-- >>> splitOn'_ "ll" "hello"
3264+-- > ["he","ll","o"]
3265+--
3266+-- @since 0.7.0
3267+{-# INLINE _splitBySeq #-}
3268+_splitBySeq
3269+ :: (IsStream t, MonadAsync m, Storable a, Enum a, Eq a)
3270+ => Array a -> Fold m a b -> t m a -> t m b
3271+_splitBySeq patt f m = intersperseM (runFold f (A.read patt)) $ splitOnSeq patt f m
3272+
3273+-- | Like 'splitSuffixOn' but keeps the suffix intact in the splits.
3274+--
3275+-- > splitSuffixOn'_ pat xs = S.toList $ FL.splitSuffixOn' (A.fromList pat) (FL.toList) (S.fromList xs)
3276+--
3277+-- >>> splitSuffixOn'_ "." ""
3278+-- [""]
3279+--
3280+-- >>> splitSuffixOn'_ "." "."
3281+-- ["."]
3282+--
3283+-- >>> splitSuffixOn'_ "." "a"
3284+-- ["a"]
3285+--
3286+-- >>> splitSuffixOn'_ "." ".a"
3287+-- > [".","a"]
3288+--
3289+-- >>> splitSuffixOn'_ "." "a."
3290+-- > ["a."]
3291+--
3292+-- >>> splitSuffixOn'_ "." "a.b"
3293+-- > ["a.","b"]
3294+--
3295+-- >>> splitSuffixOn'_ "." "a.b."
3296+-- > ["a.","b."]
3297+--
3298+-- >>> splitSuffixOn'_ "." "a..b.."
3299+-- > ["a.",".","b.","."]
3300+--
3301+-- @since 0.7.0
3302+{-# INLINE _splitBySuffixSeq #-}
3303+_splitBySuffixSeq
3304+ :: (IsStream t, MonadIO m, Storable a, Enum a, Eq a)
3305+ => Array a -> Fold m a b -> t m a -> t m b
3306+_splitBySuffixSeq patt f m =
3307+ D.fromStreamD $ D.splitSuffixOn True patt f (D.toStreamD m)
3308+
3309+{-
3310+-- This can be implemented easily using Rabin Karp
3311+-- | Split post any one of the given patterns.
3312+{-# INLINE splitSuffixOnAny #-}
3313+splitSuffixOnAny
3314+ :: (IsStream t, Monad m, Storable a, Integral a)
3315+ => [Array a] -> Fold m a b -> t m a -> t m b
3316+splitSuffixOnAny subseq f m = undefined
3317+ -- D.fromStreamD $ D.splitPostAny f subseq (D.toStreamD m)
3318+-}
3319+
3320+------------------------------------------------------------------------------
3321+-- Reorder in sequence
3322+------------------------------------------------------------------------------
3323+
3324+{-
3325+-- Buffer until the next element in sequence arrives. The function argument
3326+-- determines the difference in sequence numbers. This could be useful in
3327+-- implementing sequenced streams, for example, TCP reassembly.
3328+{-# INLINE reassembleBy #-}
3329+reassembleBy
3330+ :: (IsStream t, Monad m)
3331+ => Fold m a b
3332+ -> (a -> a -> Int)
3333+ -> t m a
3334+ -> t m b
3335+reassembleBy = undefined
3336+-}
3337+
2301------------------------------------------------------------------------------
3338------------------------------------------------------------------------------
2302-- Distributing
3339-- Distributing
2303------------------------------------------------------------------------------
3340------------------------------------------------------------------------------
@@ -2340,6 +3377,320 @@ tap f xs = D.fromStreamD $ D.tap f (D.toStreamD xs)
2340trace :: (IsStream t, MonadAsync m) => (a -> m b) -> t m a -> t m a
3377trace :: (IsStream t, MonadAsync m) => (a -> m b) -> t m a -> t m a
2341trace f = mapM (\x -> void (f x) >> return x)
3378trace f = mapM (\x -> void (f x) >> return x)
2342
3379
3380+------------------------------------------------------------------------------
3381+-- Windowed classification
3382+------------------------------------------------------------------------------
3383+
3384+-- We divide the stream into windows or chunks in space or time and each window
3385+-- can be associated with a key, all events associated with a particular key in
3386+-- the window can be folded to a single result. The stream can be split into
3387+-- windows by size or by using a split predicate on the elements in the stream.
3388+-- For example, when we receive a closing flag, we can close the window.
3389+--
3390+-- A "chunk" is a space window and a "session" is a time window. Are there any
3391+-- other better short words to describe them. An alternative is to use
3392+-- "swindow" and "twindow". Another word for "session" could be "spell".
3393+--
3394+-- TODO: To mark the position in space or time we can have Indexed or
3395+-- TimeStamped types. That can make it easy to deal with the position indices
3396+-- or timestamps.
3397+
3398+------------------------------------------------------------------------------
3399+-- Keyed Sliding Windows
3400+------------------------------------------------------------------------------
3401+
3402+{-
3403+{-# INLINABLE classifySlidingChunks #-}
3404+classifySlidingChunks
3405+ :: (IsStream t, MonadAsync m, Ord k)
3406+ => Int -- ^ window size
3407+ -> Int -- ^ window slide
3408+ -> Fold m a b -- ^ Fold to be applied to window events
3409+ -> t m (k, a, Bool) -- ^ window key, data, close event
3410+ -> t m (k, b)
3411+classifySlidingChunks wsize wslide (Fold step initial extract) str
3412+ = undefined
3413+
3414+-- XXX Another variant could be to slide the window on an event, e.g. in TCP we
3415+-- slide the send window when an ack is received and we slide the receive
3416+-- window when a sequence is complete. Sliding is stateful in case of TCP,
3417+-- sliding releases the send buffer or makes data available to the user from
3418+-- the receive buffer.
3419+{-# INLINABLE classifySlidingSessions #-}
3420+classifySlidingSessions
3421+ :: (IsStream t, MonadAsync m, Ord k)
3422+ => Double -- ^ timer tick in seconds
3423+ -> Double -- ^ time window size
3424+ -> Double -- ^ window slide
3425+ -> Fold m a b -- ^ Fold to be applied to window events
3426+ -> t m (k, a, Bool, AbsTime) -- ^ window key, data, close flag, timestamp
3427+ -> t m (k, b)
3428+classifySlidingSessions tick interval slide (Fold step initial extract) str
3429+ = undefined
3430+-}
3431+
3432+------------------------------------------------------------------------------
3433+-- Sliding Window Buffers
3434+------------------------------------------------------------------------------
3435+
3436+-- These buffered versions could be faster than concurrent incremental folds of
3437+-- all overlapping windows as in many cases we may not need all the values to
3438+-- compute the fold, we can just compute the result using the old value and new
3439+-- value. However, we may need the buffer once in a while, for example for
3440+-- string search we usually compute the hash incrementally but when the hash
3441+-- matches the hash of the pattern we need to compare the whole string.
3442+--
3443+-- XXX we should be able to implement sequence based splitting combinators
3444+-- using this combinator.
3445+
3446+{-
3447+-- | Buffer n elements of the input in a ring buffer. When t new elements are
3448+-- collected, slide the window to remove the same number of oldest elements,
3449+-- insert the new elements, and apply an incremental fold on the sliding
3450+-- window, supplying the outgoing elements, the new ring buffer as arguments.
3451+slidingChunkBuffer
3452+ :: (IsStream t, Monad m, Ord a, Storable a)
3453+ => Int -- window size
3454+ -> Int -- window slide
3455+ -> Fold m (Ring a, Array a) b
3456+ -> t m a
3457+ -> t m b
3458+slidingChunkBuffer = undefined
3459+
3460+-- Buffer n seconds worth of stream elements of the input in a radix tree.
3461+-- Every t seconds, remove the items that are older than n seconds, and apply
3462+-- an incremental fold on the sliding window, supplying the outgoing elements,
3463+-- and the new radix tree buffer as arguments.
3464+slidingSessionBuffer
3465+ :: (IsStream t, Monad m, Ord a, Storable a)
3466+ => Int -- window size
3467+ -> Int -- tick size
3468+ -> Fold m (RTree a, Array a) b
3469+ -> t m a
3470+ -> t m b
3471+slidingSessionBuffer = undefined
3472+-}
3473+
3474+------------------------------------------------------------------------------
3475+-- Keyed Session Windows
3476+------------------------------------------------------------------------------
3477+
3478+{-
3479+-- | Keyed variable size space windows. Close the window if we do not receive a
3480+-- window event in the next "spaceout" elements.
3481+{-# INLINABLE classifyChunksBy #-}
3482+classifyChunksBy
3483+ :: (IsStream t, MonadAsync m, Ord k)
3484+ => Int -- ^ window spaceout (spread)
3485+ -> Bool -- ^ reset the spaceout when a chunk window element is received
3486+ -> Fold m a b -- ^ Fold to be applied to chunk window elements
3487+ -> t m (k, a, Bool) -- ^ chunk key, data, last element
3488+ -> t m (k, b)
3489+classifyChunksBy spanout reset (Fold step initial extract) str = undefined
3490+
3491+-- | Like 'classifyChunksOf' but the chunk size is reset if an element is
3492+-- received within the chunk size window. The chunk gets closed only if no
3493+-- element is received within the chunk window.
3494+--
3495+{-# INLINABLE classifyKeepAliveChunks #-}
3496+classifyKeepAliveChunks
3497+ :: (IsStream t, MonadAsync m, Ord k)
3498+ => Int -- ^ window spaceout (spread)
3499+ -> Fold m a b -- ^ Fold to be applied to chunk window elements
3500+ -> t m (k, a, Bool) -- ^ chunk key, data, last element
3501+ -> t m (k, b)
3502+classifyKeepAliveChunks spanout = classifyChunksBy spanout True
3503+-}
3504+
3505+-- | @classifySessionsBy tick timeout reset f stream@ groups together all input
3506+-- stream elements that belong to the same session. @timeout@ is the maximum
3507+-- lifetime of a session in seconds. All elements belonging to a session are
3508+-- purged after this duration. If "reset" is 'Ture' then the timeout is reset
3509+-- after every event received in the session. Session duration is measured
3510+-- using the timestamp of the first element seen for that session. To detect
3511+-- session timeouts, a monotonic event time clock is maintained using the
3512+-- timestamps seen in the inputs and a timer with a tick duration specified by
3513+-- @tick@.
3514+--
3515+-- @session key@ is a key that uniquely identifies the session for the given
3516+-- element, @timestamp@ characterizes the time when the input element was
3517+-- generated, this is an absolute time measured from some @Epoch@. @session
3518+-- close@ is a boolean indicating whether this element marks the closing of the
3519+-- session. When an input element with @session close@ set to @True@ is seen
3520+-- the session is purged immediately.
3521+--
3522+-- All the input elements belonging to a session are collected using the fold
3523+-- @f@. The session key and the fold result are emitted in the output stream
3524+-- when the session is purged either via the session close event or via the
3525+-- session liftime timeout.
3526+--
3527+-- @since 0.7.0
3528+{-# INLINABLE classifySessionsBy #-}
3529+classifySessionsBy
3530+ :: (IsStream t, MonadAsync m, Ord k)
3531+ => Double -- ^ timer tick in seconds
3532+ -> Double -- ^ session timeout
3533+ -> Bool -- ^ reset the timeout when an event is received
3534+ -> Fold m a b -- ^ Fold to be applied to session events
3535+ -> t m (k, a, Bool, AbsTime) -- ^ session key, timestamp, close event, data
3536+ -> t m (k, b)
3537+classifySessionsBy tick timeout reset (Fold step initial extract) str =
3538+ concatMap (\(Tuple4' _ _ _ s) -> s) $ scanlM' sstep szero stream
3539+
3540+ where
3541+
3542+ timeoutMs = toRelTime (round (timeout * 1000) :: MilliSecond64)
3543+ tickMs = toRelTime (round (tick * 1000) :: MilliSecond64)
3544+ szero = Tuple4' (toAbsTime (0 :: MilliSecond64)) H.empty Map.empty K.nil
3545+
3546+ -- Got a new stream input element
3547+ sstep (Tuple4' evTime hp mp _) (Just (key, a, closing, ts)) =
3548+ -- XXX we should use a heap in pinned memory to scale it to a large
3549+ -- size
3550+ --
3551+ -- deleting a key from the heap is expensive, so we never delete a
3552+ -- key, we just purge it from the Map and it gets purged from the
3553+ -- heap on timeout. We just need an extra lookup in the Map when
3554+ -- the key is purged from the heap, that should not be expensive.
3555+ --
3556+ -- To detect session inactivity we keep a timestamp of the latest event
3557+ -- in the Map along with the fold result. When we purge the session
3558+ -- from the heap we match the timestamp in the heap with the timestamp
3559+ -- in the Map, if the latest timestamp is newer and has not expired we
3560+ -- reinsert the key in the heap.
3561+ --
3562+ -- XXX if the key is an Int, we can also use an IntMap for slightly
3563+ -- better performance.
3564+ --
3565+ let accumulate v = do
3566+ Tuple' _ old <- maybe (initial >>= return . Tuple' ts) return v
3567+ new <- step old a
3568+ return $ Tuple' ts new
3569+ in if closing
3570+ then do
3571+ let (r, mp') = Map.updateLookupWithKey (\_ _ -> Nothing) key mp
3572+ Tuple' _ acc <- accumulate r
3573+ res <- extract acc
3574+ return $ Tuple4' evTime hp mp' (yield (key, res))
3575+ else do
3576+ let r = Map.lookup key mp
3577+ acc <- accumulate r
3578+ let mp' = Map.insert key acc mp
3579+ let hp' =
3580+ case r of
3581+ Nothing ->
3582+ let expiry = addToAbsTime ts timeoutMs
3583+ in H.insert (Entry expiry key) hp
3584+ Just _ -> hp
3585+ -- Event time is maintained as monotonically increasing
3586+ -- time. If we have lagged behind any of the timestamps
3587+ -- seen then we increase it to match the latest time seen
3588+ -- in the timestamps. We also increase it on timer ticks.
3589+ return $ Tuple4' (max evTime ts) hp' mp' K.nil
3590+
3591+ -- Got a timer tick event
3592+ -- XXX can we yield the entries without accumulating them?
3593+ sstep (Tuple4' evTime heap sessions _) Nothing = do
3594+ (hp', mp', out) <- go heap sessions K.nil
3595+ return $ Tuple4' curTime hp' mp' out
3596+
3597+ where
3598+
3599+ curTime = addToAbsTime evTime tickMs
3600+ go hp mp out = do
3601+ let hres = H.uncons hp
3602+ case hres of
3603+ Just (Entry ts key, hp') -> do
3604+ let duration = diffAbsTime curTime ts
3605+ if duration >= timeoutMs
3606+ then do
3607+ let (r, mp') = Map.updateLookupWithKey
3608+ (\_ _ -> Nothing) key mp
3609+ case r of
3610+ Nothing -> go hp' mp' out
3611+ Just (Tuple' latestTS acc) -> do
3612+ let dur = diffAbsTime curTime latestTS
3613+ if dur >= timeoutMs || not reset
3614+ then do
3615+ sess <- extract acc
3616+ go hp' mp' ((key, sess) `K.cons` out)
3617+ else
3618+ -- reset the session timeout
3619+ let expiry = addToAbsTime latestTS timeoutMs
3620+ hp'' = H.insert (Entry expiry key) hp'
3621+ mp'' = Map.insert key (Tuple' latestTS acc) mp'
3622+ in go hp'' mp'' out
3623+ else return (hp, mp, out)
3624+ Nothing -> return (hp, mp, out)
3625+
3626+ -- merge timer events in the stream
3627+ stream = Serial.map Just str `Par.parallel` repeatM timer
3628+ timer = do
3629+ liftIO $ threadDelay (round $ tick * 1000000)
3630+ return Nothing
3631+
3632+-- | Like 'classifySessionsOf' but the session is kept alive if an event is
3633+-- received within the session window. The session times out and gets closed
3634+-- only if no event is received within the specified session window size.
3635+--
3636+-- @since 0.7.0
3637+{-# INLINABLE _classifyKeepAliveSessions #-}
3638+_classifyKeepAliveSessions
3639+ :: (IsStream t, MonadAsync m, Ord k)
3640+ => Double -- ^ session inactive timeout
3641+ -> Fold m a b -- ^ Fold to be applied to session payload data
3642+ -> t m (k, a, Bool, AbsTime) -- ^ session key, data, close flag, timestamp
3643+ -> t m (k, b)
3644+_classifyKeepAliveSessions timeout = classifySessionsBy 1 timeout True
3645+
3646+------------------------------------------------------------------------------
3647+-- Keyed tumbling windows
3648+------------------------------------------------------------------------------
3649+
3650+-- Tumbling windows is a special case of sliding windows where the window slide
3651+-- is the same as the window size. Or it can be a special case of session
3652+-- windows where the reset flag is set to False.
3653+
3654+-- XXX instead of using the early termination flag in the stream, we can use an
3655+-- early terminating fold instead.
3656+
3657+{-
3658+-- | Split the stream into fixed size chunks of specified size. Within each
3659+-- such chunk fold the elements in buckets identified by the keys. A particular
3660+-- bucket fold can be terminated early if a closing flag is encountered in an
3661+-- element for that key.
3662+--
3663+-- @since 0.7.0
3664+{-# INLINABLE classifyChunksOf #-}
3665+classifyChunksOf
3666+ :: (IsStream t, MonadAsync m, Ord k)
3667+ => Int -- ^ window size
3668+ -> Fold m a b -- ^ Fold to be applied to window events
3669+ -> t m (k, a, Bool) -- ^ window key, data, close event
3670+ -> t m (k, b)
3671+classifyChunksOf wsize = classifyChunksBy wsize False
3672+-}
3673+
3674+-- | Split the stream into fixed size time windows of specified interval in
3675+-- seconds. Within each such window, fold the elements in buckets identified by
3676+-- the keys. A particular bucket fold can be terminated early if a closing flag
3677+-- is encountered in an element for that key. Once a fold is terminated the key
3678+-- and value for that bucket are emitted in the output stream.
3679+--
3680+-- Session @timestamp@ in the input stream is an absolute time from some epoch,
3681+-- characterizing the time when the input element was generated. To detect
3682+-- session window end, a monotonic event time clock is maintained synced with
3683+-- the timestamps with a clock resolution of 1 second.
3684+--
3685+-- @since 0.7.0
3686+{-# INLINABLE _classifySessionsOf #-}
3687+_classifySessionsOf
3688+ :: (IsStream t, MonadAsync m, Ord k)
3689+ => Double -- ^ time window size
3690+ -> Fold m a b -- ^ Fold to be applied to window events
3691+ -> t m (k, a, Bool, AbsTime) -- ^ window key, data, close flag, timestamp
3692+ -> t m (k, b)
3693+_classifySessionsOf interval = classifySessionsBy 1 interval False
2343------------------------------------------------------------------------------
3694------------------------------------------------------------------------------
2344-- Exceptions
3695-- Exceptions
2345------------------------------------------------------------------------------
3696------------------------------------------------------------------------------
src/Streamly/Sink.hs−15
@@ -17,9 +17,6 @@ module Streamly.Sink
17 (
17 (
18 Sink (..)
18 Sink (..)
19
19
20− -- * Running
21− , sink
22−
23 -- * Upgrading
20 -- * Upgrading
24 , toFold
21 , toFold
25
22
@@ -71,12 +68,9 @@ import Prelude
71
68
72import Streamly.Fold.Types (Fold(..))
69import Streamly.Fold.Types (Fold(..))
73import Streamly.Sink.Types (Sink(..))
70import Streamly.Sink.Types (Sink(..))
74−import Streamly.Streams.Serial (SerialT)
75
71
76import qualified Data.Map.Strict as Map
72import qualified Data.Map.Strict as Map
77
73
78−import qualified Streamly.Fold as FL
79−
80------------------------------------------------------------------------------
74------------------------------------------------------------------------------
81-- Conversion
75-- Conversion
82------------------------------------------------------------------------------
76------------------------------------------------------------------------------
@@ -90,15 +84,6 @@ toFold (Sink f) = Fold step begin done
90 step _ = f
84 step _ = f
91 done _ = return ()
85 done _ = return ()
92
86
93−------------------------------------------------------------------------------
94−-- Running a sink
95−------------------------------------------------------------------------------
96−
97−-- | Drain a stream to a 'Sink'.
98−{-# INLINE sink #-}
99−sink :: Monad m => Sink m a -> SerialT m a -> m ()
100−sink = FL.foldl' . toFold
101−
102------------------------------------------------------------------------------
87------------------------------------------------------------------------------
103-- Composing with sinks
88-- Composing with sinks
104------------------------------------------------------------------------------
89------------------------------------------------------------------------------
src/Streamly/Streams/Prelude.hs+7
@@ -37,6 +37,7 @@ module Streamly.Streams.Prelude
37 , foldlx'
37 , foldlx'
38 , foldlMx'
38 , foldlMx'
39 , foldl'
39 , foldl'
40+ , runFold
40
41
41 -- Lazy left folds are useful only for reversing the stream
42 -- Lazy left folds are useful only for reversing the stream
42 , foldlS
43 , foldlS
@@ -66,6 +67,8 @@ import Control.Monad.Trans (MonadTrans(..))
66import Prelude hiding (foldr)
67import Prelude hiding (foldr)
67import qualified Prelude
68import qualified Prelude
68
69
70+import Streamly.Fold.Types (Fold (..))
71+
69#ifdef USE_STREAMK_ONLY
72#ifdef USE_STREAMK_ONLY
70import qualified Streamly.Streams.StreamK as S
73import qualified Streamly.Streams.StreamK as S
71#else
74#else
@@ -183,6 +186,10 @@ foldlT :: (Monad m, IsStream t, Monad (s m), MonadTrans s)
183 => (s m b -> a -> s m b) -> s m b -> t m a -> s m b
186 => (s m b -> a -> s m b) -> s m b -> t m a -> s m b
184foldlT f z s = S.foldlT f z (toStreamS s)
187foldlT f z s = S.foldlT f z (toStreamS s)
185
188
189+{-# INLINE runFold #-}
190+runFold :: (Monad m, IsStream t) => Fold m a b -> t m a -> m b
191+runFold (Fold step begin done) = foldlMx' step begin done
192+
186------------------------------------------------------------------------------
193------------------------------------------------------------------------------
187-- Scans
194-- Scans
188------------------------------------------------------------------------------
195------------------------------------------------------------------------------
src/Streamly/String.hs+4−5
@@ -73,7 +73,6 @@ import Streamly.Fold (Fold)
73import Streamly.Mem.Array (Array, toArray)
73import Streamly.Mem.Array (Array, toArray)
74
74
75import qualified Streamly.Prelude as S
75import qualified Streamly.Prelude as S
76−import qualified Streamly.Fold as FL
77import qualified Streamly.Mem.Array.Types as A (unlines)
76import qualified Streamly.Mem.Array.Types as A (unlines)
78import qualified Streamly.Mem.Array as A
77import qualified Streamly.Mem.Array as A
79import qualified Streamly.Mem.Array.Stream as AS
78import qualified Streamly.Mem.Array.Stream as AS
@@ -172,7 +171,7 @@ stripStart = S.dropWhile isSpace
172-- ["lines", "this", "string", "", ""]
171-- ["lines", "this", "string", "", ""]
173{-# INLINE foldLines #-}
172{-# INLINE foldLines #-}
174foldLines :: (Monad m, IsStream t) => t m Char -> Fold m Char b -> t m b
173foldLines :: (Monad m, IsStream t) => t m Char -> Fold m Char b -> t m b
175−foldLines = flip (FL.splitOnSuffix (== '\n'))
174+foldLines = flip (S.splitOnSuffix (== '\n'))
176
175
177-- | Fold each word of the stream using the supplied Fold
176-- | Fold each word of the stream using the supplied Fold
178-- and stream the result.
177-- and stream the result.
@@ -181,7 +180,7 @@ foldLines = flip (FL.splitOnSuffix (== '\n'))
181-- ["fold", "these", "words"]
180-- ["fold", "these", "words"]
182{-# INLINE foldWords #-}
181{-# INLINE foldWords #-}
183foldWords :: (Monad m, IsStream t) => t m Char -> Fold m Char b -> t m b
182foldWords :: (Monad m, IsStream t) => t m Char -> Fold m Char b -> t m b
184−foldWords = flip (FL.wordsBy isSpace)
183+foldWords = flip (S.wordsBy isSpace)
185
184
186foreign import ccall unsafe "u_iswspace"
185foreign import ccall unsafe "u_iswspace"
187 iswspace :: Int -> Int
186 iswspace :: Int -> Int
@@ -205,7 +204,7 @@ isSpace c
205-- 'foldLines' instead.
204-- 'foldLines' instead.
206{-# INLINE lines #-}
205{-# INLINE lines #-}
207lines :: (MonadIO m, IsStream t) => t m Char -> t m (Array Char)
206lines :: (MonadIO m, IsStream t) => t m Char -> t m (Array Char)
208−lines = FL.splitOnSuffix (== '\n') toArray
207+lines = S.splitOnSuffix (== '\n') toArray
209
208
210-- | Break a string up into a list of strings, which were delimited
209-- | Break a string up into a list of strings, which were delimited
211-- by characters representing white space.
210-- by characters representing white space.
@@ -217,7 +216,7 @@ lines = FL.splitOnSuffix (== '\n') toArray
217-- 'foldWords' instead.
216-- 'foldWords' instead.
218{-# INLINE words #-}
217{-# INLINE words #-}
219words :: (MonadIO m, IsStream t) => t m Char -> t m (Array Char)
218words :: (MonadIO m, IsStream t) => t m Char -> t m (Array Char)
220−words = FL.wordsBy isSpace toArray
219+words = S.wordsBy isSpace toArray
221
220
222-- | Flattens the stream of @Array Char@, after appending a terminating
221-- | Flattens the stream of @Array Char@, after appending a terminating
223-- newline to each string.
222-- newline to each string.
test/Prop.hs+1−1
@@ -561,7 +561,7 @@ groupSplitOps desc = do
561 forAll listWithZeroes $ \xs -> do
561 forAll listWithZeroes $ \xs -> do
562 withMaxSuccess maxTestCount $
562 withMaxSuccess maxTestCount $
563 monadicIO $ do
563 monadicIO $ do
564− ys <- S.toList $ FL.splitOn (== 0) toListFL (S.fromList xs)
564+ ys <- S.toList $ S.splitOn (== 0) toListFL (S.fromList xs)
565 listEquals (==) (intercalate [0] ys) xs
565 listEquals (==) (intercalate [0] ys) xs
566
566
567 where
567 where