2{-# LANGUAGE ExistentialQuantification #-}
7-- Module : Streamly.Pipe.Types
8-- Copyright : (c) 2019 Composewell Technologies
10-- Maintainer : harendra.kumar@gmail.com
11-- Stability : experimental
14module Streamly.Pipe.Types
25import Control.Arrow (Arrow(..))
26import Control.Category (Category(..))
27import Data.Maybe (isJust)
28import Data.Semigroup (Semigroup(..))
29import Prelude hiding (zipWith, map, id, unzip, null)
30import Streamly.Strict (Tuple'(..), Tuple3'(..))
32import qualified Prelude
34------------------------------------------------------------------------------
36------------------------------------------------------------------------------
38-- A scan is a much simpler version of pipes. A scan always produces an output
39-- on an input whereas a pipe does not necessarily produce an output on an
40-- input, it might consume multiple inputs before producing an output. That way
41-- it can implement filtering. Similarly, it can produce more than one output
44-- Therefore when two pipes are composed in parallel formation, one may run
45-- slower or faster than the other. If all of them are being fed from the same
46-- source, we may have to buffer the input to match the speeds. In case of
47-- scans we do not have that problem.
49-- We may also need a "Stop" constructor to indicate that we are not generating
50-- any more values and we can have a "Done" constructor to indicate that we are
51-- not consuming any more values. Similarly we can have a stop with error or
52-- exception and a done with error or leftover values.
54-- In generator mode, Continue means no output/continue. In fold mode Continue means
55-- need more input to produce result. we can perhaps call it Continue instead.
61-- | Represents a stateful transformation over an input stream of values of
62-- type @a@ to outputs of type @b@ in 'Monad' @m@.
64-- A pipe uses a consume function and a produce function. It can switch from
65-- consume/fold mode to a produce/source mode. The first step function is a
66-- fold function while the seocnd one is a stream generator function.
68-- We can upgrade a stream or a fold into a pipe. However, streams are more
69-- efficient in generation and folds are more efficient in consumption.
71-- For pure transformation we can have a 'Scan' type. A Scan would be more
72-- efficient in zipping whereas pipes are useful for merging and zipping where
73-- we know buffering can occur. A Scan type can be upgraded to a pipe.
75-- XXX In general the starting state could either be for generation or for
76-- consumption. Currently we are only starting with a consumption state.
78-- An explicit either type for better readability of the code
79data PipeState s1 s2 = Consume s1 | Produce s2
81isProduce :: PipeState s1 s2 -> Bool
88 forall s1 s2. Pipe (s1 -> a -> m (Step (PipeState s1 s2) b))
89 (s2 -> m (Step (PipeState s1 s2) b)) s1
91instance Monad m => Functor (Pipe m a) where
92 {-# INLINE_NORMAL fmap #-}
93 fmap f (Pipe consume produce initial) = Pipe consume' produce' initial
95 {-# INLINE_LATE consume' #-}
99 Yield x s -> Yield (f x) s
100 Continue s -> Continue s
102 {-# INLINE_LATE produce' #-}
106 Yield x s -> Yield (f x) s
107 Continue s -> Continue s
109-- XXX move this to a separate module
110data Deque a = Deque [a] [a]
113null :: Deque a -> Bool
114null (Deque [] []) = True
118snoc :: a -> Deque a -> Deque a
119snoc a (Deque snocList consList) = Deque (a : snocList) consList
122uncons :: Deque a -> Maybe (a, Deque a)
123uncons (Deque snocList consList) =
125 h : t -> Just (h, Deque snocList t)
127 case Prelude.reverse snocList of
128 h : t -> Just (h, Deque [] t)
131-- | The composed pipe distributes the input to both the constituent pipes and
132-- zips the output of the two using a supplied zipping function.
135{-# INLINE_NORMAL zipWith #-}
136zipWith :: Monad m => (a -> b -> c) -> Pipe m i a -> Pipe m i b -> Pipe m i c
137zipWith f (Pipe consumeL produceL stateL) (Pipe consumeR produceR stateR) =
138 Pipe consume produce state
141 -- Left state means we need to consume input from the source. A Right
142 -- state means we either have buffered input or we are in generation
143 -- mode so we do not need input from source in either case.
145 state = Tuple' (Consume stateL, Nothing, Nothing)
146 (Consume stateR, Nothing, Nothing)
148 -- XXX for heavy buffering we need to have the (ring) buffer in pinned
149 -- memory using the Storable instance.
150 {-# INLINE_LATE consume #-}
151 consume (Tuple' (sL, resL, lq) (sR, resR, rq)) a = do
152 s1 <- drive sL resL lq consumeL produceL a
153 s2 <- drive sR resR rq consumeR produceR a
159 drive st res queue fConsume fProduce val = do
161 Nothing -> goConsume st queue val fConsume fProduce
164 Nothing -> (st, Just x, Just $ (Deque [val] []))
165 Just q -> (st, Just x, Just $ snoc val q)
167 {-# INLINE goConsume #-}
168 goConsume stt queue val fConsume stp2 = do
175 Yield x s -> (s, Just x, Nothing)
176 Continue s -> (s, Nothing, Nothing)
178 case uncons queue' of
183 Yield x s -> (s, Just x, Just q')
184 Continue s -> (s, Nothing, Just q')
185 Nothing -> undefined -- never occurs
189 Yield x s -> (s, Just x, queue)
190 Continue s -> (s, Nothing, queue)
192 {-# INLINE_LATE produce #-}
193 produce (Tuple' (sL, resL, lq) (sR, resR, rq)) = do
194 s1 <- drive sL resL lq consumeL produceL
195 s2 <- drive sR resR rq consumeR produceR
201 drive stt res q fConsume fProduce = do
203 Nothing -> goProduce stt q fConsume fProduce
204 Just x -> return (stt, Just x, q)
206 {-# INLINE goProduce #-}
207 goProduce stt queue fConsume fProduce = do
211 -- See yieldOutput. We enter produce mode only when
212 -- each pipe is either in Produce state or the
213 -- queue is non-empty. So this case cannot occur.
216 case uncons queue' of
219 -- We provide a guarantee that if the
220 -- queue is "Just" it is always
221 -- non-empty. yieldOutput and goConsume
227 Yield x s -> (s, Just x, q')
228 Continue s -> (s, Nothing, q')
229 Nothing -> return (stt, Nothing, Nothing)
233 Yield x s -> (s, Just x, queue)
234 Continue s -> (s, Nothing, queue)
236 {-# INLINE yieldOutput #-}
237 yieldOutput s1@(sL', resL', lq') s2@(sR', resR', rq') = return $
238 -- switch to produce mode if we do not need input
239 if (isProduce sL' || isJust lq') && (isProduce sR' || isJust rq')
241 case (resL', resR') of
242 (Just xL, Just xR) ->
243 Yield (f xL xR) (Produce (Tuple' (clear s1) (clear s2)))
244 _ -> Continue (Produce (Tuple' s1 s2))
246 case (resL', resR') of
247 (Just xL, Just xR) ->
248 Yield (f xL xR) (Consume (Tuple' (clear s1) (clear s2)))
249 _ -> Continue (Consume (Tuple' s1 s2))
250 where clear (s, _, q) = (s, Nothing, q)
252instance Monad m => Applicative (Pipe m a) where
254 pure b = Pipe (\_ _ -> pure $ Yield b (Consume ())) undefined ()
258-- | The composed pipe distributes the input to both the constituent pipes and
259-- merges the outputs of the two.
262{-# INLINE_NORMAL tee #-}
263tee :: Monad m => Pipe m a b -> Pipe m a b -> Pipe m a b
264tee (Pipe consumeL produceL stateL) (Pipe consumeR produceR stateR) =
265 Pipe consume produce state
268 state = Tuple' (Consume stateL) (Consume stateR)
270 consume (Tuple' sL sR) a = do
275 Yield x s -> Yield x (Produce (Tuple3' (Just a) s sR))
276 Continue s -> Continue (Produce (Tuple3' (Just a) s sR))
277 -- XXX we should never come here unless the initial state of the
278 -- first pipe is set to "Right".
279 Produce _st -> undefined -- do
283 Yield x s -> Yield x (Right (Tuple3' (Just a) s sR))
284 Continue s -> Continue (Right (Tuple3' (Just a) s sR))
287 produce (Tuple3' (Just a) sL sR) = do
293 let nextL s = Consume (Tuple' sL s)
294 let nextR s = Produce (Tuple3' Nothing sL s)
296 Yield x s@(Consume _) -> Yield x (nextL s)
297 Yield x s@(Produce _) -> Yield x (nextR s)
298 Continue s@(Consume _) -> Continue (nextL s)
299 Continue s@(Produce _) -> Continue (nextR s)
300 -- We will never come here unless the initial state of
301 -- second pipe is set to "Right".
302 Produce _ -> undefined
305 let next s = Produce (Tuple3' (Just a) s sR)
307 Yield x s -> Yield x (next s)
308 Continue s -> Continue (next s)
310 produce (Tuple3' Nothing sL sR) = do
312 Consume _ -> undefined -- should never occur
316 Yield x s@(Consume _) ->
317 Yield x (Consume (Tuple' sL s))
318 Yield x s@(Produce _) ->
319 Yield x (Produce (Tuple3' Nothing sL s))
320 Continue s@(Consume _) ->
321 Continue (Consume (Tuple' sL s))
322 Continue s@(Produce _) ->
323 Continue (Produce (Tuple3' Nothing sL s))
325instance Monad m => Semigroup (Pipe m a b) where
329-- | Lift a pure function to a 'Pipe'.
333map :: Monad m => (a -> b) -> Pipe m a b
334map f = Pipe consume undefined ()
336 consume _ a = return $ Yield (f a) (Consume ())
339-- | A hollow or identity 'Pipe' passes through everything that comes in.
343id :: Monad m => Pipe m a a
347-- | Compose two pipes such that the output of the second pipe is attached to
348-- the input of the first pipe.
351{-# INLINE_NORMAL compose #-}
352compose :: Monad m => Pipe m b c -> Pipe m a b -> Pipe m a c
353compose (Pipe consumeL produceL stateL) (Pipe consumeR produceR stateR) =
354 Pipe consume produce state
358 state = Tuple' (Consume stateL) (Consume stateR)
360 consume (Tuple' sL sR) a = do
365 rres <- consumeR st a
372 lres <- consumeL stt x
373 return $ case lres of
374 Yield y s1@(Consume _) ->
375 Yield y (next $ Tuple' s1 sR')
376 Yield y s1@(Produce _) ->
377 Yield y (Produce $ Tuple' s1 sR')
378 Continue s1@(Consume _) ->
379 Continue (next $ Tuple' s1 sR')
380 Continue s1@(Produce _) ->
381 Continue (Produce $ Tuple' s1 sR')
382 Continue s1@(Consume _) ->
383 return $ Continue (Consume $ Tuple' sL s1)
384 Continue s1@(Produce _) ->
385 return $ Continue (Produce $ Tuple' sL s1)
386 Produce _ -> undefined
387 -- XXX we should never come here unless the initial state of the
388 -- first pipe is set to "Right".
389 Produce _ -> undefined
391 -- XXX we need to write the code in mor optimized fashion. Use Continue
392 -- more and less yield points.
393 produce (Tuple' sL sR) = do
397 let next s = if isProduce sR then Produce s else Consume s
399 Yield x s@(Consume _) -> Yield x (next $ Tuple' s sR)
400 Yield x s@(Produce _) -> Yield x (Produce $ Tuple' s sR)
401 Continue s@(Consume _) -> Continue (next $ Tuple' s sR)
402 Continue s@(Produce _) -> Continue (Produce $ Tuple' s sR)
415 Yield y s1@(Consume _) ->
416 Yield y (next $ Tuple' s1 sR')
417 Yield y s1@(Produce _) ->
418 Yield y (Produce $ Tuple' s1 sR')
419 Continue s1@(Consume _) ->
420 Continue (next $ Tuple' s1 sR')
421 Continue s1@(Produce _) ->
422 Continue (Produce $ Tuple' s1 sR')
423 Continue s1@(Consume _) ->
424 return $ Continue (Consume $ Tuple' sL s1)
425 Continue s1@(Produce _) ->
426 return $ Continue (Produce $ Tuple' sL s1)
427 Consume _ -> return $ Continue (Consume $ Tuple' sL sR)
429instance Monad m => Category (Pipe m) where
436unzip :: Pipe m a x -> Pipe m b y -> Pipe m (a, b) (x, y)
439instance Monad m => Arrow (Pipe m) where
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