packages feed

porcupine-core-0.1.0.0: src/System/TaskPipeline/Repetition/Streaming.hs

{-# LANGUAGE FlexibleContexts #-}

-- | This module contains functions that map a task over a stream. Delaying
-- effects of tasks like this plays badly with caching. This interface will be
-- removed in a future version of porcupine.

module System.TaskPipeline.Repetition.Streaming
  ( STask, ISTask, OSTask
  , mappingOverStream
  , listToStreamTask, runStreamTask, streamToListTask
  , Typeable
  ) where

import           Control.Arrow
import           Control.Category
import           Control.Lens                            hiding ((:>))
import           Control.Monad
import           Data.Locations
import           Katip
import           Prelude                                 hiding ((.))
import           Streaming                               (Of (..), Stream)
import qualified Streaming.Prelude                       as S
import           System.TaskPipeline.PTask
import           System.TaskPipeline.PTask.Internal
import           System.TaskPipeline.Repetition.Internal


-- * Type aliases for tasks over streams

-- | An PTask mapping a action over a Stream, transforming @a@'s into
-- @b@'s. Each element in the stream should be associated to an identifier.
type STask m a b r =
  PTask m
        (Stream (Of a) m r)
        (Stream (Of b) m r)

-- | An 'PTask' that consumes an Input Stream and just returns its result.
type ISTask m a r =
  PTask m
        (Stream (Of a) m r)
        r

-- | An 'PTask' that emits an Output Stream.
type OSTask m a b =
  PTask m
        a
        (Stream (Of b) m ())

-- * Running tasks over streams

-- | Turns a task into something that will be repeated once per each item in its
-- input. This is done by transforming VirtualFile accessed by the tasks to add
-- a 'RepetitionKey' to it, indicating that its final file name should be
-- modified by adding an identifier to it just before reading it or writing it.
-- So each loop actually accesses different locations in the end.
--
-- Calls to 'mappingOverStream' can be nested, this way the underlying VirtualFiles
-- will have one 'RepetitionKey' per loop (from outermost loop to innermost).
mappingOverStream
  :: (HasTRIndex a, CanRunPTask m)
  => LocVariable       -- ^ A variable name, used as a key to indicate which
                       -- repetition we're at. Used in the logger context and
                       -- exposed in the yaml file for each VirtualFile that
                       -- will be repeated by this task
  -> Maybe Verbosity   -- ^ The minimal vebosity level at which to display the
                       -- logger context. (Nothing if we don't want to add
                       -- context)
  -> PTask m a b       -- ^ The base task X to repeat
  -> STask m a b r   -- ^ A task that will repeat X it for each input. Each
                       -- input is associated to a identifier that will be
                       -- appended to every Loc mapped to every leaf in the
                       -- LocationTree given to X.
mappingOverStream repetitionKey mbVerb =
    over taskRunnablePart mappingRunnableOverStream
  . makeTaskRepeatable (RepInfo repetitionKey mbVerb)

{-# DEPRECATED mappingOverStream "Prefer the FoldA API to repeat tasks and consume streams" #-}

-- | IMPORTANT: That requires the RunnableTask to be repeatable. See
-- 'makeTaskRepeatable'.
mappingRunnableOverStream
  :: (CanRunPTask m)
  => RunnableTask m a b
  -> RunnableTask m
       (Stream (Of a) m r)
       (Stream (Of b) m r)
mappingRunnableOverStream runnable =
  withRunnableState $ \state inputStream -> do
    firstElem <- S.next inputStream
    case firstElem of
      Left r -> return (return r)  -- Empty input stream
      Right (firstInput, inputStream') -> do
        firstResult <- go state firstInput
        return $
          firstResult `S.cons` S.mapM (go state) inputStream'
  where
    go = execRunnableTask runnable
         -- NOTE: We "cheat" here: we run the funflow layer of the inner
         -- task. We should find a way not to have to do that, but when using
         -- Streaming (which delays effects in a monad) it's really problematic.

-- * Helper functions to create and run streams

-- | Runs the input stream, forgets all its elements and just returns its result
runStreamTask :: (KatipContext m)
              => PTask m
                       (Stream (Of t) m r)
                       r
runStreamTask = toTask S.effects

-- | An 'PTask' converting a list to a stream
listToStreamTask :: (Monad m)
                 => PTask m
                          [t]
                          (Stream (Of t) m ())
listToStreamTask = arr S.each

-- | An 'PTask' converting an input stream to a list. WARNING: It can cause
-- space leaks if the list is too big, as the output list will be eagerly
-- evaluated. This function is provided only for compatibility with existing
-- tasks expecting lists. Please consider switching to processing streams
-- directly. See 'S.toList' for more details.
streamToListTask :: (KatipContext m)
                 => PTask m
                          (Stream (Of t) m r)
                          [t]
streamToListTask = toTask (S.toList_ . void)