{-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE RankNTypes #-} #include "../inline.hs" -- | -- Module : Streamly.Streams.StreamD.Type -- Copyright : (c) 2018 Harendra Kumar -- Copyright : (c) Roman Leshchinskiy 2008-2010 -- -- License : BSD3 -- Maintainer : harendra.kumar@gmail.com -- Stability : experimental -- Portability : GHC module Streamly.Streams.StreamD.Type ( -- * The stream type Step (..) -- XXX UnStream is exported to avoid a performance issue in concatMap if we -- use the pattern synonym "Stream". #if __GLASGOW_HASKELL__ >= 800 , Stream (Stream, UnStream) #else , Stream (UnStream) , pattern Stream #endif , fromStreamK , toStreamK , map , mapM , foldrT , foldrM , foldrMx , foldr , foldl' , foldlM' , foldlx' , foldlMx' , toList , fromList , eqBy , cmpBy , take , groupsOf ) where import Control.Applicative (liftA2) import Control.Monad.Trans (lift, MonadTrans) import GHC.Types (SPEC(..)) import Prelude hiding (map, mapM, foldr, take) import Streamly.SVar (State(..), adaptState, defState) import Streamly.Fold.Types (Fold(..)) import qualified Streamly.Streams.StreamK as K ------------------------------------------------------------------------------ -- The direct style stream type ------------------------------------------------------------------------------ -- | A stream is a succession of 'Step's. A 'Yield' produces a single value and -- the next state of the stream. 'Stop' indicates there are no more values in -- the stream. data Step s a = Yield a s | Skip s | Stop instance Functor (Step s) where {-# INLINE fmap #-} fmap f (Yield x s) = Yield (f x) s fmap _ (Skip s) = Skip s fmap _ Stop = Stop -- gst = global state -- | A stream consists of a step function that generates the next step given a -- current state, and the current state. data Stream m a = forall s. UnStream (State K.Stream m a -> s -> m (Step s a)) s unShare :: Stream m a -> Stream m a unShare (UnStream step state) = UnStream step' state where step' gst = step (adaptState gst) pattern Stream :: (State K.Stream m a -> s -> m (Step s a)) -> s -> Stream m a pattern Stream step state <- (unShare -> UnStream step state) where Stream = UnStream #if __GLASGOW_HASKELL__ >= 802 {-# COMPLETE Stream #-} #endif {-# INLINE_LATE fromStreamK #-} fromStreamK :: Monad m => K.Stream m a -> Stream m a fromStreamK = Stream step where step gst m1 = let stop = return Stop single a = return $ Yield a K.nil yieldk a r = return $ Yield a r in K.foldStreamShared gst yieldk single stop m1 -- Convert a direct stream to and from CPS encoded stream {-# INLINE_LATE toStreamK #-} toStreamK :: Monad m => Stream m a -> K.Stream m a toStreamK (Stream step state) = go state where go st = K.mkStream $ \gst yld sng stp -> do r <- step gst st case r of Yield x s -> yld x (go s) Skip s -> K.foldStreamShared gst yld sng stp $ go s Stop -> stp #ifndef DISABLE_FUSION {-# RULES "fromStreamK/toStreamK fusion" forall s. toStreamK (fromStreamK s) = s #-} {-# RULES "toStreamK/fromStreamK fusion" forall s. fromStreamK (toStreamK s) = s #-} #endif ------------------------------------------------------------------------------ -- Instances ------------------------------------------------------------------------------ -- | Map a monadic function over a 'Stream' {-# INLINE_NORMAL mapM #-} mapM :: Monad m => (a -> m b) -> Stream m a -> Stream m b mapM f (Stream step state) = Stream step' state where {-# INLINE_LATE step' #-} step' gst st = do r <- step (adaptState gst) st case r of Yield x s -> f x >>= \a -> return $ Yield a s Skip s -> return $ Skip s Stop -> return Stop {-# INLINE map #-} map :: Monad m => (a -> b) -> Stream m a -> Stream m b map f = mapM (return . f) instance Monad m => Functor (Stream m) where {-# INLINE fmap #-} fmap = map -- XXX Use of SPEC constructor in folds causes 2x performance degradation in -- one shot operations, but helps immensely in operations composed of multiple -- combinators or the same combinator many times. There seems to be an -- opportunity to optimize here, can we get both, better perf for single ops -- as well as composed ops? Without SPEC, all single operation benchmarks -- become 2x faster. -- The way we want a left fold to be strict, dually we want the right fold to -- be lazy. The correct signature of the fold function to keep it lazy must be -- (a -> m b -> m b) instead of (a -> b -> m b). We were using the latter -- earlier, which is incorrect. In the latter signature we have to feed the -- value to the fold function after evaluating the monadic action, depending on -- the bind behavior of the monad, the action may get evaluated immediately -- introducing unnecessary strictness to the fold. If the implementation is -- lazy the following example, must work: -- -- S.foldrM (\x t -> if x then return t else return False) (return True) -- (S.fromList [False,undefined] :: SerialT IO Bool) -- {-# INLINE_NORMAL foldrM #-} foldrM :: Monad m => (a -> m b -> m b) -> m b -> Stream m a -> m b foldrM f z (Stream step state) = go SPEC state where go !_ st = do r <- step defState st case r of Yield x s -> f x (go SPEC s) Skip s -> go SPEC s Stop -> z {-# INLINE_NORMAL foldrMx #-} foldrMx :: Monad m => (a -> m x -> m x) -> m x -> (m x -> m b) -> Stream m a -> m b foldrMx fstep final convert (Stream step state) = convert $ go SPEC state where go !_ st = do r <- step defState st case r of Yield x s -> fstep x (go SPEC s) Skip s -> go SPEC s Stop -> final -- Note that foldr works on pure values, therefore it becomes necessarily -- strict when the monad m is strict. In that case it cannot terminate early, -- it would evaluate all of its input. Though, this should work fine with lazy -- monads. For example, if "any" is implemented using "foldr" instead of -- "foldrM" it performs the same with Identity monad but performs 1000x slower -- with IO monad. -- {-# INLINE_NORMAL foldr #-} foldr :: Monad m => (a -> b -> b) -> b -> Stream m a -> m b foldr f z = foldrM (\a b -> liftA2 f (return a) b) (return z) -- Right fold to some transformer (T) monad. This can be useful to implement -- stateless combinators like map, filtering, insertions, takeWhile, dropWhile. -- {-# INLINE_NORMAL foldrT #-} foldrT :: (Monad m, Monad (t m), MonadTrans t) => (a -> t m b -> t m b) -> t m b -> Stream m a -> t m b foldrT f final (Stream step state) = go SPEC state where go !_ st = do r <- lift $ step defState st case r of Yield x s -> f x (go SPEC s) Skip s -> go SPEC s Stop -> final {-# INLINE toList #-} toList :: Monad m => Stream m a -> m [a] toList = foldr (:) [] -- XXX run begin action only if the stream is not empty. {-# INLINE_NORMAL foldlMx' #-} foldlMx' :: Monad m => (x -> a -> m x) -> m x -> (x -> m b) -> Stream m a -> m b foldlMx' fstep begin done (Stream step state) = begin >>= \x -> go SPEC x state where -- XXX !acc? go !_ acc st = acc `seq` do r <- step defState st case r of Yield x s -> do acc' <- fstep acc x go SPEC acc' s Skip s -> go SPEC acc s Stop -> done acc {-# INLINE foldlx' #-} foldlx' :: Monad m => (x -> a -> x) -> x -> (x -> b) -> Stream m a -> m b foldlx' fstep begin done m = foldlMx' (\b a -> return (fstep b a)) (return begin) (return . done) m -- XXX implement in terms of foldlMx'? {-# INLINE_NORMAL foldlM' #-} foldlM' :: Monad m => (b -> a -> m b) -> b -> Stream m a -> m b foldlM' fstep begin (Stream step state) = go SPEC begin state where go !_ acc st = acc `seq` do r <- step defState st case r of Yield x s -> do acc' <- fstep acc x go SPEC acc' s Skip s -> go SPEC acc s Stop -> return acc {-# INLINE foldl' #-} foldl' :: Monad m => (b -> a -> b) -> b -> Stream m a -> m b foldl' fstep = foldlM' (\b a -> return (fstep b a)) -- | Convert a list of pure values to a 'Stream' {-# INLINE_LATE fromList #-} fromList :: Monad m => [a] -> Stream m a fromList = Stream step where {-# INLINE_LATE step #-} step _ (x:xs) = return $ Yield x xs step _ [] = return Stop ------------------------------------------------------------------------------ -- Comparisons ------------------------------------------------------------------------------ {-# INLINE_NORMAL eqBy #-} eqBy :: Monad m => (a -> b -> Bool) -> Stream m a -> Stream m b -> m Bool eqBy eq (Stream step1 t1) (Stream step2 t2) = eq_loop0 SPEC t1 t2 where eq_loop0 !_ s1 s2 = do r <- step1 defState s1 case r of Yield x s1' -> eq_loop1 SPEC x s1' s2 Skip s1' -> eq_loop0 SPEC s1' s2 Stop -> eq_null s2 eq_loop1 !_ x s1 s2 = do r <- step2 defState s2 case r of Yield y s2' | eq x y -> eq_loop0 SPEC s1 s2' | otherwise -> return False Skip s2' -> eq_loop1 SPEC x s1 s2' Stop -> return False eq_null s2 = do r <- step2 defState s2 case r of Yield _ _ -> return False Skip s2' -> eq_null s2' Stop -> return True -- | Compare two streams lexicographically {-# INLINE_NORMAL cmpBy #-} cmpBy :: Monad m => (a -> b -> Ordering) -> Stream m a -> Stream m b -> m Ordering cmpBy cmp (Stream step1 t1) (Stream step2 t2) = cmp_loop0 SPEC t1 t2 where cmp_loop0 !_ s1 s2 = do r <- step1 defState s1 case r of Yield x s1' -> cmp_loop1 SPEC x s1' s2 Skip s1' -> cmp_loop0 SPEC s1' s2 Stop -> cmp_null s2 cmp_loop1 !_ x s1 s2 = do r <- step2 defState s2 case r of Yield y s2' -> case x `cmp` y of EQ -> cmp_loop0 SPEC s1 s2' c -> return c Skip s2' -> cmp_loop1 SPEC x s1 s2' Stop -> return GT cmp_null s2 = do r <- step2 defState s2 case r of Yield _ _ -> return LT Skip s2' -> cmp_null s2' Stop -> return EQ {-# INLINE_NORMAL take #-} take :: Monad m => Int -> Stream m a -> Stream m a take n (Stream step state) = n `seq` Stream step' (state, 0) where {-# INLINE_LATE step' #-} step' gst (st, i) | i < n = do r <- step gst st return $ case r of Yield x s -> Yield x (s, i + 1) Skip s -> Skip (s, i) Stop -> Stop step' _ (_, _) = return Stop ------------------------------------------------------------------------------ -- Grouping/Splitting ------------------------------------------------------------------------------ {-# INLINE_LATE foldOneGroup #-} foldOneGroup :: Monad m => Int -> Fold m a b -> a -> State K.Stream m a -> (State K.Stream m a -> s -> m (Step s a)) -> s -> m (b, Maybe s) foldOneGroup n (Fold fstep begin done) x gst step state = do acc0 <- begin acc <- fstep acc0 x go SPEC state acc 1 where -- XXX is it strict enough? go !_ st !acc i | i < n = do r <- step gst st case r of Yield y s -> do acc1 <- fstep acc y go SPEC s acc1 (i + 1) Skip s -> go SPEC s acc i Stop -> do res <- done acc return (res, Nothing) go !_ st acc _ = do r <- done acc return (r, Just st) -- groupsOf takes 11 sec to write the file from a stream, whereas just -- reading in the same file as a stream and folding each element of the stream -- using (+) takes just 1.5 sec, so we are still pretty slow (7x slow), there -- is scope to make it faster. There is a possibility of better fusion here. -- {-# INLINE_NORMAL groupsOf #-} groupsOf :: Monad m => Int -> Fold m a b -> Stream m a -> Stream m b groupsOf n f (Stream step state) = n `seq` Stream stepOuter (Just state) where {-# INLINE_LATE stepOuter #-} stepOuter gst (Just st) = do -- We retrieve the first element of the stream before we start to fold -- a chunk so that we do not return an empty chunk in case the stream -- is empty. res <- step (adaptState gst) st case res of Yield x s -> do -- XXX how to make sure that stepInner and f get fused -- This problem seems to be similar to the concatMap problem (r, s1) <- foldOneGroup n f x (adaptState gst) step s return $ Yield r s1 Skip s -> return $ Skip $ Just s Stop -> return Stop stepOuter _ Nothing = return Stop