packages feed

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

{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings          #-}
{-# LANGUAGE TupleSections              #-}

module System.TaskPipeline.Repetition
  ( RepInfo(..)
  , TRIndex(..)
  , HasTRIndex(..)
  , OneOrSeveral(..)
  , parMapTask
  , parMapTask_
  , IndexRange(..)
  , oneIndex
  , oneRange
  , enumIndices
  , enumTRIndices
  ) where

import           Control.Applicative
import           Control.Arrow.Free                       (mapA)
import           Control.Lens                             hiding ((.=))
import           Control.Monad
import           Data.Aeson
import           Data.Aeson.Types                         (Parser)
import qualified Data.Text                                as T
import           Prelude                                  hiding ((.))
import           System.TaskPipeline.PTask
import           System.TaskPipeline.Repetition.Internal


-- | Makes a 'PTask' repeatable and maps it in parallel over a list.
parMapTask
  :: (HasTRIndex a, KatipContext m)
  => RepInfo
  -> PTask m a b
  -> PTask m [a] [b]
parMapTask ri =
  over taskRunnablePart mapA . makeTaskRepeatable ri

-- | Simply repeats a task which takes no input over a list of indices, and
-- ignores the end result. See 'RepInfo' for how these indices are
-- used. See 'parMapTask' for a more complete version.
parMapTask_
  :: (HasTRIndex idx, KatipContext m)
  => RepInfo
  -> PTask m () b
  -> PTask m [idx] ()
parMapTask_ ri task =
   arr (map (, ())) >>> parMapTask ri (arr snd >>> task) >>> arr (const ())


-- * A simple type to handle index ranges

data OneRange i = OneIndex i | OneRange i i

toJSONStr :: (ToJSON a) => a -> Either Value T.Text
toJSONStr a = case toJSON a of
  String s -> Right s
  Number n -> Right $ T.pack $ show n
  o        -> Left o

parseJSONStr :: (FromJSON a) => T.Text -> Parser a
parseJSONStr v = tryNumber v <|> parseJSON (String v)
  where
    tryNumber n = case reads $ T.unpack n of
      [(n',_)] -> parseJSON $ Number n'
      _        -> fail "Not a number"

instance (ToJSON i) => ToJSON (OneRange i) where
  toJSON (OneIndex i) = toJSON i
  toJSON (OneRange a b) = case (toJSONStr a, toJSONStr b) of
    (Right a', Right b') -> String $ a' <> ".." <> b'
    (a', b')             -> object ["lower" .= toJ a', "upper" .= toJ b']
    where toJ (Left o)  = o
          toJ (Right s) = String s

instance (FromJSON i) => FromJSON (OneRange i) where
  parseJSON o@(String s) = case T.splitOn ".." s of
    [a,b] -> (OneRange <$> parseJSONStr a <*> parseJSONStr b)
             <|> (OneIndex <$> parseJSON o)
    _ -> OneIndex <$> parseJSON o
  parseJSON (Object o) = OneRange <$> o .: "lower" <*> o .: "upper"
  parseJSON o = OneIndex <$> parseJSON o

-- | Allows to read from a JSON file either one @a@ or an array of @a@
newtype OneOrSeveral a = OneOrSeveral {getOneOrSeveral :: [a]}

instance (ToJSON a) => ToJSON (OneOrSeveral a) where
  toJSON (OneOrSeveral [r]) = toJSON r
  toJSON (OneOrSeveral rs)  = toJSON rs

instance (FromJSON a) => FromJSON (OneOrSeveral a) where
  parseJSON o@(Array _) = OneOrSeveral <$> parseJSON o
  parseJSON o           = OneOrSeveral . (:[]) <$> parseJSON o

-- | A simple index list that can be used in configuration, and from which a
-- list of indices can be extracted. The JSON representation of it is more
-- compact than that of [(i,i)], as ranges are represented by "a..b" strings
newtype IndexRange i = IndexRange (OneOrSeveral (OneRange i))
  deriving (FromJSON, ToJSON)

-- | A range of just one index
oneIndex :: i -> IndexRange i
oneIndex i = IndexRange $ OneOrSeveral [OneIndex i]

-- | A range of consecutive values
oneRange :: i -> i -> IndexRange i
oneRange a b = IndexRange $ OneOrSeveral [OneRange a b]

-- | Gives a list of indices from an index range
enumIndices :: (Enum i) => IndexRange i -> [i]
enumIndices (IndexRange (OneOrSeveral rs)) = concatMap toL rs
  where
    toL (OneIndex i)   = [i]
    toL (OneRange a b) = [a..b]

-- | Gives a list of TaskRepetitionIndex 
enumTRIndices :: (Enum i, Show i) => IndexRange i -> [TRIndex]
enumTRIndices = map (TRIndex . show) . enumIndices