on 162fc17 help sign in
534 lines 18 kB history raw
1-- |
2-- Module : Streamly.Internal.Data.Stream.Bottom
3-- Copyright : (c) 2017 Composewell Technologies
4-- License : BSD-3-Clause
5-- Maintainer : streamly@composewell.com
6-- Stability : experimental
7-- Portability : GHC
8--
9-- Bottom level Stream module that can be used by all other upper level
10-- Stream modules.
12module Streamly.Internal.Data.Stream.Bottom
13 (
14 -- * Generation
15 fromPure
16 , fromEffect
17 , fromList
18 , timesWith
19 , absTimesWith
20 , relTimesWith
22 -- * Folds
23 , fold
24 , foldContinue
25 , foldBreak
27 -- * Scans
28 , smapM
29 -- $smapM_Notes
30 , postscan
31 , catMaybes
32 , scanMaybe
34 , take
35 , takeWhile
36 , takeEndBy
37 , drop
38 , findIndices
40 -- * Merge
41 , intersperseM
43 -- * Fold and Unfold
44 , reverse
45 , reverse'
47 -- * Expand
48 , concatM
49 , concatMapM
50 , concatMap
52 -- * Reduce
53 , foldManyPost
55 -- * Zipping
56 , zipWithM
57 , zipWith
58 )
59where
61#include "inline.hs"
63import Control.Monad.IO.Class (MonadIO(..))
64import Streamly.Internal.Data.Fold.Type (Fold (..))
65import Streamly.Internal.Data.Time.Units (AbsTime, RelTime64, addToAbsTime64)
66import Streamly.Internal.Data.Unboxed (Unboxed)
67import Streamly.Internal.System.IO (defaultChunkSize)
69import qualified Streamly.Internal.Data.Array.Unboxed.Type as A
70import qualified Streamly.Internal.Data.Fold as Fold
71import qualified Streamly.Internal.Data.Stream.Common as Common
72import qualified Streamly.Internal.Data.Stream.StreamK as K
73import qualified Streamly.Internal.Data.Stream.StreamD as D
75import Prelude hiding (take, takeWhile, drop, reverse, concatMap, map, zipWith)
77import Streamly.Internal.Data.Stream.Type
79--
80-- $setup
81-- >>> :m
82-- >>> import Control.Monad (join)
83-- >>> import Control.Monad.Trans.Class (lift)
84-- >>> import Data.Function (fix, (&))
85-- >>> import Data.Maybe (fromJust, isJust)
86-- >>> import Data.Semigroup (cycle1)
87-- >>> import Prelude hiding (take, takeWhile, drop, reverse)
88-- >>> import System.IO.Unsafe (unsafePerformIO)
89-- >>> import Streamly.Internal.Data.Stream as Stream
90-- >>> import qualified Streamly.Data.Array.Unboxed as Array
91-- >>> import qualified Streamly.Internal.Data.Fold as Fold
92-- >>> import qualified Streamly.Internal.Data.Parser as Parser
93-- >>> import qualified Streamly.Internal.Data.Unfold as Unfold
95------------------------------------------------------------------------------
96-- Generation
97------------------------------------------------------------------------------
99-- |
100-- >>> fromList = Prelude.foldr Stream.cons Stream.nil
101--
102-- Construct a stream from a list of pure values. This is more efficient than
103-- 'fromFoldable'.
104--
105-- @since 0.9.0
106{-# INLINE fromList #-}
107fromList :: Monad m => [a] -> Stream m a
108fromList = fromStreamK . Common.fromList
110------------------------------------------------------------------------------
111-- Generation - Time related
112------------------------------------------------------------------------------
114-- | @timesWith g@ returns a stream of time value tuples. The first component
115-- of the tuple is an absolute time reference (epoch) denoting the start of the
116-- stream and the second component is a time relative to the reference.
117--
118-- The argument @g@ specifies the granularity of the relative time in seconds.
119-- A lower granularity clock gives higher precision but is more expensive in
120-- terms of CPU usage. Any granularity lower than 1 ms is treated as 1 ms.
121--
122-- >>> import Control.Concurrent (threadDelay)
123-- >>> f = Fold.drainBy (\x -> print x >> threadDelay 1000000)
124-- >>> Stream.fold f $ Stream.take 3 $ Stream.timesWith 0.01
125-- (AbsTime (TimeSpec {sec = ..., nsec = ...}),RelTime64 (NanoSecond64 ...))
126-- (AbsTime (TimeSpec {sec = ..., nsec = ...}),RelTime64 (NanoSecond64 ...))
127-- (AbsTime (TimeSpec {sec = ..., nsec = ...}),RelTime64 (NanoSecond64 ...))
128--
129-- Note: This API is not safe on 32-bit machines.
130--
131-- /Pre-release/
132--
133{-# INLINE timesWith #-}
134timesWith :: MonadIO m => Double -> Stream m (AbsTime, RelTime64)
135timesWith g = fromStreamD $ D.times g
137-- | @absTimesWith g@ returns a stream of absolute timestamps using a clock of
138-- granularity @g@ specified in seconds. A low granularity clock is more
139-- expensive in terms of CPU usage. Any granularity lower than 1 ms is treated
140-- as 1 ms.
141--
142-- >>> f = Fold.drainBy print
143-- >>> Stream.fold f $ Stream.delayPre 1 $ Stream.take 3 $ Stream.absTimesWith 0.01
144-- AbsTime (TimeSpec {sec = ..., nsec = ...})
145-- AbsTime (TimeSpec {sec = ..., nsec = ...})
146-- AbsTime (TimeSpec {sec = ..., nsec = ...})
147--
148-- Note: This API is not safe on 32-bit machines.
149--
150-- /Pre-release/
151--
152{-# INLINE absTimesWith #-}
153absTimesWith :: MonadIO m => Double -> Stream m AbsTime
154absTimesWith = fmap (uncurry addToAbsTime64) . timesWith
156-- | @relTimesWith g@ returns a stream of relative time values starting from 0,
157-- using a clock of granularity @g@ specified in seconds. A low granularity
158-- clock is more expensive in terms of CPU usage. Any granularity lower than 1
159-- ms is treated as 1 ms.
160--
161-- >>> f = Fold.drainBy print
162-- >>> Stream.fold f $ Stream.delayPre 1 $ Stream.take 3 $ Stream.relTimesWith 0.01
163-- RelTime64 (NanoSecond64 ...)
164-- RelTime64 (NanoSecond64 ...)
165-- RelTime64 (NanoSecond64 ...)
166--
167-- Note: This API is not safe on 32-bit machines.
168--
169-- /Pre-release/
170--
171{-# INLINE relTimesWith #-}
172relTimesWith :: MonadIO m => Double -> Stream m RelTime64
173relTimesWith = fmap snd . timesWith
175------------------------------------------------------------------------------
176-- Elimination - Running a Fold
177------------------------------------------------------------------------------
179-- | We can create higher order folds using 'foldContinue'. We can fold a
180-- number of streams to a given fold efficiently with full stream fusion. For
181-- example, to fold a list of streams on the same sum fold:
182--
183-- >>> concatFold = Prelude.foldl Stream.foldContinue Fold.sum
184--
185-- >>> fold f = Fold.finish . Stream.foldContinue f
186--
187-- @since 0.9.0
188{-# INLINE foldContinue #-}
189foldContinue :: Monad m => Fold m a b -> Stream m a -> Fold m a b
190foldContinue f s = D.foldContinue f $ toStreamD s
192-- | Fold a stream using the supplied left 'Fold' and reducing the resulting
193-- expression strictly at each step. The behavior is similar to 'foldl''. A
194-- 'Fold' can terminate early without consuming the full stream. See the
195-- documentation of individual 'Fold's for termination behavior.
196--
197-- >>> Stream.fold Fold.sum (Stream.enumerateFromTo 1 100)
198-- 5050
199--
200-- Folds never fail, therefore, they produce a default value even when no input
201-- is provided. It means we can always fold an empty stream and get a valid
202-- result. For example:
203--
204-- >>> Stream.fold Fold.sum Stream.nil
205-- 0
206--
207-- >>> fold f = Stream.parse (Parser.fromFold f)
208--
209-- @since 0.9.0
210{-# INLINE fold #-}
211fold :: Monad m => Fold m a b -> Stream m a -> m b
212fold fl strm = D.fold fl $ D.fromStreamK $ toStreamK strm
214-- | Like 'fold' but also returns the remaining stream.
215--
216-- /Not fused/
217--
218-- @since 0.9.0
219{-# INLINE foldBreak #-}
220foldBreak :: Monad m => Fold m a b -> Stream m a -> m (b, Stream m a)
221{-
222-- XXX This shows quadratic performance when used recursively perhaps because
223-- of StreamK to StreamD conversions not getting eliminated sue to recursion.
224foldBreak fl (Stream strm) = fmap f $ D.foldBreak fl $ D.fromStreamK strm
226 where
228 f (b, str) = (b, Stream (D.toStreamK str))
229-}
230foldBreak fl strm = fmap f $ K.foldBreak fl (toStreamK strm)
232 where
234 f (b, str) = (b, fromStreamK str)
236------------------------------------------------------------------------------
237-- Transformation
238------------------------------------------------------------------------------
240{-
241-- |
242-- >>> map = fmap
243--
244-- Same as 'fmap'.
245--
246-- >>> Stream.fold Fold.toList $ fmap (+1) $ Stream.fromList [1,2,3]
247-- [2,3,4]
248--
249{-# INLINE map #-}
250map :: Monad m => (a -> b) -> Stream m a -> Stream m b
251map f = fromStreamD . D.map f . toStreamD
252-}
254-- | Postscan a stream using the given monadic fold.
255--
256-- The following example extracts the input stream up to a point where the
257-- running average of elements is no more than 10:
258--
259-- >>> import Data.Maybe (fromJust)
260-- >>> let avg = Fold.teeWith (/) Fold.sum (fmap fromIntegral Fold.length)
261-- >>> s = Stream.enumerateFromTo 1.0 100.0
262-- >>> :{
263-- Stream.fold Fold.toList
264-- $ fmap (fromJust . fst)
265-- $ Stream.takeWhile (\(_,x) -> x <= 10)
266-- $ Stream.postscan (Fold.tee Fold.last avg) s
267-- :}
268-- [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]
269--
270-- @since 0.9.0
271{-# INLINE postscan #-}
272postscan :: Monad m => Fold m a b -> Stream m a -> Stream m b
273postscan fld = fromStreamD . D.postscanOnce fld . toStreamD
275-- $smapM_Notes
276--
277-- The stateful step function can be simplified to @(s -> a -> m b)@ to provide
278-- a read-only environment. However, that would just be 'mapM'.
279--
280-- The initial action could be @m (s, Maybe b)@, and we can also add a final
281-- action @s -> m (Maybe b)@. This can be used to get pre/post scan like
282-- functionality and also to flush the state in the end like scanlMAfter'.
283-- We can also use it along with a fusible version of bracket to get
284-- scanlMAfter' like functionality. See issue #677.
285--
286-- This can be further generalized to a type similar to Fold/Parser, giving it
287-- filtering and parsing capability as well (this is in fact equivalent to
288-- parseMany):
289--
290-- smapM :: (s -> a -> m (Step s b)) -> m s -> Stream m a -> Stream m b
291--
293-- | A stateful 'mapM', equivalent to a left scan, more like mapAccumL.
294-- Hopefully, this is a better alternative to @scan@. Separation of state from
295-- the output makes it easier to think in terms of a shared state, and also
296-- makes it easier to keep the state fully strict and the output lazy.
297--
298-- See also: 'postscan'
299--
300-- /Pre-release/
301--
302{-# INLINE smapM #-}
303smapM :: Monad m =>
304 (s -> a -> m (s, b))
305 -> m s
306 -> Stream m a
307 -> Stream m b
308smapM step initial stream =
309 -- XXX implement this directly instead of using postscan
310 let f = Fold.foldlM'
311 (\(s, _) a -> step s a)
312 (fmap (,undefined) initial)
313 in fmap snd $ postscan f stream
315-- | In a stream of 'Maybe's, discard 'Nothing's and unwrap 'Just's.
316--
317-- >>> catMaybes = Stream.mapMaybe id
318-- >>> catMaybes = fmap fromJust . Stream.filter isJust
319--
320-- /Pre-release/
321--
322{-# INLINE catMaybes #-}
323catMaybes :: Monad m => Stream m (Maybe a) -> Stream m a
324-- catMaybes = fmap fromJust . filter isJust
325catMaybes = fromStreamD . D.catMaybes . toStreamD
327-- | Use a filtering fold on a stream.
328--
329-- >>> scanMaybe f = Stream.catMaybes . Stream.postscan f
330--
331-- @since 0.9.0
332{-# INLINE scanMaybe #-}
333scanMaybe :: Monad m => Fold m a (Maybe b) -> Stream m a -> Stream m b
334scanMaybe p = catMaybes . postscan p
336------------------------------------------------------------------------------
337-- Transformation - Trimming
338------------------------------------------------------------------------------
340-- | Take first 'n' elements from the stream and discard the rest.
341--
342-- @since 0.9.0
343{-# INLINE take #-}
344take :: Monad m => Int -> Stream m a -> Stream m a
345-- take n = scanMaybe (Fold.taking n)
346take n m = fromStreamD $ D.take n $ toStreamD m
348-- | End the stream as soon as the predicate fails on an element.
349--
350-- @since 0.9.0
351{-# INLINE takeWhile #-}
352takeWhile :: Monad m => (a -> Bool) -> Stream m a -> Stream m a
353-- takeWhile p = scanMaybe (Fold.takingEndBy_ (not . p))
354takeWhile p m = fromStreamD $ D.takeWhile p $ toStreamD m
356{-# INLINE takeEndBy #-}
357takeEndBy :: Monad m => (a -> Bool) -> Stream m a -> Stream m a
358-- takeEndBy p = scanMaybe (Fold.takingEndBy p)
359takeEndBy p m = fromStreamD $ D.takeEndBy p $ toStreamD m
361-- | Discard first 'n' elements from the stream and take the rest.
362--
363-- @since 0.9.0
364{-# INLINE drop #-}
365drop :: Monad m => Int -> Stream m a -> Stream m a
366-- drop n = scanMaybe (Fold.dropping n)
367drop n m = fromStreamD $ D.drop n $ toStreamD m
369------------------------------------------------------------------------------
370-- Searching
371------------------------------------------------------------------------------
373-- | Find all the indices where the element in the stream satisfies the given
374-- predicate.
375--
376-- >>> findIndices p = Stream.fold (Fold.findIndices p)
377--
378-- @since 0.9.0
379{-# INLINE findIndices #-}
380findIndices :: Monad m => (a -> Bool) -> Stream m a -> Stream m Int
381-- findIndices p = scanMaybe (Fold.findIndices p)
382findIndices p m = fromStreamD $ D.findIndices p (toStreamD m)
384------------------------------------------------------------------------------
385-- Transformation by Inserting
386------------------------------------------------------------------------------
388-- intersperseM = intersperseBySpan 1
390-- | Insert an effect and its output before consuming an element of a stream
391-- except the first one.
392--
393-- >>> input = Stream.fromList "hello"
394-- >>> Stream.fold Fold.toList $ Stream.trace putChar $ Stream.intersperseM (putChar '.' >> return ',') input
395-- h.,e.,l.,l.,o"h,e,l,l,o"
396--
397-- Be careful about the order of effects. In the above example we used trace
398-- after the intersperse, if we use it before the intersperse the output would
399-- be he.l.l.o."h,e,l,l,o".
400--
401-- >>> Stream.fold Fold.toList $ Stream.intersperseM (putChar '.' >> return ',') $ Stream.trace putChar input
402-- he.l.l.o."h,e,l,l,o"
403--
404-- @since 0.9.0
405{-# INLINE intersperseM #-}
406intersperseM :: Monad m => m a -> Stream m a -> Stream m a
407intersperseM m = fromStreamD . D.intersperseM m . toStreamD
409------------------------------------------------------------------------------
410-- Transformation by Reordering
411------------------------------------------------------------------------------
413-- XXX Use a compact region list to temporarily store the list, in both reverse
414-- as well as in reverse'.
415--
416-- /Note:/ 'reverse'' is much faster than this, use that when performance
417-- matters.
418--
419-- | Returns the elements of the stream in reverse order. The stream must be
420-- finite. Note that this necessarily buffers the entire stream in memory.
421--
422-- >>> reverse = Stream.foldlT (flip Stream.cons) Stream.nil
423--
424-- @since 0.9.0
425{-# INLINE reverse #-}
426reverse :: Monad m => Stream m a -> Stream m a
427reverse s = fromStreamD $ D.reverse $ toStreamD s
429-- | Like 'reverse' but several times faster, requires a 'Storable' instance.
430--
431-- /Pre-release/
432{-# INLINE reverse' #-}
433reverse' :: (MonadIO m, Unboxed a) => Stream m a -> Stream m a
434-- reverse' s = fromStreamD $ D.reverse' $ toStreamD s
435reverse' =
436 fromStreamD
437 . A.flattenArraysRev -- unfoldMany A.readRev
438 . D.fromStreamK
439 . K.reverse
440 . D.toStreamK
441 . A.arraysOf defaultChunkSize
442 . toStreamD
444------------------------------------------------------------------------------
445-- Combine streams and flatten
446------------------------------------------------------------------------------
448-- | Map a stream producing monadic function on each element of the stream
449-- and then flatten the results into a single stream. Since the stream
450-- generation function is monadic, unlike 'concatMap', it can produce an
451-- effect at the beginning of each iteration of the inner loop.
452--
453-- @since 0.9.0
454{-# INLINE concatMapM #-}
455concatMapM :: Monad m => (a -> m (Stream m b)) -> Stream m a -> Stream m b
456concatMapM f m = fromStreamD $ D.concatMapM (fmap toStreamD . f) (toStreamD m)
458-- | Map a stream producing function on each element of the stream and then
459-- flatten the results into a single stream.
460--
461-- >>> concatMap f = Stream.concatMapM (return . f)
462-- >>> concatMap f = Stream.concatMapWith Stream.append f
463-- >>> concatMap f = Stream.concat . fmap f
464-- >>> concatMap f = Stream.unfoldMany (Unfold.lmap f Unfold.fromStream)
465--
466-- @since 0.9.0
467{-# INLINE concatMap #-}
468concatMap ::Monad m => (a -> Stream m b) -> Stream m a -> Stream m b
469concatMap f m = fromStreamD $ D.concatMap (toStreamD . f) (toStreamD m)
471-- | Given a stream value in the underlying monad, lift and join the underlying
472-- monad with the stream monad.
473--
474-- >>> concatM = Stream.concat . Stream.fromEffect
475-- >>> concatM = Stream.concat . lift -- requires (MonadTrans t)
476-- >>> concatM = join . lift -- requires (MonadTrans t, Monad (Stream m))
477--
478-- See also: 'concat', 'sequence'
479--
480-- /Internal/
481--
482{-# INLINE concatM #-}
483concatM :: Monad m => m (Stream m a) -> Stream m a
484concatM generator = concatMapM (\() -> generator) (fromPure ())
486-- | Like 'foldMany' but appends empty fold output if the fold and stream
487-- termination aligns:
488--
489-- >>> f = Fold.take 2 Fold.sum
490-- >>> Stream.fold Fold.toList $ Stream.foldManyPost f $ Stream.fromList []
491-- [0]
492-- >>> Stream.fold Fold.toList $ Stream.foldManyPost f $ Stream.fromList [1..9]
493-- [3,7,11,15,9]
494-- >>> Stream.fold Fold.toList $ Stream.foldManyPost f $ Stream.fromList [1..10]
495-- [3,7,11,15,19,0]
496--
497-- /Pre-release/
498--
499{-# INLINE foldManyPost #-}
500foldManyPost
501 :: Monad m
502 => Fold m a b
503 -> Stream m a
504 -> Stream m b
505foldManyPost f m = fromStreamD $ D.foldManyPost f (toStreamD m)
507------------------------------------------------------------------------------
508-- Zipping
509------------------------------------------------------------------------------
511-- | Like 'zipWith' but using a monadic zipping function.
512--
513-- @since 0.9.0
514{-# INLINE zipWithM #-}
515zipWithM :: Monad m =>
516 (a -> b -> m c) -> Stream m a -> Stream m b -> Stream m c
517zipWithM f m1 m2 = fromStreamD $ D.zipWithM f (toStreamD m1) (toStreamD m2)
519-- | Stream @a@ is evaluated first, followed by stream @b@, the resulting
520-- elements @a@ and @b@ are then zipped using the supplied zip function and the
521-- result @c@ is yielded to the consumer.
522--
523-- If stream @a@ or stream @b@ ends, the zipped stream ends. If stream @b@ ends
524-- first, the element @a@ from previous evaluation of stream @a@ is discarded.
525--
526-- >>> s1 = Stream.fromList [1,2,3]
527-- >>> s2 = Stream.fromList [4,5,6]
528-- >>> Stream.fold Fold.toList $ Stream.zipWith (+) s1 s2
529-- [5,7,9]
530--
531-- @since 0.9.0
532{-# INLINE zipWith #-}
533zipWith :: Monad m => (a -> b -> c) -> Stream m a -> Stream m b -> Stream m c
534zipWith f m1 m2 = fromStreamD $ D.zipWith f (toStreamD m1) (toStreamD m2)