Exception handling in Haskell Jobs

I am using yesod-job-queue to define the job . The use-case was, that this was a scheduled job and we needed to log the failures.

myJob :: (MonadCatch m, MonadIO m, MonadBaseControl IO m) => ReaderT master m ()

But on running this; there was an error, since runJob in yesod-job-queue does not have the MonadCatch constraint. So the simplest solution was to switch to Control.Exception.Safe .

myJob = do
_<- foo1
_<- liftIO $ foo2
foo1 :: MonadIO m => m ()
foo1 = do
-- operation might throw error
foo2 :: IO ()
foo2 = do

-- operation might throw error

Note : foo2 has IO since we were using catch inside the block.

This did eliminate the error; but now when the exception occurred, the job remained stuck! Also, the catch produces IO () and we were in ReaderT master m ()

MonadCatch is a monad that catches errors that are thrown. So of course, that constraint cannot be eliminated. MonadBaseControlIO ; on the other hand, takes an m and run IO callbacks using it.

So is there an effective way to handle an exception such that it does not obstruct the job ?

Let’s see.. what can be done for our use case is, we take the catch from safe-exceptions and monomorphize it to IO such that it’s type would be :

Control.Exception.Safe.catch::Exception e => IO a -> (e -> IO a) -> IO a

Now MonadBaseControl can be used to life up the IO :

catchLift :: (Exception e, MonadBaseControl IO m) => m a -> (e -> m a) -> m a
catchLift action handler = control $ \runInIO ->
runInIO action `Control.Exception.Safe.catch` \e -> runInIO (handler e)

And that should work.

But.. What does it mean to say — monomorphized to IO ?

Well, simply put, MonadCatch m becomes IO .
There’s an instance MonadCatch IO
So wherever you see MonadCatch m => m a, you can specialize m to IO to get IO a .

The major idea here is that MonadIO gives you liftIO :: IO a -> m a and MonadBaseContro IO m gives control :: ((m a -> IO a) -> IO a) -> m a .
So basically, it gives you a function to run your monad into IO so you can call your special monad functions in IO . Then you return it an IO action and it will life it to your monad stack.

Why not use MonadUnliftIO?

Personally, I’m a bit hesitant because the ecosystem has still not adapted to it, although it’s strictly less general than MonadBaseControl. But if you find a good implementation, do leave a note and it shall be tried.

Special Mentions

MonadBaseControl in 5 minutes