@@ -112,16 +112,23 @@ module Streamly.Internal.Data.Unfold
113 -- * Exceptions
113 -- * Exceptions
114 , gbracket
114 , gbracket
117 , onException
119 , onException
118 , finally
120 , finally
119 , bracket
122 , bracket
124import Control.Exception (Exception)
128import Control.Exception (Exception)
129+import Control.Monad.IO.Class (MonadIO(..))
130+import Control.Monad.Trans.Control (MonadBaseControl)
131+import Data.IORef (readIORef, writeIORef)
125import Data.Void (Void)
132import Data.Void (Void)
126import GHC.Types (SPEC(..))
133import GHC.Types (SPEC(..))
127import Prelude hiding (concat, map, mapM, takeWhile, take, filter, const)
134import Prelude hiding (concat, map, mapM, takeWhile, take, filter, const)
@@ -132,7 +139,7 @@ import Streamly.Internal.Data.Stream.StreamD.Type (pattern Stream)
133import Streamly.Internal.Data.Unfold.Types (Unfold(..))
140import Streamly.Internal.Data.Unfold.Types (Unfold(..))
134import Streamly.Internal.Data.Fold.Types (Fold(..))
141import Streamly.Internal.Data.Fold.Types (Fold(..))
135−import Streamly.Internal.Data.SVar (defState)
142+import Streamly.Internal.Data.SVar (defState, MonadAsync)
136import Control.Monad.Catch (MonadCatch)
143import Control.Monad.Catch (MonadCatch)
138import qualified Prelude
145import qualified Prelude
@@ -653,6 +660,53 @@ gbracket bef exc aft (Unfold estep einject) (Unfold step1 inject1) =
653 Skip s -> return $ Skip (Left s)
660 Skip s -> return $ Skip (Left s)
654 Stop -> return Stop
661 Stop -> return Stop
663+-- | The most general bracketing and exception combinator. All other
664+-- combinators can be expressed in terms of this combinator. This can also be
665+-- used for cases which are not covered by the standard combinators.
669+{-# INLINE_NORMAL gbracketIO #-}
671+ :: (MonadIO m, MonadBaseControl IO m)
672+ => (a -> m c) -- ^ before
673+ -> (forall s. m s -> m (Either e s)) -- ^ try (exception handling)
674+ -> (c -> m d) -- ^ after, on normal stop, or GC
675+ -> Unfold m (c, e) b -- ^ on exception
676+ -> Unfold m c b -- ^ unfold to run
678+gbracketIO bef exc aft (Unfold estep einject) (Unfold step1 inject1) =
685+ ref <- D.newFinalizedIORef (aft r)
687+ return $ Right (s, r, ref)
689+ {-# INLINE_LATE step #-}
690+ step (Right (st, v, ref)) = do
691+ res <- exc $ step1 st
693+ Right r -> case r of
694+ Yield x s -> return $ Yield x (Right (s, v, ref))
695+ Skip s -> return $ Skip (Right (s, v, ref))
697+ liftIO $ writeIORef ref Nothing
698+ aft v >> return Stop
700+ liftIO (writeIORef ref Nothing)
702+ return $ Skip (Left r)
706+ Yield x s -> return $ Yield x (Left s)
707+ Skip s -> return $ Skip (Left s)
656-- The custom implementation of "before" is slightly faster (5-7%) than
710-- The custom implementation of "before" is slightly faster (5-7%) than
657-- "_before". This is just to document and make sure that we can always use
711-- "_before". This is just to document and make sure that we can always use
658-- gbracket to implement before. The same applies to other combinators as well.
712-- gbracket to implement before. The same applies to other combinators as well.
@@ -690,6 +744,10 @@ _after aft = gbracket return (fmap Right) aft undefined
691-- | Run a side effect whenever the unfold stops normally.
745-- | Run a side effect whenever the unfold stops normally.
747+-- Prefer afterIO over this as the @after@ action in this combinator is not
748+-- executed if the unfold is partially evaluated lazily and then garbage
693-- /Internal/
751-- /Internal/
694{-# INLINE_NORMAL after #-}
752{-# INLINE_NORMAL after #-}
695after :: Monad m => (a -> m c) -> Unfold m a b -> Unfold m a b
753after :: Monad m => (a -> m c) -> Unfold m a b -> Unfold m a b
@@ -709,6 +767,33 @@ after action (Unfold step1 inject1) = Unfold step inject
709 Skip s -> return $ Skip (s, v)
767 Skip s -> return $ Skip (s, v)
710 Stop -> action v >> return Stop
768 Stop -> action v >> return Stop
770+-- | Run a side effect whenever the unfold stops normally
771+-- or is garbage collected after a partial lazy evaluation.
774+{-# INLINE_NORMAL afterIO #-}
775+afterIO :: (MonadIO m, MonadBaseControl IO m)
776+ => (a -> m c) -> Unfold m a b -> Unfold m a b
777+afterIO action (Unfold step1 inject1) = Unfold step inject
783+ ref <- D.newFinalizedIORef (action x)
786+ {-# INLINE_LATE step #-}
790+ Yield x s -> return $ Yield x (s, ref)
791+ Skip s -> return $ Skip (s, ref)
793+ Just f <- readIORef ref
794+ writeIORef ref Nothing
712{-# INLINE_NORMAL _onException #-}
797{-# INLINE_NORMAL _onException #-}
713_onException :: MonadCatch m => (a -> m c) -> Unfold m a b -> Unfold m a b
798_onException :: MonadCatch m => (a -> m c) -> Unfold m a b -> Unfold m a b
714_onException action unf =
799_onException action unf =
@@ -746,6 +831,10 @@ _finally action unf =
746-- | Run a side effect whenever the unfold stops normally or aborts due to an
831-- | Run a side effect whenever the unfold stops normally or aborts due to an
747-- exception.
832-- exception.
834+-- Prefer finallyIO over this as the @after@ action in this combinator is not
835+-- executed if the unfold is partially evaluated lazily and then garbage
749-- /Internal/
838-- /Internal/
750{-# INLINE_NORMAL finally #-}
839{-# INLINE_NORMAL finally #-}
751finally :: MonadCatch m => (a -> m c) -> Unfold m a b -> Unfold m a b
840finally :: MonadCatch m => (a -> m c) -> Unfold m a b -> Unfold m a b
@@ -765,6 +854,32 @@ finally action (Unfold step1 inject1) = Unfold step inject
765 Skip s -> return $ Skip (s, v)
854 Skip s -> return $ Skip (s, v)
766 Stop -> action v >> return Stop
855 Stop -> action v >> return Stop
857+-- | Run a side effect whenever the unfold stops normally, aborts due to an
858+-- exception or if it is garbage collected after a partial lazy evaluation.
861+{-# INLINE_NORMAL finallyIO #-}
862+finallyIO :: (MonadAsync m, MonadCatch m)
863+ => (a -> m c) -> Unfold m a b -> Unfold m a b
864+finallyIO action (Unfold step1 inject1) = Unfold step inject
870+ ref <- D.newFinalizedIORef (action x)
873+ {-# INLINE_LATE step #-}
874+ step (st, v, ref) = do
875+ res <- step1 st `MC.onException` action v
877+ Yield x s -> return $ Yield x (s, v, ref)
878+ Skip s -> return $ Skip (s, v, ref)
880+ liftIO (writeIORef ref Nothing)
881+ action v >> return Stop
768{-# INLINE_NORMAL _bracket #-}
883{-# INLINE_NORMAL _bracket #-}
769_bracket :: MonadCatch m
884_bracket :: MonadCatch m
770 => (a -> m c) -> (c -> m d) -> Unfold m c b -> Unfold m a b
885 => (a -> m c) -> (c -> m d) -> Unfold m c b -> Unfold m a b
@@ -777,6 +892,10 @@ _bracket bef aft unf =
777-- if an exception occurs then the @after@ action is run with the output of
892-- if an exception occurs then the @after@ action is run with the output of
778-- @before@ as argument.
893-- @before@ as argument.
895+-- Prefer bracketIO over this as the @after@ action in this combinator is not
896+-- executed if the unfold is partially evaluated lazily and then garbage
780-- /Internal/
899-- /Internal/
781{-# INLINE_NORMAL bracket #-}
900{-# INLINE_NORMAL bracket #-}
782bracket :: MonadCatch m
901bracket :: MonadCatch m
@@ -798,6 +917,36 @@ bracket bef aft (Unfold step1 inject1) = Unfold step inject
798 Skip s -> return $ Skip (s, v)
917 Skip s -> return $ Skip (s, v)
799 Stop -> aft v >> return Stop
918 Stop -> aft v >> return Stop
920+-- | @bracket before after between@ runs the @before@ action and then unfolds
921+-- its output using the @between@ unfold. When the @between@ unfold is done or
922+-- if an exception occurs then the @after@ action is run with the output of
923+-- @before@ as argument. The after action is also executed if the unfold is
924+-- paritally evaluated and then garbage collected.
927+{-# INLINE_NORMAL bracketIO #-}
928+bracketIO :: (MonadAsync m, MonadCatch m)
929+ => (a -> m c) -> (c -> m d) -> Unfold m c b -> Unfold m a b
930+bracketIO bef aft (Unfold step1 inject1) = Unfold step inject
937+ ref <- D.newFinalizedIORef (aft r)
940+ {-# INLINE_LATE step #-}
941+ step (st, v, ref) = do
942+ res <- step1 st `MC.onException` aft v
944+ Yield x s -> return $ Yield x (s, v, ref)
945+ Skip s -> return $ Skip (s, v, ref)
947+ liftIO (writeIORef ref Nothing)
948+ aft v >> return Stop
801-- | When unfolding if an exception occurs, unfold the exception using the
950-- | When unfolding if an exception occurs, unfold the exception using the
802-- exception unfold supplied as the first argument to 'handle'.
951-- exception unfold supplied as the first argument to 'handle'.