packages feed

pipes-fluid 0.3.0.0 → 0.3.1.0

raw patch · 7 files changed

+295/−154 lines, 7 filesdep +thesedep ~pipes-miscPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: these

Dependency ranges changed: pipes-misc

API changes (from Hackage documentation)

- Pipes.Fluid.Alternative: bothOrEither :: Alternative f => f a -> f b -> f (Either (a, b) (Either a b))
- Pipes.Fluid.React: merge :: (Alternative m, Monad m) => React m x -> React m y -> React m (Either (x, y) (Either (x, Maybe y) (Maybe x, y)))
- Pipes.Fluid.React: merge' :: (Alternative m, Monad m) => Maybe x -> Maybe y -> React m x -> React m y -> React m (Either (x, y) (Either (x, Maybe y) (Maybe x, y)))
- Pipes.Fluid.ReactIO: mergeIO :: (MonadBaseControl IO m, Forall (Pure m)) => ReactIO m x -> ReactIO m y -> ReactIO m (Either (x, y) (Either (x, Maybe y) (Maybe x, y)))
- Pipes.Fluid.ReactIO: mergeIO' :: (MonadBaseControl IO m, Forall (Pure m)) => Maybe x -> Maybe y -> ReactIO m x -> ReactIO m y -> ReactIO m (Either (x, y) (Either (x, Maybe y) (Maybe x, y)))
+ Pipes.Fluid.Merge: Coupled :: Source -> a -> b -> Merged a b
+ Pipes.Fluid.Merge: FromBoth :: Source
+ Pipes.Fluid.Merge: FromLeft :: OtherStatus -> Source
+ Pipes.Fluid.Merge: FromRight :: OtherStatus -> Source
+ Pipes.Fluid.Merge: LeftOnly :: OtherStatus -> a -> Merged a b
+ Pipes.Fluid.Merge: OtherDead :: OtherStatus
+ Pipes.Fluid.Merge: OtherLive :: OtherStatus
+ Pipes.Fluid.Merge: RightOnly :: OtherStatus -> b -> Merged a b
+ Pipes.Fluid.Merge: class Merge f
+ Pipes.Fluid.Merge: data Merged a b
+ Pipes.Fluid.Merge: data OtherStatus
+ Pipes.Fluid.Merge: data Source
+ Pipes.Fluid.Merge: discreteBoth :: Merged x y -> Maybe (x, y)
+ Pipes.Fluid.Merge: discreteLeft :: Merged x y -> Maybe x
+ Pipes.Fluid.Merge: discreteRight :: Merged x y -> Maybe y
+ Pipes.Fluid.Merge: instance (GHC.Classes.Eq b, GHC.Classes.Eq a) => GHC.Classes.Eq (Pipes.Fluid.Merge.Merged a b)
+ Pipes.Fluid.Merge: instance (GHC.Classes.Ord b, GHC.Classes.Ord a) => GHC.Classes.Ord (Pipes.Fluid.Merge.Merged a b)
+ Pipes.Fluid.Merge: instance (GHC.Show.Show b, GHC.Show.Show a) => GHC.Show.Show (Pipes.Fluid.Merge.Merged a b)
+ Pipes.Fluid.Merge: instance GHC.Classes.Eq Pipes.Fluid.Merge.OtherStatus
+ Pipes.Fluid.Merge: instance GHC.Classes.Eq Pipes.Fluid.Merge.Source
+ Pipes.Fluid.Merge: instance GHC.Classes.Ord Pipes.Fluid.Merge.OtherStatus
+ Pipes.Fluid.Merge: instance GHC.Classes.Ord Pipes.Fluid.Merge.Source
+ Pipes.Fluid.Merge: instance GHC.Generics.Generic (Pipes.Fluid.Merge.Merged a b)
+ Pipes.Fluid.Merge: instance GHC.Generics.Generic Pipes.Fluid.Merge.OtherStatus
+ Pipes.Fluid.Merge: instance GHC.Generics.Generic Pipes.Fluid.Merge.Source
+ Pipes.Fluid.Merge: instance GHC.Show.Show Pipes.Fluid.Merge.OtherStatus
+ Pipes.Fluid.Merge: instance GHC.Show.Show Pipes.Fluid.Merge.Source
+ Pipes.Fluid.Merge: isBothLive :: Merged x y -> Bool
+ Pipes.Fluid.Merge: isLeftDead :: Merged x y -> Bool
+ Pipes.Fluid.Merge: isLeftLive :: Merged x y -> Bool
+ Pipes.Fluid.Merge: isRightDead :: Merged x y -> Bool
+ Pipes.Fluid.Merge: isRightLive :: Merged x y -> Bool
+ Pipes.Fluid.Merge: merge :: Merge f => f x -> f y -> f (Merged x y)
+ Pipes.Fluid.Merge: merge' :: Merge f => Maybe x -> Maybe y -> f x -> f y -> f (Merged x y)
+ Pipes.Fluid.React: instance (GHC.Base.Alternative m, GHC.Base.Monad m) => Pipes.Fluid.Merge.Merge (Pipes.Fluid.React.React m)
+ Pipes.Fluid.ReactIO: instance (Control.Monad.Trans.Control.MonadBaseControl GHC.Types.IO m, Data.Constraint.Forall.Forall (Control.Concurrent.Async.Lifted.Safe.Pure m)) => Pipes.Fluid.Merge.Merge (Pipes.Fluid.ReactIO.ReactIO m)

Files

pipes-fluid.cabal view
@@ -1,5 +1,5 @@ name:                pipes-fluid-version:             0.3.0.0+version:             0.3.1.0 synopsis:            Reactively combines Producers so that a value is yielded as soon as possible. description:         Please see README.md homepage:            https://github.com/louispan/pipes-fluid#readme@@ -15,10 +15,11 @@  library   hs-source-dirs:      src-  exposed-modules:     Pipes.Fluid.React-                     , Pipes.Fluid.ReactIO-                     , Pipes.Fluid.Sync-                     , Pipes.Fluid.Alternative+  exposed-modules:     Pipes.Fluid+                       Pipes.Fluid.Merge+                       Pipes.Fluid.React+                       Pipes.Fluid.ReactIO+                       Pipes.Fluid.Sync   build-depends:       base >= 4.7 && < 5                      , constraints >= 0.4 && < 1                      , lens >= 4 && < 5@@ -28,6 +29,7 @@                      , stm >= 2.4 && < 3                      , transformers >= 0.4 && < 6                      , transformers-base >= 0.4 && < 1+                     , these >= 0.7 && < 1   ghc-options:         -Wall   default-language:    Haskell2010 @@ -47,7 +49,7 @@                      , pipes >= 4 && < 5                      , pipes-concurrency >= 2 && < 3                      , pipes-fluid-                     , pipes-misc >= 0.2.0.0 && < 1+                     , pipes-misc >= 0.2.3.0 && < 1                      , stm >= 2.4 && < 3                      , transformers >= 0.4 && < 0.6   ghc-options:         -Wall -threaded -rtsopts -with-rtsopts=-N
+ src/Pipes/Fluid.hs view
@@ -0,0 +1,11 @@+module Pipes.Fluid+    ( module Pipes.Fluid.Merge+    , module Pipes.Fluid.React+    , module Pipes.Fluid.ReactIO+    , module Pipes.Fluid.Sync+    ) where++import Pipes.Fluid.Merge+import Pipes.Fluid.React+import Pipes.Fluid.ReactIO+import Pipes.Fluid.Sync
− src/Pipes/Fluid/Alternative.hs
@@ -1,11 +0,0 @@-module Pipes.Fluid.Alternative where--import Control.Applicative--bothOrEither :: Alternative f => f a -> f b -> f (Either (a, b) (Either a b))-bothOrEither left right =-  (curry Left <$> left <*> right)-  <|>-  (Right . Left <$> left)-  <|>-  (Right . Right <$> right)
+ src/Pipes/Fluid/Merge.hs view
@@ -0,0 +1,98 @@+{-# LANGUAGE DeriveGeneric #-}++module Pipes.Fluid.Merge where++import qualified GHC.Generics as G++-- | Differentiates whether a value from either or both producers.+-- In the case of one producer, additional identify if the other producer is live or dead.+data Source = FromBoth | FromLeft OtherStatus | FromRight OtherStatus+    deriving (Eq, Show, Ord, G.Generic)++-- | The other producer can be live (still yielding values), or dead+data OtherStatus = OtherLive | OtherDead+    deriving (Eq, Show, Ord, G.Generic)++-- | Differentiates when only one side is available (due to initial merge values of Nothing)+-- or if two values (one of which may be a previous values) are availabe.+data Merged a b =+    Coupled Source a b+    | LeftOnly OtherStatus a+    | RightOnly OtherStatus b+    deriving (Eq, Show, Ord, G.Generic)++-- | This can be used with 'Pipes.Prelude.takeWhile'+isBothLive :: Merged x y -> Bool+isBothLive (Coupled FromBoth _ _) = True+isBothLive (Coupled (FromLeft OtherLive) _ _) = True+isBothLive (Coupled (FromRight OtherLive) _ _) = True+isBothLive (LeftOnly OtherLive _) = True+isBothLive (RightOnly OtherLive _) = True+isBothLive _ = False+{-# INLINABLE isBothLive #-}++-- | This can be used with 'Pipes.Prelude.takeWhile'+isLeftLive :: Merged x y -> Bool+isLeftLive (Coupled FromBoth _ _) = True+isLeftLive (Coupled (FromLeft _) _ _) = True+isLeftLive (Coupled (FromRight OtherLive) _ _) = True+isLeftLive (LeftOnly _ _) = True+isLeftLive (RightOnly OtherLive _) = True+isLeftLive _ = False+{-# INLINABLE isLeftLive #-}++-- | This can be used with 'Pipes.Prelude.takeWhile'+isRightLive :: Merged x y -> Bool+isRightLive (Coupled FromBoth _ _) = True+isRightLive (Coupled (FromRight _) _ _) = True+isRightLive (Coupled (FromLeft OtherLive) _ _) = True+isRightLive (RightOnly _ _) = True+isRightLive (LeftOnly OtherLive _) = True+isRightLive _ = False+{-# INLINABLE isRightLive #-}++-- | This can be used with 'Pipes.Prelude.takeWhile'+isRightDead :: Merged x y -> Bool+isRightDead (Coupled (FromLeft OtherDead) _ _) = True+isRightDead (LeftOnly OtherDead _) = True+isRightDead _ = False+{-# INLINABLE isRightDead #-}++-- | This can be used with 'Pipes.Prelude.takeWhile'+isLeftDead :: Merged x y -> Bool+isLeftDead (Coupled (FromRight OtherDead) _ _) = True+isLeftDead (RightOnly OtherDead _) = True+isLeftDead _ = False+{-# INLINABLE isLeftDead #-}++class Merge f where+    merge' :: Maybe x -> Maybe y -> f x -> f y -> f (Merged x y)++merge :: Merge f => f x -> f y -> f (Merged x y)+merge = merge' Nothing Nothing+{-# INLINABLE merge #-}++-- | Keep only the values originated from the left, replacing other yields with Nothing.+-- This is useful when React is based on STM, since filtering with Producer STM results in+-- larger STM transactions which may result in blocking.+discreteLeft :: Merged x y -> Maybe x+discreteLeft (LeftOnly _ x) = Just x+discreteLeft (Coupled FromBoth  x _) = Just x+discreteLeft (Coupled (FromLeft _)  x _) = Just x+discreteLeft _ = Nothing++-- | Keep only the values originated from the right, replacing other yields with Nothing.+-- This is useful when React is based on STM, since filtering with Producer STM results in+-- larger STM transactions which may result in blocking.+discreteRight :: Merged x y -> Maybe y+discreteRight (RightOnly _ y) = Just y+discreteRight (Coupled FromBoth  _ y) = Just y+discreteRight (Coupled (FromRight _)  _ y) = Just y+discreteRight _ = Nothing++-- | Keep only the values originated from both, replacing other yields with Nothing.+-- This is useful when React is based on STM, since filtering with Producer STM results in+-- larger STM transactions which may result in blocking.+discreteBoth :: Merged x y -> Maybe (x, y)+discreteBoth (Coupled FromBoth  x y) = Just (x, y)+discreteBoth _ = Nothing
src/Pipes/Fluid/React.hs view
@@ -4,22 +4,21 @@ {-# LANGUAGE GADTs #-} {-# LANGUAGE ImpredicativeTypes #-} {-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE NoMonomorphismRestriction #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-}  module Pipes.Fluid.React     ( React(..)-    , merge-    , merge'+    , module Pipes.Fluid.Merge     ) where  import Control.Applicative import Control.Lens import Control.Monad.Trans.Class+import Data.These import qualified Pipes as P-import qualified Pipes.Fluid.Alternative as PFA+import Pipes.Fluid.Merge import qualified Pipes.Prelude as PP  -- | The applicative instance of this combines multiple Producers reactively@@ -33,6 +32,7 @@ instance Monad m =>          Functor (React m) where     fmap f (React as) = React $ as P.>-> PP.map f+    {-# INLINABLE fmap #-}  -- | Reactively combines two producers, given initial values to use when the producer is blocked/failed. -- This only works for Alternative m where failure means there was no effects, eg. 'Control.Concurrent.STM', or @MonadTrans t => t STM@.@@ -40,66 +40,84 @@ instance (Alternative m, Monad m) =>          Applicative (React m) where     pure = React . P.yield+    {-# INLINABLE pure #-}+     fs <*> as =         React $         P.for (reactively $ merge fs as) $ \r ->             case r of-                Left (f, a) -> P.yield $ f a-                Right (Left (f, Just a)) -> P.yield $ f a-                Right (Right (Just f, a)) -> P.yield $ f a+                Coupled _ f a -> P.yield $ f a                 -- never got anything from one of the signals, can't do anything yet.                 -- fail/retry/block until we get something from the other signal-                Right (Left (_, Nothing)) -> lift empty-                Right (Right (Nothing, _)) -> lift empty+                LeftOnly _ _ -> lift empty+                RightOnly _ _-> lift empty+    {-# INLINABLE (<*>) #-}  -- | Reactively combines two producers, given initial values to use when the produce hasn't produced anything yet -- Combine two signals, and returns a signal that emits -- @Either bothfired (Either (leftFired, previousRight) (previousLeft, rightFired))@. -- This only works for Alternative m where failure means there was no effects, eg. 'Control.Concurrent.STM', or @MonadTrans t => t STM@. -- Be careful of monad transformers ExceptT that hides the STM Alternative instance.-merge' :: (Alternative m, Monad m) =>-  Maybe x-  -> Maybe y-  -> React m x-  -> React m y-  -> React m (Either (x, y) (Either (x, Maybe y) (Maybe x, y)))-merge' px_ py_ (React xs_) (React ys_) = React $ go px_ py_ xs_ ys_-  where-    go px py xs ys = do-        -- use the Alternative of m, not P.Proxy-        r <- lift $ PFA.bothOrEither (P.next xs) (P.next ys)-        case r-            -- both fs and as have ended-              of-            Left (Left _, Left _) -> pure ()-            -- @xs@ ended,                @ys@ failed/retry/blocked-            Right (Left (Left _)) -> ys P.>-> PP.map useRight-            -- @xs@ failed/retry/blocked, @ys@ ended-            Right (Right (Left _)) -> xs P.>-> PP.map useLeft-            -- @xs@ produced something,   @ys@ failed/retry/blocked-            Right (Left (Right (x, xs'))) -> do-                P.yield $ Right (Left (x, py))-                go (Just x) py xs' ys-            -- @xs@ failed/retry/blocked, @ys@ produced something-            Right (Right (Right (y, ys'))) -> do-                P.yield $ useRight y-                go px (Just y) xs ys'-            -- @xs@ produced something,   @ys@ ended-            Left (Right (x, xs'), Left _) -> do-                P.yield $ useLeft x-                xs' P.>-> PP.map useLeft-            -- @fs@ ended,                @as@ produced something-            Left (Left _, Right (y, ys')) -> do-                P.yield $ useRight y-                ys' P.>-> PP.map useRight-            -- both @fs@ and @as@ produced something-            Left (Right (x, xs'), Right (y, ys')) -> do-                P.yield $ Left (x, y)-                go (Just x) (Just y) xs' ys'+instance (Alternative m, Monad m) => Merge (React m) where+    merge' px_ py_ (React xs_) (React ys_) = React $ go px_ py_ xs_ ys_       where-        useRight y = Right (Right (px, y))-        useLeft x = Right (Left (x, py))+        go px py xs ys = do+            -- use the Alternative of m, not P.Proxy+            r <- lift $ bothOrEither (P.next xs) (P.next ys)+            case r+                -- both fs and as have ended+                  of+                These (Left _) (Left _) -> pure ()+                -- @xs@ ended,                @ys@ failed/retry/blocked+                This (Left _) -> case px of+                    Nothing -> ys P.>-> PP.map (RightOnly OtherDead)+                    Just x -> ys P.>-> PP.map (Coupled (FromRight OtherDead) x)+                -- @xs@ failed/retry/blocked, @ys@ ended+                That (Left _) -> case py of+                    Nothing -> xs P.>-> PP.map (LeftOnly OtherDead)+                    Just y -> xs P.>-> PP.map (\x -> Coupled (FromLeft OtherDead) x y)+                -- @xs@ produced something,   @ys@ failed/retry/blocked+                This (Right (x, xs')) -> do+                    case py of+                        Nothing -> P.yield $ LeftOnly OtherLive x+                        Just y -> P.yield $ Coupled (FromLeft OtherLive) x y+                    go (Just x) py xs' ys+                -- @xs@ failed/retry/blocked, @ys@ produced something+                That (Right (y, ys')) -> do+                    case px of+                        Nothing -> P.yield $ RightOnly OtherLive y+                        Just x -> P.yield $ Coupled (FromRight OtherLive) x y+                    go px (Just y) xs ys'+                -- @xs@ produced something,   @ys@ ended+                These (Right (x, xs')) (Left _) ->+                    case py of+                        Nothing -> do+                            P.yield $ LeftOnly OtherDead x+                            xs' P.>-> PP.map (LeftOnly OtherDead)+                        Just y -> do+                            P.yield $ Coupled (FromLeft OtherDead) x y+                            xs' P.>-> PP.map (\x' -> Coupled (FromLeft OtherDead) x' y)+                -- @fs@ ended,                @as@ produced something+                These (Left _) (Right (y, ys')) ->+                    case px of+                        Nothing -> do+                            P.yield $ RightOnly OtherDead y+                            ys' P.>-> PP.map (RightOnly OtherDead)+                        Just x -> do+                            P.yield $ Coupled (FromRight OtherDead) x y+                            ys' P.>-> PP.map (Coupled (FromRight OtherDead) x)+                -- both @fs@ and @as@ produced something+                These (Right (x, xs')) (Right (y, ys')) -> do+                    P.yield $ Coupled FromBoth x y+                    go (Just x) (Just y) xs' ys'+    {-# INLINABLE merge' #-} --- | A simpler version of merge', with the initial values as Nothing-merge :: (Alternative m, Monad m) => React m x -> React m y -> React m (Either (x, y) (Either (x, Maybe y) (Maybe x, y)))-merge = merge' Nothing Nothing+-- | Used internally by React and ReactIO identifying which side (or both) returned values+bothOrEither :: Alternative f => f a -> f b -> f (These a b)+bothOrEither left right =+  (These <$> left <*> right)+  <|>+  (This <$> left)+  <|>+  (That <$> right)+{-# INLINABLE bothOrEither #-}
src/Pipes/Fluid/ReactIO.hs view
@@ -4,7 +4,6 @@ {-# LANGUAGE GADTs #-} {-# LANGUAGE ImpredicativeTypes #-} {-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE NoMonomorphismRestriction #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-}@@ -12,10 +11,10 @@  module Pipes.Fluid.ReactIO     ( ReactIO(..)-    , mergeIO-    , mergeIO'+    , module Pipes.Fluid.Merge     ) where +import Control.Applicative import qualified Control.Concurrent.Async.Lifted.Safe as A import qualified Control.Concurrent.STM as S import Control.Lens@@ -23,8 +22,9 @@ import Control.Monad.Trans.Class import Control.Monad.Trans.Control import Data.Constraint.Forall (Forall)+import Data.These import qualified Pipes as P-import qualified Pipes.Fluid.Alternative as PFA+import Pipes.Fluid.Merge import qualified Pipes.Prelude as PP  -- | The applicative instance of this combines multiple Producers reactively@@ -41,19 +41,21 @@  instance Monad m => Functor (ReactIO m) where   fmap f (ReactIO as) = ReactIO $ as P.>-> PP.map f+  {-# INLINABLE fmap #-}  instance (MonadBaseControl IO m, Forall (A.Pure m)) => Applicative (ReactIO m) where     pure = ReactIO . P.yield+    {-# INLINABLE pure #-}+     -- 'ap' doesn't know about initial values-    fs <*> as = ReactIO $ P.for (reactivelyIO $ mergeIO fs as) $ \r ->+    fs <*> as = ReactIO $ P.for (reactivelyIO $ merge fs as) $ \r ->         case r of-            Left (f, a) -> P.yield $ f a-            Right (Left (f, Just a)) -> P.yield $ f a-            Right (Right (Just f, a)) -> P.yield $ f a+            Coupled _ f a -> P.yield $ f a             -- never got anything from one of the signals, can't do anything yet.             -- drop the event-            Right (Left (_, Nothing)) -> pure ()-            Right (Right (Nothing, _)) -> pure ()+            LeftOnly _ _ -> pure ()+            RightOnly _ _-> pure ()+    {-# INLINABLE (<*>) #-}  -- | Reactively combines two producers, given initial values to use when the produce hasn't produced anything yet -- Combine two signals, and returns a signal that emits@@ -63,77 +65,97 @@ -- so if the monad is something like (StateT s IO), then the state will alternate -- between the two input producers, which is most likely not what you want. -- This will be detect as a compile error due to use of Control.Concurrent.Async.Lifted.Safe-mergeIO' :: (MonadBaseControl IO m, Forall (A.Pure m)) =>-     Maybe x-  -> Maybe y-  -> ReactIO m x-  -> ReactIO m y-  -> ReactIO m (Either (x, y) (Either (x, Maybe y) (Maybe x, y)))-mergeIO' px_ py_ (ReactIO xs_) (ReactIO ys_) = ReactIO $ do-    ax <- lift $ A.async $ P.next xs_-    ay <- lift $ A.async $ P.next ys_-    doMergeIO px_ py_ ax ay-  where-    doMergeIO :: (MonadBaseControl IO m, Forall (A.Pure m)) =>-         Maybe x-      -> Maybe y-      -> A.Async (Either () (x, P.Producer x m ()))-      -> A.Async (Either () (y, P.Producer y m ()))-      -> P.Producer (Either (x, y) (Either (x, Maybe y) (Maybe x, y))) m ()-    doMergeIO px py ax ay = do-        r <--            lift $-            liftBase . S.atomically $ PFA.bothOrEither (A.waitSTM ax) (A.waitSTM ay)-        case r of-            Left (Left _, Left _) -> pure () -- both @ax@ and @ay@ have ended-            -- @ax@ ended,                @ay@ still waiting-            Right (Left (Left _)) -> do-                ry <- lift $ A.wait ay -- wait for @ay@ to return and then-                                       -- only use @ys@-                case ry of-                    Left _ -> pure ()-                    Right (y', ys') -> do-                        P.yield $ useRight y'-                        ys' P.>-> PP.map useRight-            -- @ax@ still waiting,        @ay@ ended-            Right (Right (Left _)) -> do-                rx <- lift $ A.wait ax -- wait for @ax@ to retrun and then-                                       -- only use @xs@-                case rx of-                    Left _ -> pure ()-                    Right (x', xs') -> do-                        P.yield $ useLeft x'-                        xs' P.>-> PP.map useLeft-            -- @ax@ produced something,   @ay@ still waiting-            Right (Left (Right (x, xs'))) -> do-                P.yield $ useLeft x-                ax' <- lift $ A.async $ P.next xs'-                doMergeIO (Just x) py ax' ay-            -- @ax@ still waiting,        @ay@ produced something-            Right (Right (Right (y, ys'))) -> do-                P.yield $ useRight y-                ay' <- lift $ A.async $ P.next ys'-                doMergeIO px (Just y) ax ay'-            -- @ax@ produced something,   @ay@ ended-            Left (Right (x, xs'), Left _) -> do-                P.yield $ useLeft x-                xs' P.>-> PP.map useLeft-            -- @af@ ended,                @aa@ produced something-            Left (Left _, Right (y, ys')) -> do-                P.yield $ useRight y-                ys' P.>-> PP.map useRight-            -- both @fs@ and @as@ produced something-            Left (Right (x, xs'), Right (y, ys')) -> do-                P.yield $ Left (x, y)-                ax' <- lift $ A.async $ P.next xs'-                ay' <- lift $ A.async $ P.next ys'-                doMergeIO (Just x) (Just y) ax' ay'+instance (MonadBaseControl IO m, Forall (A.Pure m)) => Merge (ReactIO m) where+    merge' px_ py_ (ReactIO xs_) (ReactIO ys_) = ReactIO $ do+        ax <- lift $ A.async $ P.next xs_+        ay <- lift $ A.async $ P.next ys_+        doMergeIO px_ py_ ax ay       where-        useRight y = Right (Right (px, y))-        useLeft x = Right (Left (x, py))+        doMergeIO :: (MonadBaseControl IO m, Forall (A.Pure m)) =>+             Maybe x+          -> Maybe y+          -> A.Async (Either () (x, P.Producer x m ()))+          -> A.Async (Either () (y, P.Producer y m ()))+          -> P.Producer (Merged x y) m ()+        doMergeIO px py ax ay = do+            r <-+                lift $+                liftBase . S.atomically $ bothOrEither (A.waitSTM ax) (A.waitSTM ay)+            case r of+                -- both @ax@ and @ay@ have ended+                These (Left _) (Left _) -> pure ()+                -- @ax@ ended,                @ay@ still waiting+                This (Left _) -> do+                    ry <- lift $ A.wait ay -- wait for @ay@ to return and then+                                           -- only use @ys@+                    case ry of+                        Left _ -> pure ()+                        Right (y, ys') -> case px of+                            Nothing -> do+                                P.yield $ RightOnly OtherDead y+                                ys' P.>-> PP.map (RightOnly OtherDead)+                            Just x -> do+                                P.yield $ Coupled (FromRight OtherDead) x y+                                ys' P.>-> PP.map (Coupled (FromRight OtherDead) x)+                -- @ax@ still waiting,        @ay@ ended+                That (Left _) -> do+                    rx <- lift $ A.wait ax -- wait for @ax@ to retrun and then+                                           -- only use @xs@+                    case rx of+                        Left _ -> pure ()+                        Right (x, xs') -> case py of+                            Nothing -> do+                                P.yield $ LeftOnly OtherDead x+                                xs' P.>-> PP.map (LeftOnly OtherDead)+                            Just y -> do+                                P.yield $ Coupled (FromLeft OtherDead) x y+                                xs' P.>-> PP.map (\x' -> Coupled (FromLeft OtherDead) x' y)+                -- @ax@ produced something,   @ay@ still waiting+                This (Right (x, xs')) -> do+                    case py of+                        Nothing -> P.yield $ LeftOnly OtherLive x+                        Just y -> P.yield $ Coupled (FromLeft OtherLive) x y+                    ax' <- lift $ A.async $ P.next xs'+                    doMergeIO (Just x) py ax' ay+                -- @ax@ still waiting,        @ay@ produced something+                That (Right (y, ys')) -> do+                    case px of+                        Nothing -> P.yield $ RightOnly OtherLive y+                        Just x -> P.yield $ Coupled (FromRight OtherLive) x y+                    ay' <- lift $ A.async $ P.next ys'+                    doMergeIO px (Just y) ax ay'+                -- @ax@ produced something,   @ay@ ended+                These (Right (x, xs')) (Left _) ->+                    case py of+                        Nothing -> do+                            P.yield $ LeftOnly OtherDead x+                            xs' P.>-> PP.map (LeftOnly OtherDead)+                        Just y -> do+                            P.yield $ Coupled (FromLeft OtherDead) x y+                            xs' P.>-> PP.map (\x' -> Coupled (FromLeft OtherDead) x' y)+                -- @af@ ended,                @aa@ produced something+                These (Left _) (Right (y, ys')) ->+                    case px of+                        Nothing -> do+                            P.yield $ RightOnly OtherDead y+                            ys' P.>-> PP.map (RightOnly OtherDead)+                        Just x -> do+                            P.yield $ Coupled (FromRight OtherDead) x y+                            ys' P.>-> PP.map (Coupled (FromRight OtherDead) x)+                -- both @fs@ and @as@ produced something+                These (Right (x, xs')) (Right (y, ys')) -> do+                    P.yield $ Coupled FromBoth x y+                    ax' <- lift $ A.async $ P.next xs'+                    ay' <- lift $ A.async $ P.next ys'+                    doMergeIO (Just x) (Just y) ax' ay'+    {-# INLINABLE merge' #-} -mergeIO :: (MonadBaseControl IO m, Forall (A.Pure m))-    => ReactIO m x-    -> ReactIO m y-    -> ReactIO m (Either (x, y) (Either (x, Maybe y) (Maybe x, y)))-mergeIO = mergeIO' Nothing Nothing+-- | Used internally by React and ReactIO identifying which side (or both) returned values+bothOrEither :: Alternative f => f a -> f b -> f (These a b)+bothOrEither left right =+  (These <$> left <*> right)+  <|>+  (This <$> left)+  <|>+  (That <$> right)+{-# INLINABLE bothOrEither #-}
test/Spec.hs view
@@ -22,7 +22,8 @@ import qualified Pipes.Fluid.React as PF import qualified Pipes.Fluid.ReactIO as PF import qualified Pipes.Fluid.Sync as PF-import qualified Pipes.Misc as PM+import qualified Pipes.Misc.Concurrent as PM+import qualified Pipes.Misc.State.Strict as PM import qualified Pipes.Prelude as PP import Test.Hspec @@ -164,7 +165,7 @@                     testSig' False $ \as bs ->                         PP.toListM $                         PF.reactivelyIO $-                        (PF.ReactIO $ hoist atomically as) `PF.mergeIO` (PF.ReactIO $ hoist atomically bs)+                        (PF.ReactIO $ hoist atomically as) `PF.merge` (PF.ReactIO $ hoist atomically bs)                 xs `shouldSatisfy` isDifferent  isBigger :: Ord a => [a] -> Bool