simple-conduit-0.5.1: Conduit/Simple/Core.hs
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
-- | Please see the project README for more details:
--
-- https://github.com/jwiegley/simple-conduit/blob/master/README.md
--
-- Also see this blog article:
--
-- https://www.newartisans.com/2014/06/simpler-conduit-library
module Conduit.Simple.Core where
import Control.Applicative (Alternative((<|>), empty),
Applicative((<*>), pure))
import Control.Arrow (first)
import Control.Monad.Catch (MonadThrow(..), MonadMask, MonadCatch)
import qualified Control.Monad.Catch as Catch
import Control.Monad.Cont
import Control.Monad.Error.Class (MonadError(..))
import Control.Monad.Free
import Control.Monad.Morph (MMonad(..), MFunctor(..))
import Control.Monad.Reader.Class (MonadReader(..))
import Control.Monad.State.Class (MonadState(..))
import Control.Monad.Trans.Either (EitherT(..), left)
import Control.Monad.Writer.Class (MonadWriter(..))
import Data.Bifunctor (Bifunctor(bimap))
import Data.Foldable (Foldable(foldMap))
import Data.Functor.Identity
import Data.Semigroup (Monoid(..), Semigroup((<>)))
-- | A Source is a short-circuiting monadic fold.
--
-- 'Source' forms a Monad that behaves as 'ListT'; for example:
--
-- @
-- do x <- yieldMany [1..3]
-- line <- sourceFile "foo.txt"
-- return (x, line)
-- @
--
-- This yields the cross-product of [3] and the lines in the files, but only
-- reading chunks from the file as needed by the sink.
--
-- To skip to the next value in a Source, use the function 'skip' or 'mempty';
-- to close the source, use 'close'. For example:
--
-- @
-- do x <- yieldMany [1..10]
-- if x == 2 || x == 9
-- then return x
-- else if x < 5
-- then skip
-- else close
-- @
--
-- This outputs the list @[2]@.
--
-- A key difference from the @conduit@ library is that monadic chaining of
-- sources with '>>' follows 'ListT', and not concatenation as in conduit. To
-- achieve conduit-style behavior, use the Monoid instance:
--
-- >>> sinkList $ yieldMany [1..3] <> yieldMany [4..6]
-- [1,2,3,4,5,6]
newtype Source m a = Source { getSource :: forall r. Cont (r -> EitherT r m r) a }
deriving Functor
-- | A 'Conduit' is a "Source homomorphism", or simple a mapping between
-- sources. There is no need for it to be a type synonym, except to save
-- repetition across type signatures.
type Conduit a m b = Source m a -> Source m b
-- | A 'Sink' folds a 'Source' down to its result value. It is simply a
-- convenient type synonym for functions mapping a 'Source' to some result
-- type.
type Sink a m r = Source m a -> m r
instance Monad m => Semigroup (Source m a) where
x <> y = source $ \r c -> runSource x r c >>= \r' -> runSource y r' c
instance Monad m => Monoid (Source m a) where
mempty = skip
mappend = (<>)
instance Monad m => Alternative (Source m) where
empty = skip
(<|>) = (<>)
instance Monad m => MonadPlus (Source m) where
mzero = skip
mplus = (<|>)
instance Applicative (Source m) where
pure = return
f <*> x = source $ \z yield ->
runSource f z (\r f' -> runSource x r (\s x' -> yield s (f' x')))
instance Monad (Source m) where
return x = Source $ return x
Source m >>= f = Source $ join (liftM (getSource . f) m)
instance MFunctor Source where
hoist nat m = source $ runSource (hoist nat m)
instance MMonad Source where
embed f m = source $ runSource (embed f m)
instance MonadIO m => MonadIO (Source m) where
liftIO m = source $ \r yield -> liftIO m >>= yield r
instance MonadTrans Source where
lift m = source $ \r yield -> lift m >>= yield r
instance (Functor f, MonadFree f m) => MonadFree f (Source m) where
wrap t = source $ \r h -> wrap $ fmap (\p -> runSource p r h) t
-- jww (2014-06-15): If it weren't for the universally quantified r...
-- instance MonadCont (Source m) where
-- callCC f = source $ \z c -> runSource (f (\x -> source $ \r _ -> c r x)) z c
instance MonadReader r m => MonadReader r (Source m) where
ask = lift ask
local f = conduit $ \r yield -> local f . yield r
reader = lift . reader
instance MonadState s m => MonadState s (Source m) where
get = lift get
put = lift . put
state = lift . state
instance MonadWriter w m => MonadWriter w (Source m) where
writer = lift . writer
tell = lift . tell
listen = conduit $ \r yield x ->
listen (return ()) >>= yield r . first (const x)
pass = conduit $ \r yield (x, f) -> pass (return ((), f)) >> yield r x
instance MonadError e m => MonadError e (Source m) where
throwError = lift . throwError
catchError src f = source $ \z yield -> EitherT $
runEitherT (runSource src z yield)
`catchError` \e -> runEitherT (runSource (f e) z yield)
instance MonadThrow m => MonadThrow (Source m) where
throwM = lift . throwM
instance MonadCatch m => MonadCatch (Source m) where
catch src f = source $ \z yield -> EitherT $
runEitherT (runSource src z yield)
`Catch.catch` \e -> runEitherT (runSource (f e) z yield)
instance MonadMask m => MonadMask (Source m) where
mask a = source $ \z yield -> EitherT $ Catch.mask $ \u ->
runEitherT $ runSource (a $ \b -> source $ \r yield' ->
EitherT $ liftM Right $ u $ sink r yield' b) z yield
uninterruptibleMask a =
source $ \z yield -> EitherT $ Catch.uninterruptibleMask $ \u ->
runEitherT $ runSource (a $ \b -> source $ \r yield' ->
EitherT $ liftM Right $ u $ sink r yield' b) z yield
instance Foldable (Source Identity) where
foldMap f = runIdentity . sink mempty (\r x -> return $ r `mappend` f x)
-- | Promote any sink to a source. This can be used as if it were a source
-- transformer (aka, a conduit):
--
-- >>> sinkList $ returnC $ sumC $ mapC (+1) $ yieldMany [1..10]
-- [65]
--
-- Note that 'returnC' is a synonym for 'Control.Monad.Trans.Class.lift'.
returnC :: Monad m => m a -> Source m a
returnC = lift
prod :: Source m (Cont (r -> EitherT r m r) (Source m a))
-> Cont (r -> EitherT r m r) (Source m a)
prod (Source (ContT src)) = ContT $ \yield -> src $ \(ContT x) -> x yield
close :: Monad m => Source m a
close = source $ const . left
skip :: Monad m => Source m a
skip = source $ const . return
runSource :: Source m a -> r -> (r -> a -> EitherT r m r) -> EitherT r m r
runSource (Source (ContT src)) z yield =
runIdentity (src (\x -> Identity $ \r -> yield r x)) z
lowerSource :: (Monad m, Monoid a) => Source m a -> m a
lowerSource src = unwrap $ runSource src mempty ((return .) . mappend)
source :: (forall r. r -> (r -> a -> EitherT r m r) -> EitherT r m r) -> Source m a
source await = Source $ ContT $ \yield -> Identity $ \z ->
await z (\r x -> runIdentity (yield x) r)
conduit :: (forall r. r -> (r -> b -> EitherT r m r) -> a -> EitherT r m r)
-> Conduit a m b
conduit f src = source $ \z c -> runSource src z (`f` c)
-- | Most of the time conduits pass the fold variable through unmolested, but
-- sometimes you need to ignore that variable and use your own within a
-- stage of the pipeline. This is done by wrapping the fold variable in a
-- tuple and then unwrapping it when the conduit is done. 'conduitWith'
-- makes this transparent.
conduitWith :: Monad m
=> s
-> (forall r. (r, s) -> (r -> b -> EitherT (r, s) m (r, s)) -> a
-> EitherT (r, s) m (r, s))
-> Conduit a m b
conduitWith s f src = source $ \z yield ->
rewrap fst $ runSource src (z, s) $ \(r, t) ->
f (r, t) (\r' -> rewrap (, t) . yield r')
unwrap :: Monad m => EitherT a m a -> m a
unwrap k = either id id `liftM` runEitherT k
rewrap :: Monad m => (a -> b) -> EitherT a m a -> EitherT b m b
rewrap f k = EitherT $ bimap f f `liftM` runEitherT k
sink :: forall m a r. Monad m => r -> (r -> a -> EitherT r m r) -> Sink a m r
sink z f src = either id id `liftM` runEitherT (runSource src z f)
awaitForever :: (a -> Source m b) -> Conduit a m b
awaitForever = (=<<)