I'm interested in the problem of resumable (and serializable) processes and saw that there is a Haskell package ("Workflow") that seems to do just that: take an existing computation (of type IO ()
) and wrap it in a monad that make the computation resumable.
My rough idea of how that should work is that you consider an element of type IO ()
as an expression built-up from "binds", and that the monad transformer (i.e. lift
) should map binds to binds, thus allowing you to insert some pre- or post-processing steps into your expression. However so far I have not been able to make this work.
So my I guess my question would be, is my idea reasonable? If not, how does the Workflow package do its magic?
To fix ideas, below is what I've tried:
-- Instance of MonadTrans for WorkflowT
{-# LANGUAGE FlexibleContexts #-}
import Control.Monad.Trans.Class (MonadTrans, lift)
import Control.Monad.IO.Class (MonadIO, liftIO)
-- Definition of the Workflow monad transformer
newtype WorkflowT m a = WorkflowT { runWorkflowT :: m a }
instance MonadTrans WorkflowT where
lift = WorkflowT
-- Instance of Functor for WorkflowT
instance Functor m => Functor (WorkflowT m) where
fmap f (WorkflowT ma) = WorkflowT $ fmap f ma
-- Instance of Applicative for WorkflowT
instance Applicative m => Applicative (WorkflowT m) where
pure = WorkflowT . pure
(WorkflowT mf) <*> (WorkflowT ma) = WorkflowT $ mf <*> ma
-- Instance of MonadIO for WorkflowT
instance MonadIO m => MonadIO (WorkflowT m) where
liftIO = WorkflowT . liftIO
-- Instance of Monad for WorkflowT
instance MonadIO m => Monad (WorkflowT m) where
return = pure
WorkflowT ma >>= f = WorkflowT $ do
a <- ma
let WorkflowT mb = f a
b <- mb
liftIO $ putStrLn "Checkpointing after executing step..."
return b
-- Example computation representing multiple steps
compositeComputation :: IO ()
compositeComputation = do
putStrLn "Executing step 1..."
putStrLn "Executing step 2..."
putStrLn "Executing step 3..."
-- Lift the composite computation into the WorkflowT monad
transformer
liftedComputation :: MonadIO m => WorkflowT m ()
liftedComputation = liftIO compositeComputation
main :: IO ()
main = do
-- Run the lifted computation in the base monad
runWorkflowT liftedComputation
My expectation (hope) would have been that the line "Checkpointing..." would be printed after every "step", but it is not.
Nothing in your code ever uses the bind of WorkflowT. compositeComputation
may be composite in the IO monad but it's wrapped in a single WorkflowT.
If you do something like this it works.
runWorkflowT (do liftIO (putStrLn "a")
liftIO (putStrLn "b")
liftIO (putStrLn "c"))
The Workflow package requires you to do something similar - if you look at the example here it wraps each individual IO action with step
, not all of them together.
(Maybe you also want to move the logging before b <- mb
. It seems weird for it to come after both actions rather than in between.)