potoki-core-2.3: library/Potoki/Core/Consume.hs
module Potoki.Core.Consume
(
Consume(..),
apConcurrently,
list,
sum,
transform,
count,
head,
last,
reverseList,
vector,
concat,
fold,
foldInIO,
folding,
foldingInIO,
execState,
writeBytesToStdout,
writeBytesToFile,
writeBytesToFileWithoutBuffering,
appendBytesToFile,
deleteFiles,
printBytes,
printText,
printString,
parseBytes,
parseText,
concurrently,
)
where
import Potoki.Core.Prelude hiding (sum, head, fold, concat, last)
import Potoki.Core.Types
import qualified Potoki.Core.Fetch as A
import qualified Acquire.IO as B
import qualified Potoki.Core.Transform as J
import qualified Potoki.Core.IO.Fetch as L
import qualified Data.ByteString as C
import qualified Data.Attoparsec.ByteString as E
import qualified Data.Attoparsec.Text as F
import qualified Data.Attoparsec.Types as I
import qualified Data.Text.IO as K
import qualified Control.Foldl as D
import qualified System.Directory as G
import qualified Potoki.Core.Transform.Concurrency as B
import qualified Control.Monad.Trans.State.Strict as O
instance Profunctor Consume where
{-# INLINE dimap #-}
dimap inputMapping outputMapping (Consume consume) =
Consume (\ fetch -> fmap outputMapping (consume $ fmap inputMapping fetch))
instance Choice Consume where
right' :: Consume a b -> Consume (Either c a) (Either c b)
right' (Consume rightConsumeIO) =
Consume $ \ (Fetch eitherFetchIO) -> do
fetchedLeftMaybeRef <- newIORef Nothing
consumedRight <-
rightConsumeIO $ Fetch $ do
eitherFetch <- eitherFetchIO
case eitherFetch of
Nothing -> return Nothing
Just element -> case element of
Right fetchedRight -> return $ Just fetchedRight
Left fetchedLeft -> do
writeIORef fetchedLeftMaybeRef $ Just fetchedLeft
return Nothing
fetchedLeftMaybe <- readIORef fetchedLeftMaybeRef
case fetchedLeftMaybe of
Nothing -> return $ Right consumedRight
Just fetchedLeft -> return $ Left fetchedLeft
instance Functor (Consume input) where
fmap = rmap
instance Applicative (Consume a) where
pure x = Consume $ \ _ -> pure x
Consume leftConsumeIO <*> Consume rightConsumeIO =
Consume $ \ fetch -> leftConsumeIO fetch <*> rightConsumeIO fetch
instance Monad (Consume a) where
Consume leftConsumeIO >>= toRightConsumeIO = Consume $ \ fetch -> do
Consume rightConsumeIO <- toRightConsumeIO <$> leftConsumeIO fetch
rightConsumeIO fetch
instance MonadIO (Consume a) where
liftIO a = Consume $ \ _ -> a
apConcurrently :: Consume a (b -> c) -> Consume a b -> Consume a c
apConcurrently (Consume leftConsumeIO) (Consume rightConsumeIO) =
Consume $ \ fetch -> do
(leftFetch, rightFetch) <- A.duplicate fetch
rightOutputVar <- newEmptyMVar
_ <- forkIO $ do
!rightOutput <- rightConsumeIO rightFetch
putMVar rightOutputVar rightOutput
!leftOutput <- leftConsumeIO leftFetch
rightOutput <- takeMVar rightOutputVar
return (leftOutput rightOutput)
unit :: Consume a ()
unit =
Consume $ \ _ -> return ()
{-# INLINABLE list #-}
list :: Consume input [input]
list =
Consume $ \ (Fetch fetchIO) ->
let
build !acc = do
fetch <- fetchIO
case fetch of
Nothing -> pure $ acc []
Just !element -> build $ acc . (:) element
in build id
{-# INLINE sum #-}
sum :: Num num => Consume num num
sum =
Consume $ \ (Fetch fetchIO) ->
let
build !acc = do
fetch <- fetchIO
case fetch of
Nothing -> pure acc
Just !element -> build $ element + acc
in build 0
{-# INLINABLE transform #-}
transform :: Transform input1 input2 -> Consume input2 output -> Consume input1 output
transform (Transform transformAcquire) (Consume consumeIO) =
Consume $ \ fetch -> B.acquire (transformAcquire fetch) consumeIO
{-# INLINABLE head #-}
head :: Consume input (Maybe input)
head =
Consume (\ (A.Fetch fetchIO) -> fetchIO)
{-# INLINABLE last #-}
last :: Consume input (Maybe input)
last =
fold D.last
{-|
A faster alternative to "list",
which however constructs the list in the reverse order.
-}
{-# INLINABLE reverseList #-}
reverseList :: Consume input [input]
reverseList =
Consume $ \ (A.Fetch fetchIO) -> build fetchIO []
where
build fetchIO !acc =
fetchIO >>= \case
Nothing -> pure acc
Just element -> build fetchIO (element : acc)
{-# INLINABLE vector #-}
vector :: Consume input (Vector input)
vector =
foldInIO D.vectorM
{-# INLINABLE count #-}
count :: Consume input Int
count =
Consume $ \ (A.Fetch fetchIO) -> let
iterate !count = do
fetchResult <- fetchIO
case fetchResult of
Just _ -> iterate (succ count)
Nothing -> return count
in iterate 0
{-# INLINABLE concat #-}
concat :: (Semigroup monoid, Monoid monoid) => Consume monoid monoid
concat =
Consume $ \ (A.Fetch fetchIO) -> build fetchIO mempty
where
build fetchIO !acc =
fetchIO >>= \case
Nothing -> pure acc
Just element -> build fetchIO (acc <> element)
{-# INLINABLE processInIO #-}
processInIO :: IO () -> (element -> IO ()) -> Consume element ()
processInIO stop process =
Consume (\ fetch -> L.fetchAndHandleAll fetch stop process)
{-# INLINABLE printBytes #-}
printBytes :: Consume ByteString ()
printBytes =
processInIO (putChar '\n') C.putStr
{-# INLINABLE printText #-}
printText :: Consume Text ()
printText =
processInIO (putChar '\n') K.putStr
{-# INLINABLE printString #-}
printString :: Consume String ()
printString =
processInIO (putChar '\n') putStr
{-# INLINABLE writeBytesToStdout #-}
writeBytesToStdout :: Consume ByteString ()
writeBytesToStdout =
processInIO (return ()) (C.hPut stdout)
{-|
Overwrite a file.
* Exception-free
* Automatic resource management
-}
{-# INLINABLE writeBytesToFile #-}
writeBytesToFile :: FilePath -> Consume ByteString (Either IOException ())
writeBytesToFile =
writeBytesToFileWithBuffering (BlockBuffering Nothing)
{-|
Overwrite a file.
* Exception-free
* Automatic resource management
-}
{-# INLINABLE writeBytesToFileWithBuffering #-}
writeBytesToFileWithBuffering :: BufferMode -> FilePath -> Consume ByteString (Either IOException ())
writeBytesToFileWithBuffering bufferMode path =
Consume $ \ fetch ->
try $ withFile path WriteMode $ \ handle ->
do
hSetBuffering handle bufferMode
L.fetchAndHandleAll fetch (return ()) (C.hPut handle)
{-|
A more efficient implementation than just writing to file without buffering.
It uses an explicit buffer of input chunks and flushes all the chunks that have been so far aggregated at once.
-}
{-# INLINABLE writeBytesToFileWithoutBuffering #-}
writeBytesToFileWithoutBuffering :: FilePath -> Consume ByteString (Either IOException ())
writeBytesToFileWithoutBuffering =
transform (arr mconcat . B.bufferizeFlushing 64) . writeBytesToFileWithBuffering NoBuffering
{-|
Append to a file.
* Exception-free
* Automatic resource management
-}
{-# INLINABLE appendBytesToFile #-}
appendBytesToFile :: FilePath -> Consume ByteString (Either IOException ())
appendBytesToFile path =
Consume $ \ fetch ->
try $ withFile path AppendMode $ \ handleVal ->
do
L.fetchAndHandleAll fetch (return ()) (C.hPut handleVal)
{-# INLINABLE deleteFiles #-}
deleteFiles :: Consume FilePath (Either IOException ())
deleteFiles =
Consume $ \ fetch ->
try $ L.fetchAndHandleAll fetch (return ()) G.removeFile
{-# INLINABLE fold #-}
fold :: D.Fold input output -> Consume input output
fold (D.Fold step initVal finish) =
Consume $ \ (A.Fetch fetch) -> build fetch initVal
where
build fetch !acc =
fetch >>= \case
Nothing -> pure $ finish acc
Just input -> build fetch (step acc input)
{-# INLINABLE foldInIO #-}
foldInIO :: D.FoldM IO input output -> Consume input output
foldInIO (D.FoldM step initVal finish) =
Consume $ \ (A.Fetch fetch) -> build fetch =<< initVal
where
build fetch !acc =
fetch >>= \case
Nothing -> finish acc
Just input -> step acc input >>= build fetch
{-# INLINABLE folding #-}
folding :: D.Fold a b -> Consume a c -> Consume a (b, c)
folding (D.Fold step initVal extract) (Consume consumeIO) =
Consume $ \ fetch -> do
foldStateRef <- newIORef initVal
consumptionResult <-
consumeIO (A.handlingElements (\ element -> do
!newState <- flip step element <$> readIORef foldStateRef
writeIORef foldStateRef newState) fetch)
foldResult <- extract <$> readIORef foldStateRef
return (foldResult, consumptionResult)
{-# INLINABLE foldingInIO #-}
foldingInIO :: D.FoldM IO a b -> Consume a c -> Consume a (b, c)
foldingInIO (D.FoldM step initVal extract) (Consume consumeIO) =
Consume $ \ fetch -> do
foldStateRef <- newIORef =<< initVal
consumptionResult <-
consumeIO (A.handlingElements (\ element -> do
!newState <- flip step element =<< readIORef foldStateRef
writeIORef foldStateRef newState) fetch)
foldResult <- extract =<< readIORef foldStateRef
return (foldResult, consumptionResult)
{-# INLINE execState #-}
execState :: (a -> O.State s b) -> s -> Consume a s
execState stateFn initialState =
fold $ D.Fold (\currentState input -> snd $ O.runState (stateFn input) currentState) initialState id
{-# INLINABLE runParseResult #-}
runParseResult :: (Monoid input, Eq input) => (input -> I.IResult input output) -> Consume input (Either Text output)
runParseResult inputToResult =
Consume $ \ (A.Fetch fetchInput) ->
let
just !input =
case inputToResult input of
I.Partial newInputToResult -> consume newInputToResult
I.Done _ parsed -> return (Right parsed)
I.Fail _ contexts message -> return (Left resultMessage)
where
resultMessage =
if null contexts
then fromString message
else fromString (showString (intercalate " > " contexts) (showString ": " message))
consume _ =
fetchInput >>= \case
Nothing -> just mempty
Just !input -> just input
in consume inputToResult
{-# INLINABLE parseBytes #-}
parseBytes :: E.Parser output -> Consume ByteString (Either Text output)
parseBytes =
runParseResult . E.parse
{-# INLINABLE parseText #-}
parseText :: F.Parser output -> Consume Text (Either Text output)
parseText =
runParseResult . F.parse
{-|
Execute a Consume concurrently and consume its results.
-}
{-# INLINABLE concurrently #-}
concurrently :: NFData b => Int -> Consume a b -> Consume b c -> Consume a c
concurrently amount consume1 consume2 =
transform (B.concurrently amount (J.consume consume1)) consume2