1{-# LANGUAGE BangPatterns #-}
3{-# LANGUAGE ExistentialQuantification #-}
4{-# LANGUAGE FlexibleContexts #-}
5{-# LANGUAGE PatternSynonyms #-}
6{-# LANGUAGE RankNTypes #-}
7{-# LANGUAGE RecordWildCards #-}
8{-# LANGUAGE ScopedTypeVariables #-}
13-- Module : Streamly.Internal.Data.Unfold
14-- Copyright : (c) 2019 Composewell Technologies
16-- Maintainer : harendra.kumar@gmail.com
17-- Stability : experimental
20-- Streams forcing a closed control flow loop can be categorized under
21-- two types, unfolds and folds, both of these are duals of each other.
23-- Unfold streams are really generators of a sequence of elements, we can also
24-- call them pull style streams. These are lazy producers of streams. On each
25-- evaluation the producer generates the next element. A consumer can
26-- therefore pull elements from the stream whenever it wants to. A stream
27-- consumer can multiplex pull streams by pulling elements from the chosen
28-- streams, therefore, pull streams allow merging or multiplexing. On the
29-- other hand, with this representation we cannot split or demultiplex a
30-- stream. So really these are stream sources that can be generated from a
31-- seed and can be merged or zipped into a single stream.
33-- The dual of Unfolds are Folds. Folds can also be called as push style
34-- streams or reducers. These are strict consumers of streams. We keep pushing
35-- elements to a fold and we can extract the result at any point. A driver can
36-- choose which fold to push to and can also push the same element to multiple
37-- folds. Therefore, folds allow splitting or demultiplexing a stream. On the
38-- other hand, we cannot merge streams using this representation. So really
39-- these are stream consumers that reduce the stream to a single value, these
40-- consumers can be composed such that a stream can be split over multiple
45-- Composing a tree or graph of computations with unfolds can be much more
46-- efficient compared to composing with the Monad instance. The reason is that
47-- unfolds allow the compiler to statically know the state and optimize it
48-- using stream fusion whereas it is not possible with the monad bind because
49-- the state is determined dynamically.
51-- Open control flow style streams can also have two representations. StreamK
52-- is a producer style representation. We can also have a consumer style
53-- representation. We can use that for composable folds in StreamK
56module Streamly.Internal.Data.Unfold
61 -- * Operations on Input
69 -- * Operations on Output
77 , enumerateFromStepIntegral
78 , enumerateFromToIntegral
79 , enumerateFromIntegral
106import Control.Exception (Exception)
107import Data.Void (Void)
108import GHC.Types (SPEC(..))
109import Prelude hiding (concat, map, takeWhile, take, filter)
111import Streamly.Streams.StreamD.Type (Stream(..), Step(..))
112#if __GLASGOW_HASKELL__ < 800
113import Streamly.Streams.StreamD.Type (pattern Stream)
115import Streamly.Internal.Data.Unfold.Types (Unfold(..))
116import Streamly.Internal.Data.Fold.Types (Fold(..))
117import Streamly.Internal.Data.SVar (defState)
118import Control.Monad.Catch (MonadCatch)
120import qualified Control.Monad.Catch as MC
122-------------------------------------------------------------------------------
124-------------------------------------------------------------------------------
126-- | Supply the seed to an unfold closing the input end of the unfold.
130{-# INLINE_NORMAL close #-}
131close :: Unfold m a b -> a -> Unfold m Void b
132close (Unfold ustep uinject) a = Unfold ustep (const $ uinject a)
134-- XXX rename to closeFst/closeSnd
136-- | Supply the first component of the tuple to an unfold that accepts a tuple
137-- as a seed resulting in a fold that accepts the second component of the tuple
142{-# INLINE_NORMAL first #-}
143first :: Unfold m (a, b) c -> a -> Unfold m b c
144first (Unfold ustep uinject) a = Unfold ustep (\b -> uinject (a, b))
146-- | Supply the second component of the tuple to an unfold that accepts a tuple
147-- as a seed resulting in a fold that accepts the first component of the tuple
152{-# INLINE_NORMAL second #-}
153second :: Unfold m (a, b) c -> b -> Unfold m a c
154second (Unfold ustep uinject) b = Unfold ustep (\a -> uinject (a, b))
156{-# INLINE_NORMAL lmap #-}
157lmap :: (a -> c) -> Unfold m c b -> Unfold m a b
158lmap f (Unfold ustep uinject) = Unfold ustep (\x -> uinject $ f x)
160-------------------------------------------------------------------------------
162-------------------------------------------------------------------------------
164-- | Compose an 'Unfold' and a 'Fold'. Given an @Unfold m a b@ and a
165-- @Fold m b c@, returns a monadic action @a -> m c@ representing the
166-- application of the fold on the unfolded stream.
170{-# INLINE_NORMAL fold #-}
171fold :: Monad m => Unfold m a b -> Fold m b c -> a -> m c
172fold (Unfold ustep inject) (Fold fstep initial extract) a =
173 initial >>= \x -> inject a >>= go SPEC x
176 {-# INLINE_LATE go #-}
177 go !_ acc st = acc `seq` do
183 Skip s -> go SPEC acc s
186{-# INLINE_NORMAL map #-}
187map :: Monad m => (b -> c) -> Unfold m a b -> Unfold m a c
188map f (Unfold ustep uinject) = Unfold step uinject
190 {-# INLINE_LATE step #-}
194 Yield x s -> Yield (f x) s
198-------------------------------------------------------------------------------
200-------------------------------------------------------------------------------
202-- | Identity unfold. Generates a singleton stream with the seed as the only
203-- element in the stream.
205-- > singleton = replicateM 1
207{-# INLINE singleton #-}
208singleton :: Monad m => Unfold m a a
209singleton = Unfold step inject
211 inject x = return $ Just x
212 {-# INLINE_LATE step #-}
213 step (Just x) = return $ Yield x Nothing
214 step Nothing = return Stop
216-- | Generates a stream replicating the seed @n@ times.
218{-# INLINE replicateM #-}
219replicateM :: Monad m => Int -> Unfold m a a
220replicateM n = Unfold step inject
222 inject x = return (x, n)
223 {-# INLINE_LATE step #-}
224 step (x, i) = return $
227 else Yield x (x, (i - 1))
229-- | Convert a list of pure values to a 'Stream'
230{-# INLINE_LATE fromList #-}
231fromList :: Monad m => Unfold m [a] a
232fromList = Unfold step inject
235 {-# INLINE_LATE step #-}
236 step (x:xs) = return $ Yield x xs
237 step [] = return Stop
239-------------------------------------------------------------------------------
241-------------------------------------------------------------------------------
243{-# INLINE_NORMAL take #-}
244take :: Monad m => Int -> Unfold m a b -> Unfold m a b
245take n (Unfold step1 inject1) = Unfold step inject
250 {-# INLINE_LATE step #-}
251 step (st, i) | i < n = do
254 Yield x s -> Yield x (s, i + 1)
255 Skip s -> Skip (s, i)
257 step (_, _) = return Stop
259{-# INLINE_NORMAL takeWhileM #-}
260takeWhileM :: Monad m => (b -> m Bool) -> Unfold m a b -> Unfold m a b
261takeWhileM f (Unfold step1 inject1) = Unfold step inject1
263 {-# INLINE_LATE step #-}
269 return $ if b then Yield x s else Stop
270 Skip s -> return $ Skip s
273{-# INLINE takeWhile #-}
274takeWhile :: Monad m => (b -> Bool) -> Unfold m a b -> Unfold m a b
275takeWhile f = takeWhileM (return . f)
277{-# INLINE_NORMAL filterM #-}
278filterM :: Monad m => (b -> m Bool) -> Unfold m a b -> Unfold m a b
279filterM f (Unfold step1 inject1) = Unfold step inject1
281 {-# INLINE_LATE step #-}
287 return $ if b then Yield x s else Skip s
288 Skip s -> return $ Skip s
292filter :: Monad m => (b -> Bool) -> Unfold m a b -> Unfold m a b
293filter f = filterM (return . f)
295-------------------------------------------------------------------------------
297-------------------------------------------------------------------------------
299-- | Can be used to enumerate unbounded integrals. This does not check for
300-- overflow or underflow for bounded integrals.
301{-# INLINE_NORMAL enumerateFromStepIntegral #-}
302enumerateFromStepIntegral :: (Integral a, Monad m) => Unfold m (a, a) a
303enumerateFromStepIntegral = Unfold step inject
305 inject (from, stride) = from `seq` stride `seq` return (from, stride)
306 {-# INLINE_LATE step #-}
307 step !(x, stride) = return $ Yield x $! (x + stride, stride)
309-- We are assuming that "to" is constrained by the type to be within
311{-# INLINE enumerateFromToIntegral #-}
312enumerateFromToIntegral :: (Monad m, Integral a) => a -> Unfold m a a
313enumerateFromToIntegral to =
314 takeWhile (<= to) $ second enumerateFromStepIntegral 1
316{-# INLINE enumerateFromIntegral #-}
317enumerateFromIntegral :: (Monad m, Integral a, Bounded a) => Unfold m a a
318enumerateFromIntegral = enumerateFromToIntegral maxBound
320-------------------------------------------------------------------------------
322-------------------------------------------------------------------------------
324data ConcatState s1 s2 = ConcatOuter s1 | ConcatInner s1 s2
326{-# INLINE_NORMAL concat #-}
327concat :: Monad m => Unfold m a b -> Unfold m b c -> Unfold m a c
328concat (Unfold step1 inject1) (Unfold step2 inject2) = Unfold step inject
332 return $ ConcatOuter s
334 {-# INLINE_LATE step #-}
335 step (ConcatOuter st) = do
340 return $ Skip (ConcatInner s innerSt)
341 Skip s -> return $ Skip (ConcatOuter s)
344 step (ConcatInner ost ist) = do
347 Yield x s -> Yield x (ConcatInner ost s)
348 Skip s -> Skip (ConcatInner ost s)
349 Stop -> Skip (ConcatOuter ost)
351data OuterProductState s1 s2 sy x y =
352 OuterProductOuter s1 y | OuterProductInner s1 sy s2 x
354{-# INLINE_NORMAL outerProduct #-}
355outerProduct :: Monad m
356 => Unfold m a b -> Unfold m c d -> Unfold m (a, c) (b, d)
357outerProduct (Unfold step1 inject1) (Unfold step2 inject2) = Unfold step inject
361 return $ OuterProductOuter s1 y
363 {-# INLINE_LATE step #-}
364 step (OuterProductOuter st1 sy) = do
369 return $ Skip (OuterProductInner s sy s2 x)
370 Skip s -> return $ Skip (OuterProductOuter s sy)
373 step (OuterProductInner ost sy ist x) = do
376 Yield y s -> Yield (x, y) (OuterProductInner ost sy s x)
377 Skip s -> Skip (OuterProductInner ost sy s x)
378 Stop -> Skip (OuterProductOuter ost sy)
380-- XXX This can be used to implement a Monad instance for "Unfold m ()".
382data ConcatMapState s1 s2 = ConcatMapOuter s1 | ConcatMapInner s1 s2
384{-# INLINE_NORMAL concatMapM #-}
386 => (b -> m (Unfold m () c)) -> Unfold m a b -> Unfold m a c
387concatMapM f (Unfold step1 inject1) = Unfold step inject
391 return $ ConcatMapOuter s
393 {-# INLINE_LATE step #-}
394 step (ConcatMapOuter st) = do
398 Unfold step2 inject2 <- f x
399 innerSt <- inject2 ()
400 return $ Skip (ConcatMapInner s (Stream (\_ ss -> step2 ss)
402 Skip s -> return $ Skip (ConcatMapOuter s)
405 step (ConcatMapInner ost (Stream istep ist)) = do
406 r <- istep defState ist
408 Yield x s -> Yield x (ConcatMapInner ost (Stream istep s))
409 Skip s -> Skip (ConcatMapInner ost (Stream istep s))
410 Stop -> Skip (ConcatMapOuter ost)
412------------------------------------------------------------------------------
414------------------------------------------------------------------------------
416-- | Run a side effect before the unfold yields its first element.
419{-# INLINE_NORMAL before #-}
420before :: Monad m => m c -> Unfold m a b -> Unfold m a b
421before action (Unfold step1 inject1) = Unfold step inject
430 {-# INLINE_LATE step #-}
434 Yield x s -> return $ Yield x s
435 Skip s -> return $ Skip s
438-- | Run a side effect whenever the unfold stops normally.
441{-# INLINE_NORMAL after #-}
442after :: Monad m => m c -> Unfold m a b -> Unfold m a b
443after action (Unfold step1 inject1) = Unfold step inject1
447 {-# INLINE_LATE step #-}
451 Yield x s -> return $ Yield x s
452 Skip s -> return $ Skip s
453 Stop -> action >> return Stop
455-- | Run a side effect whenever the unfold aborts due to an exception.
458{-# INLINE_NORMAL onException #-}
459onException :: MonadCatch m => m c -> Unfold m a b -> Unfold m a b
460onException action (Unfold step1 inject1) = Unfold step inject1
464 {-# INLINE_LATE step #-}
466 res <- step1 st `MC.onException` action
468 Yield x s -> return $ Yield x s
469 Skip s -> return $ Skip s
472-- | Run a side effect whenever the unfold stops normally or aborts due to an
476{-# INLINE_NORMAL finally #-}
477finally :: MonadCatch m => m c -> Unfold m a b -> Unfold m a b
478finally action (Unfold step1 inject1) = Unfold step inject1
482 {-# INLINE_LATE step #-}
484 res <- step1 st `MC.onException` action
486 Yield x s -> return $ Yield x s
487 Skip s -> return $ Skip s
488 Stop -> action >> return Stop
490-- | @bracket before after between@ runs the @before@ action and then runs the
491-- @between@ function using the output of @before@ as its argument. When the
492-- @between@ unfold is done or if an exception occurs while running @between@
493-- the @after@ action is run with the output of @before@ as argument.
496{-# INLINE_NORMAL bracket #-}
497bracket :: MonadCatch m
498 => m c -> (c -> m d) -> (c -> Unfold m a b) -> Unfold m a b
499bracket bef aft bet = Unfold step inject
507 go (Unfold step2 inject2) r = do
509 return (Stream (\_ -> step2) st, r)
511 {-# INLINE_LATE step #-}
512 step (Stream step2 state, v) = do
513 res <- step2 defState state `MC.onException` aft v
515 Yield x s -> return $ Yield x (Stream step2 s, v)
516 Skip s -> return $ Skip (Stream step2 s, v)
517 Stop -> aft v >> return Stop
519-- | When unfolding an unfold if an exception occurs, unfold aborts
520-- and the specified exception handler is run with the exception as argument.
523{-# INLINE_NORMAL handle #-}
524handle :: (MonadCatch m, Exception e)
525 => (e -> m b) -> Unfold m a b -> Unfold m a b
526handle f (Unfold step1 inject1) = Unfold step inject
530 inject x = inject1 x >>= return . Just
532 {-# INLINE_LATE step #-}
534 res <- MC.try $ step1 st
536 Left e -> f e >>= \x -> return (Yield x Nothing)
538 Yield x s -> return $ Yield x (Just s)
539 Skip s -> return $ Skip (Just s)
541 step Nothing = return Stop
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list