packages feed

Frames-streamly-0.3.1.0: src/Frames/Streamly/Streaming/Pipes.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
module Frames.Streamly.Streaming.Pipes
  (
    PipeStream(..)
    -- * re-exports for MonadSafe
  , MonadSafe
  , SafeT
  , runSafeT
  ) where

import Frames.Streamly.Streaming.Class
import qualified Frames.Streamly.Streaming.Common as Common
import Frames.Streamly.Internal.CSV (FramesCSVException(..))

import qualified Pipes
import Pipes ((>->))
import qualified Pipes.Prelude as Pipes
import qualified Pipes.Safe.Prelude as PSafe
import qualified Pipes.Safe as PSafe
import Pipes.Safe (MonadSafe, SafeT, runSafeT)
import qualified Pipes.Prelude.Text as PText
import qualified System.IO as IO
import qualified Control.Foldl as Foldl
import           Control.Monad.Catch                     ( MonadThrow(..))
import Data.Maybe (fromJust)
import qualified Data.ByteString.Lazy as BL
--import Control.Monad.IO.Class (MonadIO(..))
import qualified Data.Text.Encoding as Text
import Data.Word8 (_lf)

newtype PipeStream m a = PipeStream { producer :: Pipes.Producer a m () }

instance Monad m => StreamFunctions PipeStream m where
  type FoldType PipeStream = Foldl.FoldM
  sThrowIfEmpty = pipesThrowIfEmpty . producer
  sLength = Pipes.length . producer
  sCons a s = PipeStream $ Pipes.yield a >> producer s
  sUncons = pipeStreamUncons
  sHead = Pipes.head . producer
  sMap f s = PipeStream $ producer s >-> Pipes.map f
  sMapMaybe f s = PipeStream $  producer s >-> Pipes.mapMaybe f
  sScanM step start s = PipeStream $ producer s >-> Pipes.scanM step start return
  sDrop n s = PipeStream $ producer s >-> Pipes.drop n
  sTake n s = PipeStream $ producer s >-> Pipes.take n
  sFolder step start = pipesFolder step start . producer
  sBuildFold = pipesBuildFold
  sBuildFoldM = pipesBuildFoldM
  sMapFoldM = pipesPostMapM
  sLMapFoldM = Foldl.premapM
  sFoldMaybe = pipesFoldMaybe
  sFold fld  = Foldl.impurely Pipes.foldM fld . producer
  sToList = Pipes.toListM . producer -- this might be bad (not lazy) compared to streamly
  sFromFoldable = PipeStream . pipesFromFoldable

  {-# INLINEABLE sThrowIfEmpty #-}
  {-# INLINEABLE sLength #-}
  {-# INLINEABLE sCons #-}
  {-# INLINEABLE sUncons #-}
  {-# INLINEABLE sHead #-}
  {-# INLINEABLE sMap #-}
  {-# INLINEABLE sMapMaybe #-}
  {-# INLINEABLE sScanM #-}
  {-# INLINEABLE sDrop #-}
  {-# INLINEABLE sTake #-}
  {-# INLINEABLE sFolder #-}
  {-# INLINEABLE sBuildFold #-}
  {-# INLINEABLE sBuildFoldM #-}
  {-# INLINEABLE sMapFoldM #-}
  {-# INLINEABLE sLMapFoldM #-}
  {-# INLINEABLE sFoldMaybe #-}
  {-# INLINEABLE sFold #-}
  {-# INLINEABLE sToList #-}
  {-# INLINEABLE sFromFoldable #-}

instance (Monad m, MonadThrow m, PSafe.MonadMask m, MonadIO m, Foldl.PrimMonad (PSafe.SafeT m)) => StreamFunctionsIO PipeStream m where
  type IOSafe PipeStream m = PSafe.SafeT m
  runSafe = PSafe.runSafeT
  sReadTextLines fp = PipeStream $ PSafe.withFile fp IO.ReadMode unfoldViaBS
  sTokenized sep qm = sMap (Common.tokenizeRow sep qm) . sReadTextLines
  sReadScanMAndFold = pipestreamReadScanMAndFold
  sWriteTextLines fp s = PSafe.runSafeT $ Pipes.runEffect $ (producer s) Pipes.>-> PText.writeFileLn fp

  {-# INLINEABLE runSafe #-}
  {-# INLINEABLE sReadTextLines #-}
  {-# INLINEABLE sTokenized #-}
  {-# INLINEABLE sReadScanMAndFold #-}
  {-# INLINEABLE sWriteTextLines #-}


pipestreamReadScanMAndFold :: MonadSafe m => FilePath -> (x -> Text -> m x) -> m x -> Foldl.FoldM m x b -> m b
pipestreamReadScanMAndFold fp scanStep scanStart fld = Foldl.impurely Pipes.foldM fld $ PText.readFileLn fp >-> Pipes.scanM scanStep scanStart return
{-# INLINE pipestreamReadScanMAndFold #-}

pipesFoldMaybe :: Monad m => Foldl.FoldM m a b -> Foldl.FoldM m (Maybe a) b
pipesFoldMaybe = Foldl.prefilterM (return . isJust) . Foldl.premapM (return . fromJust)


pipesPostMapM :: Monad m => (b -> m c) -> Foldl.FoldM m a b -> Foldl.FoldM m a c
pipesPostMapM f (Foldl.FoldM step begin done) = Foldl.FoldM step begin done'
  where done' x = done x >>= f
{-# INLINABLE pipesPostMapM #-}

pipesBuildFold :: Monad m => (x -> a -> x) -> x -> (x -> b) -> Foldl.FoldM m a b
pipesBuildFold step start extract = Foldl.generalize $ Foldl.Fold step start extract
{-# INLINEABLE pipesBuildFold #-}

pipesBuildFoldM :: (x -> a -> m x) -> m x -> (x -> m b) -> Foldl.FoldM m a b
pipesBuildFoldM = Foldl.FoldM
{-# INLINEABLE pipesBuildFoldM #-}

pipesThrowIfEmpty :: MonadThrow m => Pipes.Producer a m () -> m ()
pipesThrowIfEmpty s = Pipes.null s >>= \b -> if b then throwM EmptyStreamException else return ()
{-# INLINEABLE pipesThrowIfEmpty #-}

pipesFolder :: Monad m => (x -> b -> x) -> x -> Pipes.Producer b m () -> m x
pipesFolder step start = Pipes.fold step start id
{-# INLINE pipesFolder #-}

pipesFromFoldable :: (Functor m, Foldable f) => f a -> Pipes.Producer a m ()
pipesFromFoldable = Pipes.each
{-# INLINEABLE pipesFromFoldable #-}

pipeStreamUncons :: Monad m => PipeStream m a -> m (Maybe (a, PipeStream m a))
pipeStreamUncons p = do
  pUncons <- Pipes.next (producer p)
  case pUncons of
    Left () -> return Nothing
    Right (a, s) -> return $ Just (a, PipeStream s)
{-# INLINABLE pipeStreamUncons #-}

unfoldViaBS' :: Monad m => BL.ByteString -> Pipes.Producer BL.ByteString m ()
unfoldViaBS' = Pipes.unfoldr inner
  where
    {-# INLINE inner #-}
    inner input'
      | BL.null input' = pure $ Left ()
      | otherwise =
          case BL.elemIndex _lf input' of
            Nothing -> pure $ Right (input', BL.empty)
            Just i ->
              let (prefix, suffix) = BL.splitAt i input'
              in pure $ Right (prefix, BL.drop 1 suffix)
{-# INLINE unfoldViaBS' #-}

unfoldViaBS :: MonadIO m => IO.Handle -> Pipes.Producer Text m ()
unfoldViaBS h = do
  lbs <- Pipes.lift $ liftIO $ BL.hGetContents h
  unfoldViaBS' lbs >-> Pipes.map (Text.decodeUtf8 . BL.toStrict)
{-# INLINEABLE unfoldViaBS #-}