grapesy-1.0.0: src/Network/GRPC/Common/StreamElem.hs
-- | Positioned elements
--
-- Intended for qualified import.
--
-- > import Network.GRPC.Common.StreamElem qualified as StreamElem
--
-- "Network.GRPC.Common" (intended for unqualified import) exports
-- @StreamElem(..)@, but none of the operations on 'StreamElem'.
module Network.GRPC.Common.StreamElem (
StreamElem(..)
-- * Conversion
, value
-- * Iteration
-- * Iteration
, mapM_
, forM_
, whileNext_
, collect
, whenDefinitelyFinal
) where
import Prelude hiding (mapM_)
import Control.Monad.State (StateT, runStateT, lift, modify)
import Data.Bifoldable
import Data.Bifunctor
import Data.Bitraversable
import Data.Tuple (swap)
{-------------------------------------------------------------------------------
Definition
-------------------------------------------------------------------------------}
-- | An element positioned in a stream
data StreamElem b a =
-- | Element in the stream
--
-- The final element in a stream may or may not be marked as final; if it is
-- not, we will only discover /after/ receiving the final element that it
-- was in fact final. Moreover, we do not know ahead of time whether or not
-- the final element will be marked.
--
-- When we receive an element and it is not marked final, this might
-- therefore mean one of two things, without being able to tell which:
--
-- * We are dealing with a stream in which the final element is not marked.
--
-- In this case, the element may or may not be the final element; if it
-- is, the next value will be 'NoMoreElems' (but waiting for the next
-- value might mean a blocking call).
--
-- * We are dealing with a stream in which the final element /is/ marked.
--
-- In this case, this element is /not/ final (and the final element, when
-- we receive it, will be tagged as 'Final').
StreamElem !a
-- | We received the final element
--
-- The final element is annotated with some additional information.
| FinalElem !a !b
-- | There are no more elements
--
-- This is used in two situations:
--
-- * The stream didn't contain any elements at all.
-- * The final element was not marked as final.
-- See 'StreamElem' for detailed additional discussion.
| NoMoreElems !b
deriving stock (Show, Eq, Functor, Foldable, Traversable)
instance Bifunctor StreamElem where
bimap g f (FinalElem a b) = FinalElem (f a) (g b)
bimap g _ (NoMoreElems b) = NoMoreElems (g b)
bimap _ f (StreamElem a ) = StreamElem (f a)
instance Bifoldable StreamElem where
bifoldMap g f (FinalElem a b) = f a <> g b
bifoldMap g _ (NoMoreElems b) = g b
bifoldMap _ f (StreamElem a ) = f a
instance Bitraversable StreamElem where
bitraverse g f (FinalElem a b) = FinalElem <$> f a <*> g b
bitraverse g _ (NoMoreElems b) = NoMoreElems <$> g b
bitraverse _ f (StreamElem a ) = StreamElem <$> f a
{-------------------------------------------------------------------------------
Conversion
-------------------------------------------------------------------------------}
-- | Value of the element, if one is present
--
-- Returns 'Nothing' in case of 'NoMoreElems'
--
-- Using this function loses the information whether the item was the final
-- item; this information can be recovered using 'whenDefinitelyFinal'.
value :: StreamElem b a -> Maybe a
value = \case
StreamElem a -> Just a
FinalElem a _ -> Just a
NoMoreElems _ -> Nothing
{-------------------------------------------------------------------------------
Iteration
-------------------------------------------------------------------------------}
-- | Invoke the callback for each element
--
-- The final element is marked using 'FinalElem'; the callback is only invoked
-- on 'NoMoreElems' if the list is empty.
--
-- > mapM_ f ([1,2,3], b)
-- > == do f (StreamElem 1)
-- > f (StreamElem 2)
-- > f (FinalElem 3 b)
-- >
-- > mapM_ f ([], b)
-- > == do f (NoMoreElems b)
mapM_ :: forall m a b. Monad m => (StreamElem b a -> m ()) -> [a] -> b -> m ()
mapM_ f = go
where
go :: [a] -> b -> m ()
go [] b = f (NoMoreElems b)
go [a] b = f (FinalElem a b)
go (a:as) b = f (StreamElem a) >> go as b
-- | Like 'mapM_', but with the arguments in opposite order
forM_ :: Monad m => [a] -> b -> (StreamElem b a -> m ()) -> m ()
forM_ as b f = mapM_ f as b
-- | Invoke a function on each 'NextElem', until 'FinalElem' or 'NoMoreElems'
whileNext_ :: forall m a b. Monad m => m (StreamElem b a) -> (a -> m ()) -> m b
whileNext_ f g = go
where
go :: m b
go = do
ma <- f
case ma of
StreamElem a -> g a >> go
FinalElem a b -> g a >> return b
NoMoreElems b -> return b
-- | Invoke the callback until 'FinalElem' or 'NoMoreElems', collecting results
collect :: forall m b a. Monad m => m (StreamElem b a) -> m ([a], b)
collect f =
first reverse . swap <$> flip runStateT [] aux
where
aux :: StateT [a] m b
aux = whileNext_ (lift f) $ modify . (:)
-- | Do we have evidence that this element is the final one?
--
-- The callback is not called on 'StreamElem'; this does /not/ mean that the
-- element was not final; see 'StreamElem' for detailed discussion.
whenDefinitelyFinal :: Applicative m => StreamElem b a -> (b -> m ()) -> m ()
whenDefinitelyFinal msg k =
case msg of
StreamElem _ -> pure ()
FinalElem _ b -> k b
NoMoreElems b -> k b