SciFlow-0.3.0: src/Scientific/Workflow/Types.hs
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE CPP #-}
module Scientific.Workflow.Types where
import Control.Applicative
import Control.Arrow (Arrow (..), Kleisli (..),
first, second)
import qualified Control.Category as C
import Control.Lens (makeLenses, use, (%=))
import Control.Monad.State (MonadTrans, StateT, lift,
(>=>))
import qualified Data.ByteString as B
import Data.Default.Class
import qualified Data.HashMap.Strict as M
import qualified Data.Text as T
import qualified Language.Haskell.TH.Lift as L
import Shelly (shelly, test_d, lsT, fromText)
import Scientific.Workflow.Serialization (Serializable (..))
import Debug.Trace
--------------------------------------------------------------------------------
-- Workflow
--------------------------------------------------------------------------------
type ID = String
data NodeState = Finished
| Unfinished
| Skip
deriving (Show)
type NodesDB = M.HashMap ID NodeState
data WorkflowConfig = WorkflowConfig
{ _baseDir :: !FilePath
, _logDir :: !FilePath
, _nodeStatus :: !NodesDB
}
makeLenses ''WorkflowConfig
readNodeStatus :: ID -> NodesDB -> NodeState
readNodeStatus = M.lookupDefault Unfinished
{-# INLINE readNodeStatus #-}
writeNodeStatus :: ID -> NodeState -> NodesDB -> NodesDB
writeNodeStatus = M.insert
{-# INLINE writeNodeStatus #-}
data Mode = All
| Select [ID]
L.deriveLift ''Mode
data RunOpt = RunOpt
{ _runDir :: !FilePath
, _runLogDir :: !FilePath
, _runMode :: !Mode
, _runForce :: !Bool
}
L.deriveLift ''RunOpt
instance Default RunOpt where
def = RunOpt
{ _runDir = "./"
, _runLogDir = "wfCache/"
, _runMode = All
, _runForce = False
}
mkNodesDB :: RunOpt -> IO NodesDB
mkNodesDB opt = do
fls <- shelly $ do
e <- test_d $ fromText $ T.pack dir
if e then lsT $ fromText $ T.pack dir
else return []
return $ M.fromList $
zip (map (T.unpack . snd . T.breakOnEnd "/") fls) $ repeat Finished
where
dir = _runDir opt ++ "/" ++ _runLogDir opt ++ "/"
data Workflow where
Workflow :: IOProcessor () b -> Workflow
--------------------------------------------------------------------------------
-- Arrow
--------------------------------------------------------------------------------
-- | labeled Arrow
newtype Processor m a b = Processor { runProcessor :: a -> m b }
instance Monad m => C.Category (Processor m) where
id = Processor return
(Processor f) . (Processor g) = Processor $ g >=> f
instance Monad m => Arrow (Processor m) where
arr f = Processor (return . f)
first (Processor f) = Processor (\ ~(b,d) -> f b >>= \c -> return (c,d))
second (Processor f) = Processor (\ ~(d,b) -> f b >>= \c -> return (d,c))
-- | Label is a pair of side effects
type Label m l o = (l -> m (Maybe o), l -> o -> m ())
-- | Turn a Kleisli arrow into a labeled arrow
label :: (MonadTrans t, Monad m, Monad (t m))
=> Label (t m) l b
-> l
-> Kleisli m a b
-> Processor (t m) a b
label (pre, suc) l (Kleisli f) = Processor $ \x -> do
d <- pre l
case d of
Nothing -> do
o <- lift $ f x
suc l o
return o
Just v -> return v
{-# INLINE label #-}
class Arrow a => Actor a b c where
arrIO :: a b c -> Kleisli IO b c
instance Actor (->) a b where
arrIO = arr
instance Actor (Kleisli IO) a b where
arrIO = id
proc :: Actor ar a b => Serializable b => ID -> ar a b -> IOProcessor a b
proc l ar = label (recover, save) l $ arrIO ar
source :: Serializable o => ID -> o -> Source o
source l x = proc l $ const x
nullSource :: Source o
nullSource = label (const $ return $ Just undefined, undefined) ("" :: String) $ arr $ const undefined
recover :: Serializable a => ID -> StateT WorkflowConfig IO (Maybe a)
recover l = do
st <- readNodeStatus l <$> use nodeStatus
#ifdef DEBUG
traceM $ "Process node: " ++ l ++ " . Status is: " ++ show st
#endif
case st of
Finished -> do
dir1 <- use baseDir
dir2 <- use logDir
let file = dir1 ++ "/" ++ dir2 ++ "/" ++ l
(Just . deserialize) <$> lift (B.readFile file)
Unfinished -> return Nothing
Skip -> return $ Just undefined
{-# INLINE recover #-}
save :: Serializable a => ID -> a -> StateT WorkflowConfig IO ()
save l x = do
dir1 <- use baseDir
dir2 <- use logDir
lift $ B.writeFile (dir1 ++ "/" ++ dir2 ++ "/" ++ l) $ serialize x
nodeStatus %= writeNodeStatus l Finished
#ifdef DEBUG
traceM $ "Finish node: " ++ l ++ "\n"
#endif
{-# INLINE save #-}
type IOProcessor = Processor (StateT WorkflowConfig IO)
-- | Source produce an output without inputs
type Source = IOProcessor ()
instance Functor Source where
fmap f (Processor g) = Processor $ fmap f . g
instance Applicative Source where
pure = Processor . const . return
Processor f <*> Processor g = Processor $ \x -> do
a <- f x
b <- g x
return $ a b