From 41cf8aae74cd516160406724ab21f0465d97020d Mon Sep 17 00:00:00 2001 From: Kirill Elagin Date: Wed, 14 Oct 2020 19:57:44 -0400 Subject: [PATCH 1/9] Pipe: Implement uncons and unconsE --- conduit/src/Data/Conduit/Internal/Pipe.hs | 28 +++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/conduit/src/Data/Conduit/Internal/Pipe.hs b/conduit/src/Data/Conduit/Internal/Pipe.hs index 90a518ad4..3bbfe7f26 100644 --- a/conduit/src/Data/Conduit/Internal/Pipe.hs +++ b/conduit/src/Data/Conduit/Internal/Pipe.hs @@ -18,6 +18,8 @@ module Data.Conduit.Internal.Pipe , yield , yieldM , leftover + , uncons + , unconsE -- ** Finalization , bracketP -- ** Composition @@ -271,6 +273,32 @@ leftover = Leftover (Done ()) {-# INLINE [1] leftover #-} {-# RULES "conduit: leftover l >> p" forall l (p :: Pipe l i o u m r). leftover l >> p = Leftover p l #-} +-- | Split a pipe into head and tail. +-- +-- Since 1.3.3 +uncons :: forall m o pipe. (Monad m, pipe ~ Pipe Void () o () m ()) + => pipe -> m (Maybe (o, pipe)) +uncons = go + where + go (HaveOutput p o) = pure $ Just (o, p) + go (NeedInput _ c) = go $ c () + go (Done ()) = pure Nothing + go (PipeM mp) = mp >>= go + go (Leftover _ i) = absurd i + +-- | Split a pipe into head and tail or return its result if it is done. +-- +-- Since 1.3.3 +unconsE :: forall m o r pipe. (Monad m, pipe ~ Pipe Void () o () m r) + => pipe -> m (Either r (o, pipe)) +unconsE = go + where + go (HaveOutput p o) = pure $ Right (o, p) + go (NeedInput _ c) = go $ c () + go (Done r) = pure $ Left r + go (PipeM mp) = mp >>= go + go (Leftover _ i) = absurd i + -- | Bracket a pipe computation between allocation and release of a resource. -- We guarantee, via the @MonadResource@ context, that the resource -- finalization is exception safe. However, it will not necessarily be From 108464ec8089ab28a07c1966ebe15fbd1e3177f7 Mon Sep 17 00:00:00 2001 From: Kirill Elagin Date: Wed, 14 Oct 2020 22:09:32 -0400 Subject: [PATCH 2/9] ConduitT: Implement uncons and unconsE --- conduit/src/Data/Conduit/Internal.hs | 3 +- conduit/src/Data/Conduit/Internal/Conduit.hs | 37 +++++++++++++++++++- conduit/src/Data/Conduit/List.hs | 3 ++ conduit/test/main.hs | 25 +++++++++++++ 4 files changed, 66 insertions(+), 2 deletions(-) diff --git a/conduit/src/Data/Conduit/Internal.hs b/conduit/src/Data/Conduit/Internal.hs index 118e2763d..0e58a5b9c 100644 --- a/conduit/src/Data/Conduit/Internal.hs +++ b/conduit/src/Data/Conduit/Internal.hs @@ -14,6 +14,7 @@ import Data.Conduit.Internal.Conduit hiding (await, leftover, mapInput, mapInputM, mapOutput, mapOutputMaybe, transPipe, - yield, yieldM) + yield, yieldM, + uncons, unconsE) import Data.Conduit.Internal.Pipe import Data.Conduit.Internal.Fusion diff --git a/conduit/src/Data/Conduit/Internal/Conduit.hs b/conduit/src/Data/Conduit/Internal/Conduit.hs index 00aa8058c..0df637fc5 100644 --- a/conduit/src/Data/Conduit/Internal/Conduit.hs +++ b/conduit/src/Data/Conduit/Internal/Conduit.hs @@ -38,6 +38,8 @@ module Data.Conduit.Internal.Conduit , runConduitRes , fuse , connect + , uncons + , unconsE -- ** Composition , connectResume , connectResumeConduit @@ -101,12 +103,13 @@ import Control.Monad.State.Class(MonadState(..)) import Control.Monad.Trans.Class (MonadTrans (lift)) import Control.Monad.IO.Unlift (MonadIO (liftIO), MonadUnliftIO, withRunInIO) import Control.Monad.Primitive (PrimMonad, PrimState, primitive) +import Data.Bifunctor (second) import Data.Functor.Identity (Identity, runIdentity) import Data.Void (Void, absurd) import Data.Monoid (Monoid (mappend, mempty)) import Data.Semigroup (Semigroup ((<>))) import Control.Monad.Trans.Resource -import Data.Conduit.Internal.Pipe hiding (yield, mapOutput, leftover, yieldM, await, awaitForever, bracketP) +import Data.Conduit.Internal.Pipe hiding (yield, mapOutput, leftover, yieldM, await, awaitForever, bracketP, uncons, unconsE) import qualified Data.Conduit.Internal.Pipe as CI import Control.Monad (forever) import Data.Traversable (Traversable (..)) @@ -720,6 +723,38 @@ connect :: Monad m -> m r connect = ($$) +-- | Split a conduit into head and tail. +-- +-- Note that you have to 'sealConduitT' it first. +-- +-- Since 1.3.3 +uncons :: forall m o conduit. (Monad m, conduit ~ SealedConduitT () o m ()) + => conduit -> m (Maybe (o, conduit)) +uncons (SealedConduitT p) = fmap (fmap (second SealedConduitT)) $ go p + where + -- This function is the same as @Pipe.uncons@ but it ignores leftovers. + go (HaveOutput p o) = pure $ Just (o, p) + go (NeedInput _ c) = go $ c () + go (Done ()) = pure Nothing + go (PipeM mp) = mp >>= go + go (Leftover p ()) = go p + +-- | Split a coundit into head and tail or return its result if it is done. +-- +-- Note that you have to 'sealConduitT' it first. +-- +-- Since 1.3.3 +unconsE :: forall m o r conduit. (Monad m, conduit ~ SealedConduitT () o m r) + => conduit -> m (Either r (o, conduit)) +unconsE (SealedConduitT p) = fmap (fmap (second SealedConduitT)) $ go p + where + -- This function is the same as @Pipe.unconsE@ but it ignores leftovers. + go (HaveOutput p o) = pure $ Right (o, p) + go (NeedInput _ c) = go $ c () + go (Done r) = pure $ Left r + go (PipeM mp) = mp >>= go + go (Leftover p ()) = go p + -- | Named function synonym for '.|' -- -- Equivalent to '.|' and '=$='. However, the latter is diff --git a/conduit/src/Data/Conduit/List.hs b/conduit/src/Data/Conduit/List.hs index 3dcedcbb7..858e50677 100644 --- a/conduit/src/Data/Conduit/List.hs +++ b/conduit/src/Data/Conduit/List.hs @@ -43,6 +43,8 @@ module Data.Conduit.List , foldMapM , foldM , mapM_ + , uncons + , unconsE -- * Conduits -- ** Pure , map @@ -95,6 +97,7 @@ import Prelude import Data.Monoid (Monoid, mempty, mappend) import qualified Data.Foldable as F import Data.Conduit +import Data.Conduit.Internal.Conduit (uncons, unconsE) import Data.Conduit.Internal.Fusion import Data.Conduit.Internal.List.Stream import qualified Data.Conduit.Internal as CI diff --git a/conduit/test/main.hs b/conduit/test/main.hs index a9b89f17c..d35cec6c9 100644 --- a/conduit/test/main.hs +++ b/conduit/test/main.hs @@ -166,6 +166,31 @@ main = hspec $ do let y = DL.unfoldr f seed x `shouldBe` y + describe "uncons" $ do + prop "folds to list" $ \xs -> + let src = C.sealConduitT $ CL.sourceList xs in + (xs :: [Int]) == DL.unfoldr (runIdentity . CL.uncons) src + + describe "unconsE" $ do + let + eitherToMaybe :: Either l a -> Maybe a + eitherToMaybe (Left _) = Nothing + eitherToMaybe (Right a) = Just a + prop "folds outputs" $ \xs -> + let c = CL.sourceList xs .| CL.mapAccum (\a s -> (s + a, a)) 0 in + let sealed = C.sealConduitT c in + (xs :: [Int]) == DL.unfoldr (eitherToMaybe . runIdentity . CL.unconsE) sealed + + let + waitForLeft :: (b -> Either l (a, b)) -> b -> l + waitForLeft f x = case f x of + Left l -> l + Right (_a, b) -> waitForLeft f b + prop "returns result" $ \xs -> + let c = CL.sourceList xs .| CL.mapAccum (\a s -> (s + a, a)) 0 in + let sealed = C.sealConduitT c in + sum (xs :: [Int]) == waitForLeft (runIdentity . CL.unconsE) sealed + describe "Monoid instance for Source" $ do it "mappend" $ do x <- runConduitRes $ (CL.sourceList [1..5 :: Int] `mappend` CL.sourceList [6..10]) .| CL.fold (+) 0 From b57e816e8a9d4040882331d17b6ecf9781eb7ebf Mon Sep 17 00:00:00 2001 From: Kirill Elagin Date: Thu, 15 Oct 2020 09:54:40 -0400 Subject: [PATCH 3/9] uncons: Simplify type and implementation --- conduit/src/Data/Conduit/Internal/Conduit.hs | 18 ++++++++++-------- conduit/src/Data/Conduit/Internal/Pipe.hs | 10 ++++++---- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/conduit/src/Data/Conduit/Internal/Conduit.hs b/conduit/src/Data/Conduit/Internal/Conduit.hs index 0df637fc5..2b3451c13 100644 --- a/conduit/src/Data/Conduit/Internal/Conduit.hs +++ b/conduit/src/Data/Conduit/Internal/Conduit.hs @@ -728,12 +728,13 @@ connect = ($$) -- Note that you have to 'sealConduitT' it first. -- -- Since 1.3.3 -uncons :: forall m o conduit. (Monad m, conduit ~ SealedConduitT () o m ()) - => conduit -> m (Maybe (o, conduit)) -uncons (SealedConduitT p) = fmap (fmap (second SealedConduitT)) $ go p +uncons :: Monad m + => SealedConduitT () o m () + -> m (Maybe (o, SealedConduitT () o m ())) +uncons (SealedConduitT p) = go p where -- This function is the same as @Pipe.uncons@ but it ignores leftovers. - go (HaveOutput p o) = pure $ Just (o, p) + go (HaveOutput p o) = pure $ Just (o, SealedConduitT p) go (NeedInput _ c) = go $ c () go (Done ()) = pure Nothing go (PipeM mp) = mp >>= go @@ -744,12 +745,13 @@ uncons (SealedConduitT p) = fmap (fmap (second SealedConduitT)) $ go p -- Note that you have to 'sealConduitT' it first. -- -- Since 1.3.3 -unconsE :: forall m o r conduit. (Monad m, conduit ~ SealedConduitT () o m r) - => conduit -> m (Either r (o, conduit)) -unconsE (SealedConduitT p) = fmap (fmap (second SealedConduitT)) $ go p +unconsE :: Monad m + => SealedConduitT () o m r + -> m (Either r (o, SealedConduitT () o m r)) +unconsE (SealedConduitT p) = go p where -- This function is the same as @Pipe.unconsE@ but it ignores leftovers. - go (HaveOutput p o) = pure $ Right (o, p) + go (HaveOutput p o) = pure $ Right (o, SealedConduitT p) go (NeedInput _ c) = go $ c () go (Done r) = pure $ Left r go (PipeM mp) = mp >>= go diff --git a/conduit/src/Data/Conduit/Internal/Pipe.hs b/conduit/src/Data/Conduit/Internal/Pipe.hs index 3bbfe7f26..03841f241 100644 --- a/conduit/src/Data/Conduit/Internal/Pipe.hs +++ b/conduit/src/Data/Conduit/Internal/Pipe.hs @@ -276,8 +276,9 @@ leftover = Leftover (Done ()) -- | Split a pipe into head and tail. -- -- Since 1.3.3 -uncons :: forall m o pipe. (Monad m, pipe ~ Pipe Void () o () m ()) - => pipe -> m (Maybe (o, pipe)) +uncons :: Monad m + => Pipe Void () o () m () + -> m (Maybe (o, Pipe Void () o () m ())) uncons = go where go (HaveOutput p o) = pure $ Just (o, p) @@ -289,8 +290,9 @@ uncons = go -- | Split a pipe into head and tail or return its result if it is done. -- -- Since 1.3.3 -unconsE :: forall m o r pipe. (Monad m, pipe ~ Pipe Void () o () m r) - => pipe -> m (Either r (o, pipe)) +unconsE :: Monad m + => Pipe Void () o () m r + -> m (Either r (o, Pipe Void () o () m r)) unconsE = go where go (HaveOutput p o) = pure $ Right (o, p) From 7de4792a194e68c151777f587c2175c0d9346c0b Mon Sep 17 00:00:00 2001 From: Kirill Elagin Date: Thu, 15 Oct 2020 10:00:16 -0400 Subject: [PATCH 4/9] uncons: Follow the library naming scheme closer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Rename uncons -> unconsM, since it’s monadic * Rename unconsE -> unconsEitherM to match with unfoldEitherM --- conduit/src/Data/Conduit/Internal.hs | 2 +- conduit/src/Data/Conduit/Internal/Conduit.hs | 26 ++++++++++---------- conduit/src/Data/Conduit/Internal/Pipe.hs | 20 +++++++-------- conduit/src/Data/Conduit/List.hs | 6 ++--- conduit/test/main.hs | 10 ++++---- 5 files changed, 32 insertions(+), 32 deletions(-) diff --git a/conduit/src/Data/Conduit/Internal.hs b/conduit/src/Data/Conduit/Internal.hs index 0e58a5b9c..7e93e4015 100644 --- a/conduit/src/Data/Conduit/Internal.hs +++ b/conduit/src/Data/Conduit/Internal.hs @@ -15,6 +15,6 @@ import Data.Conduit.Internal.Conduit hiding (await, mapOutput, mapOutputMaybe, transPipe, yield, yieldM, - uncons, unconsE) + unconsM, unconsEitherM) import Data.Conduit.Internal.Pipe import Data.Conduit.Internal.Fusion diff --git a/conduit/src/Data/Conduit/Internal/Conduit.hs b/conduit/src/Data/Conduit/Internal/Conduit.hs index 2b3451c13..511b0675b 100644 --- a/conduit/src/Data/Conduit/Internal/Conduit.hs +++ b/conduit/src/Data/Conduit/Internal/Conduit.hs @@ -38,8 +38,8 @@ module Data.Conduit.Internal.Conduit , runConduitRes , fuse , connect - , uncons - , unconsE + , unconsM + , unconsEitherM -- ** Composition , connectResume , connectResumeConduit @@ -109,7 +109,7 @@ import Data.Void (Void, absurd) import Data.Monoid (Monoid (mappend, mempty)) import Data.Semigroup (Semigroup ((<>))) import Control.Monad.Trans.Resource -import Data.Conduit.Internal.Pipe hiding (yield, mapOutput, leftover, yieldM, await, awaitForever, bracketP, uncons, unconsE) +import Data.Conduit.Internal.Pipe hiding (yield, mapOutput, leftover, yieldM, await, awaitForever, bracketP, unconsM, unconsEitherM) import qualified Data.Conduit.Internal.Pipe as CI import Control.Monad (forever) import Data.Traversable (Traversable (..)) @@ -728,12 +728,12 @@ connect = ($$) -- Note that you have to 'sealConduitT' it first. -- -- Since 1.3.3 -uncons :: Monad m - => SealedConduitT () o m () - -> m (Maybe (o, SealedConduitT () o m ())) -uncons (SealedConduitT p) = go p +unconsM :: Monad m + => SealedConduitT () o m () + -> m (Maybe (o, SealedConduitT () o m ())) +unconsM (SealedConduitT p) = go p where - -- This function is the same as @Pipe.uncons@ but it ignores leftovers. + -- This function is the same as @Pipe.unconsM@ but it ignores leftovers. go (HaveOutput p o) = pure $ Just (o, SealedConduitT p) go (NeedInput _ c) = go $ c () go (Done ()) = pure Nothing @@ -745,12 +745,12 @@ uncons (SealedConduitT p) = go p -- Note that you have to 'sealConduitT' it first. -- -- Since 1.3.3 -unconsE :: Monad m - => SealedConduitT () o m r - -> m (Either r (o, SealedConduitT () o m r)) -unconsE (SealedConduitT p) = go p +unconsEitherM :: Monad m + => SealedConduitT () o m r + -> m (Either r (o, SealedConduitT () o m r)) +unconsEitherM (SealedConduitT p) = go p where - -- This function is the same as @Pipe.unconsE@ but it ignores leftovers. + -- This function is the same as @Pipe.unconsEitherM@ but it ignores leftovers. go (HaveOutput p o) = pure $ Right (o, SealedConduitT p) go (NeedInput _ c) = go $ c () go (Done r) = pure $ Left r diff --git a/conduit/src/Data/Conduit/Internal/Pipe.hs b/conduit/src/Data/Conduit/Internal/Pipe.hs index 03841f241..3c22986be 100644 --- a/conduit/src/Data/Conduit/Internal/Pipe.hs +++ b/conduit/src/Data/Conduit/Internal/Pipe.hs @@ -18,8 +18,8 @@ module Data.Conduit.Internal.Pipe , yield , yieldM , leftover - , uncons - , unconsE + , unconsM + , unconsEitherM -- ** Finalization , bracketP -- ** Composition @@ -276,10 +276,10 @@ leftover = Leftover (Done ()) -- | Split a pipe into head and tail. -- -- Since 1.3.3 -uncons :: Monad m - => Pipe Void () o () m () - -> m (Maybe (o, Pipe Void () o () m ())) -uncons = go +unconsM :: Monad m + => Pipe Void () o () m () + -> m (Maybe (o, Pipe Void () o () m ())) +unconsM = go where go (HaveOutput p o) = pure $ Just (o, p) go (NeedInput _ c) = go $ c () @@ -290,10 +290,10 @@ uncons = go -- | Split a pipe into head and tail or return its result if it is done. -- -- Since 1.3.3 -unconsE :: Monad m - => Pipe Void () o () m r - -> m (Either r (o, Pipe Void () o () m r)) -unconsE = go +unconsEitherM :: Monad m + => Pipe Void () o () m r + -> m (Either r (o, Pipe Void () o () m r)) +unconsEitherM = go where go (HaveOutput p o) = pure $ Right (o, p) go (NeedInput _ c) = go $ c () diff --git a/conduit/src/Data/Conduit/List.hs b/conduit/src/Data/Conduit/List.hs index 858e50677..7505a2722 100644 --- a/conduit/src/Data/Conduit/List.hs +++ b/conduit/src/Data/Conduit/List.hs @@ -43,8 +43,8 @@ module Data.Conduit.List , foldMapM , foldM , mapM_ - , uncons - , unconsE + , unconsM + , unconsEitherM -- * Conduits -- ** Pure , map @@ -97,7 +97,7 @@ import Prelude import Data.Monoid (Monoid, mempty, mappend) import qualified Data.Foldable as F import Data.Conduit -import Data.Conduit.Internal.Conduit (uncons, unconsE) +import Data.Conduit.Internal.Conduit (unconsM, unconsEitherM) import Data.Conduit.Internal.Fusion import Data.Conduit.Internal.List.Stream import qualified Data.Conduit.Internal as CI diff --git a/conduit/test/main.hs b/conduit/test/main.hs index d35cec6c9..850231f2e 100644 --- a/conduit/test/main.hs +++ b/conduit/test/main.hs @@ -166,12 +166,12 @@ main = hspec $ do let y = DL.unfoldr f seed x `shouldBe` y - describe "uncons" $ do + describe "unconsM" $ do prop "folds to list" $ \xs -> let src = C.sealConduitT $ CL.sourceList xs in - (xs :: [Int]) == DL.unfoldr (runIdentity . CL.uncons) src + (xs :: [Int]) == DL.unfoldr (runIdentity . CL.unconsM) src - describe "unconsE" $ do + describe "unconsEitherM" $ do let eitherToMaybe :: Either l a -> Maybe a eitherToMaybe (Left _) = Nothing @@ -179,7 +179,7 @@ main = hspec $ do prop "folds outputs" $ \xs -> let c = CL.sourceList xs .| CL.mapAccum (\a s -> (s + a, a)) 0 in let sealed = C.sealConduitT c in - (xs :: [Int]) == DL.unfoldr (eitherToMaybe . runIdentity . CL.unconsE) sealed + (xs :: [Int]) == DL.unfoldr (eitherToMaybe . runIdentity . CL.unconsEitherM) sealed let waitForLeft :: (b -> Either l (a, b)) -> b -> l @@ -189,7 +189,7 @@ main = hspec $ do prop "returns result" $ \xs -> let c = CL.sourceList xs .| CL.mapAccum (\a s -> (s + a, a)) 0 in let sealed = C.sealConduitT c in - sum (xs :: [Int]) == waitForLeft (runIdentity . CL.unconsE) sealed + sum (xs :: [Int]) == waitForLeft (runIdentity . CL.unconsEitherM) sealed describe "Monoid instance for Source" $ do it "mappend" $ do From 0e81946f4538ebfb1af8e45b3772d2a28cee713e Mon Sep 17 00:00:00 2001 From: Kirill Elagin Date: Thu, 15 Oct 2020 10:27:11 -0400 Subject: [PATCH 5/9] unconsM: Add pure versions --- conduit/src/Data/Conduit/List.hs | 25 ++++++++++++++++++++++++- conduit/test/main.hs | 10 +++++----- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/conduit/src/Data/Conduit/List.hs b/conduit/src/Data/Conduit/List.hs index 7505a2722..2a4e294ae 100644 --- a/conduit/src/Data/Conduit/List.hs +++ b/conduit/src/Data/Conduit/List.hs @@ -33,6 +33,8 @@ module Data.Conduit.List -- ** Pure , fold , foldMap + , uncons + , unconsEither , take , drop , head @@ -42,9 +44,9 @@ module Data.Conduit.List -- ** Monadic , foldMapM , foldM - , mapM_ , unconsM , unconsEitherM + , mapM_ -- * Conduits -- ** Pure , map @@ -101,6 +103,7 @@ import Data.Conduit.Internal.Conduit (unconsM, unconsEitherM) import Data.Conduit.Internal.Fusion import Data.Conduit.Internal.List.Stream import qualified Data.Conduit.Internal as CI +import Data.Functor.Identity (Identity (runIdentity)) import Control.Monad (when, (<=<), liftM, void) import Control.Monad.Trans.Class (lift) @@ -183,6 +186,26 @@ unfoldEitherMC f = Left r -> return r STREAMING(unfoldEitherM, unfoldEitherMC, unfoldEitherMS, f seed) +-- | Split a pure conduit into head and tail. +-- This is equivalent to @runIdentity . unconsM@. +-- +-- Note that you have to 'sealConduitT' it first. +-- +-- Since 1.3.3 +uncons :: SealedConduitT () o Identity () + -> Maybe (o, SealedConduitT () o Identity ()) +uncons = runIdentity . unconsM + +-- | Split a pure coundit into head and tail or return its result if it is done. +-- This is equivalent to @runIdentity . unconsEitherM@. +-- +-- Note that you have to 'sealConduitT' it first. +-- +-- Since 1.3.3 +unconsEither :: SealedConduitT () o Identity r + -> Either r (o, SealedConduitT () o Identity r) +unconsEither = runIdentity . unconsEitherM + -- | Yield the values from the list. -- -- Subject to fusion diff --git a/conduit/test/main.hs b/conduit/test/main.hs index 850231f2e..f465b6175 100644 --- a/conduit/test/main.hs +++ b/conduit/test/main.hs @@ -166,12 +166,12 @@ main = hspec $ do let y = DL.unfoldr f seed x `shouldBe` y - describe "unconsM" $ do + describe "uncons" $ do prop "folds to list" $ \xs -> let src = C.sealConduitT $ CL.sourceList xs in - (xs :: [Int]) == DL.unfoldr (runIdentity . CL.unconsM) src + (xs :: [Int]) == DL.unfoldr CL.uncons src - describe "unconsEitherM" $ do + describe "unconsEither" $ do let eitherToMaybe :: Either l a -> Maybe a eitherToMaybe (Left _) = Nothing @@ -179,7 +179,7 @@ main = hspec $ do prop "folds outputs" $ \xs -> let c = CL.sourceList xs .| CL.mapAccum (\a s -> (s + a, a)) 0 in let sealed = C.sealConduitT c in - (xs :: [Int]) == DL.unfoldr (eitherToMaybe . runIdentity . CL.unconsEitherM) sealed + (xs :: [Int]) == DL.unfoldr (eitherToMaybe . CL.unconsEither) sealed let waitForLeft :: (b -> Either l (a, b)) -> b -> l @@ -189,7 +189,7 @@ main = hspec $ do prop "returns result" $ \xs -> let c = CL.sourceList xs .| CL.mapAccum (\a s -> (s + a, a)) 0 in let sealed = C.sealConduitT c in - sum (xs :: [Int]) == waitForLeft (runIdentity . CL.unconsEitherM) sealed + sum (xs :: [Int]) == waitForLeft CL.unconsEither sealed describe "Monoid instance for Source" $ do it "mappend" $ do From 69d2ef1b327e0d2b115cf971b88079b373f788a8 Mon Sep 17 00:00:00 2001 From: Kirill Elagin Date: Thu, 15 Oct 2020 10:27:49 -0400 Subject: [PATCH 6/9] Update ChangeLog --- conduit/ChangeLog.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/conduit/ChangeLog.md b/conduit/ChangeLog.md index 6180a93f2..ed15cfc01 100644 --- a/conduit/ChangeLog.md +++ b/conduit/ChangeLog.md @@ -1,5 +1,9 @@ # ChangeLog for conduit +## 1.3.3 + +* Add `uncons`, `unconsM`, `unconsEither`, `unconsEitherM`. + ## 1.3.2.1 * Fix isChunksForExactlyE [#445](https://github.com/snoyberg/conduit/issues/445) [#446](https://github.com/snoyberg/conduit/pull/446) From a1d3b3b78163920c86432fae96b16f6e747662dc Mon Sep 17 00:00:00 2001 From: Kirill Elagin Date: Thu, 15 Oct 2020 10:29:20 -0400 Subject: [PATCH 7/9] Fix a typo in Haddock --- conduit/src/Data/Conduit/Internal/Conduit.hs | 2 +- conduit/src/Data/Conduit/List.hs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/conduit/src/Data/Conduit/Internal/Conduit.hs b/conduit/src/Data/Conduit/Internal/Conduit.hs index 511b0675b..935c0ea55 100644 --- a/conduit/src/Data/Conduit/Internal/Conduit.hs +++ b/conduit/src/Data/Conduit/Internal/Conduit.hs @@ -740,7 +740,7 @@ unconsM (SealedConduitT p) = go p go (PipeM mp) = mp >>= go go (Leftover p ()) = go p --- | Split a coundit into head and tail or return its result if it is done. +-- | Split a conduit into head and tail or return its result if it is done. -- -- Note that you have to 'sealConduitT' it first. -- diff --git a/conduit/src/Data/Conduit/List.hs b/conduit/src/Data/Conduit/List.hs index 2a4e294ae..c509960c2 100644 --- a/conduit/src/Data/Conduit/List.hs +++ b/conduit/src/Data/Conduit/List.hs @@ -196,7 +196,7 @@ uncons :: SealedConduitT () o Identity () -> Maybe (o, SealedConduitT () o Identity ()) uncons = runIdentity . unconsM --- | Split a pure coundit into head and tail or return its result if it is done. +-- | Split a pure conduit into head and tail or return its result if it is done. -- This is equivalent to @runIdentity . unconsEitherM@. -- -- Note that you have to 'sealConduitT' it first. From e02407c4d894aaf2c4c03cc9aca699beb34caaa8 Mon Sep 17 00:00:00 2001 From: Kirill Elagin Date: Thu, 15 Oct 2020 13:24:37 -0400 Subject: [PATCH 8/9] Remove unused import --- conduit/src/Data/Conduit/Internal/Conduit.hs | 1 - 1 file changed, 1 deletion(-) diff --git a/conduit/src/Data/Conduit/Internal/Conduit.hs b/conduit/src/Data/Conduit/Internal/Conduit.hs index 935c0ea55..242d9327a 100644 --- a/conduit/src/Data/Conduit/Internal/Conduit.hs +++ b/conduit/src/Data/Conduit/Internal/Conduit.hs @@ -103,7 +103,6 @@ import Control.Monad.State.Class(MonadState(..)) import Control.Monad.Trans.Class (MonadTrans (lift)) import Control.Monad.IO.Unlift (MonadIO (liftIO), MonadUnliftIO, withRunInIO) import Control.Monad.Primitive (PrimMonad, PrimState, primitive) -import Data.Bifunctor (second) import Data.Functor.Identity (Identity, runIdentity) import Data.Void (Void, absurd) import Data.Monoid (Monoid (mappend, mempty)) From 66110e2a8c41ea86c7eadaebebbf92a22bda0dc5 Mon Sep 17 00:00:00 2001 From: Kirill Elagin Date: Thu, 15 Oct 2020 13:26:14 -0400 Subject: [PATCH 9/9] uncons*: Add tests against unfold* --- conduit/test/main.hs | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/conduit/test/main.hs b/conduit/test/main.hs index f465b6175..74df5c36e 100644 --- a/conduit/test/main.hs +++ b/conduit/test/main.hs @@ -24,6 +24,7 @@ import qualified Data.List.Split as DLS (chunksOf) import Control.Monad.ST (runST) import Data.Monoid import qualified Data.IORef as I +import Data.Tuple (swap) import Control.Monad.Trans.Resource (allocate, resourceForkIO) import Control.Concurrent (threadDelay, killThread) import Control.Monad.IO.Class (liftIO) @@ -52,6 +53,16 @@ equivToList :: Eq b => ([a] -> [b]) -> ConduitT a b Identity () -> [a] -> Bool equivToList f conduit xs = f xs == runConduitPure (CL.sourceList xs .| conduit .| CL.consume) +-- | Check that two conduits produce the same outputs and return the same result. +bisimilarTo :: (Eq a, Eq r) => ConduitT () a Identity r -> ConduitT () a Identity r -> Bool +left `bisimilarTo` right = + C.runConduitPure (toListRes left) == C.runConduitPure (toListRes right) + where + -- | Sink a conduit into a list and return it alongside the result. + -- So it is, essentially, @sinkList@ plus result. + toListRes :: Monad m => ConduitT () a m r -> ConduitT () Void m ([a], r) + toListRes cond = swap <$> C.fuseBoth cond CL.consume + main :: IO () main = hspec $ do @@ -171,25 +182,22 @@ main = hspec $ do let src = C.sealConduitT $ CL.sourceList xs in (xs :: [Int]) == DL.unfoldr CL.uncons src + prop "works with unfold" $ \xs -> + let src = CL.sourceList xs in + CL.unfold CL.uncons (C.sealConduitT src) `bisimilarTo` (src :: ConduitT () Int Identity ()) + describe "unconsEither" $ do let eitherToMaybe :: Either l a -> Maybe a eitherToMaybe (Left _) = Nothing eitherToMaybe (Right a) = Just a - prop "folds outputs" $ \xs -> - let c = CL.sourceList xs .| CL.mapAccum (\a s -> (s + a, a)) 0 in - let sealed = C.sealConduitT c in - (xs :: [Int]) == DL.unfoldr (eitherToMaybe . CL.unconsEither) sealed + prop "folds outputs to list" $ \xs -> + let src = C.sealConduitT $ CL.sourceList xs in + (xs :: [Int]) == DL.unfoldr (eitherToMaybe . CL.unconsEither) src - let - waitForLeft :: (b -> Either l (a, b)) -> b -> l - waitForLeft f x = case f x of - Left l -> l - Right (_a, b) -> waitForLeft f b - prop "returns result" $ \xs -> - let c = CL.sourceList xs .| CL.mapAccum (\a s -> (s + a, a)) 0 in - let sealed = C.sealConduitT c in - sum (xs :: [Int]) == waitForLeft CL.unconsEither sealed + prop "works with unfoldEither" $ \(xs, r) -> + let src = CL.sourceList xs *> pure r in + CL.unfoldEither CL.unconsEither (C.sealConduitT src) `bisimilarTo` (src :: ConduitT () Int Identity Int) describe "Monoid instance for Source" $ do it "mappend" $ do