"Hooking" monad binds using monad transformers?

122 Views Asked by At

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.

1

There are 1 best solutions below

8
On BEST ANSWER

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.)