Skip to content

Add MonadThrow instance for Codensity#83

Merged
RyanGlScott merged 1 commit into
ekmett:masterfrom
kaol:codensity-catch
Sep 27, 2025
Merged

Add MonadThrow instance for Codensity#83
RyanGlScott merged 1 commit into
ekmett:masterfrom
kaol:codensity-catch

Conversation

@kaol

@kaol kaol commented Sep 26, 2025

Copy link
Copy Markdown
Contributor

The exceptions package is a transitive dependency of this package via semigroupoids so these instances can't be there.

@RyanGlScott

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

I think adding a MonadThrow instance is quite reasonable. For symmetry with the MonadTrans instance for ContT, I wonder if we should instead define Codensity's MonadThrow instance as:

instance MonadThrow m => MonadThrow (Codensity m) where
  throwM = lift . throwM

(I'm not sure if this implementation is meaningfully different from the one you've provided, but it wouldn't surprise me if there were a way to observe the difference between the two instances in certain corner cases.)

I'm more reluctant to add a MonadCatch instance, however. ContT (which Codensity is very similar to) intentionally does not have a MonadCatch instance because it can lead to extremely unusual behavior (see this link) where an exception handler may be run multiple times (or not at all) depending on how the continuation is used. Here is an adaptation of the example in the link that uses Codensity instead of ContT:

module Main where

import Control.Monad.Catch
import Control.Monad.Codensity
import Control.Monad.IO.Class

bracket_' :: MonadCatch m
          => m a  -- ^ computation to run first (\"acquire resource\")
          -> m b  -- ^ computation to run last when successful (\"release resource\")
          -> m b  -- ^ computation to run last when an exception occurs
          -> m c  -- ^ computation to run in-between
          -> m c  -- returns the value from the in-between computation
bracket_' before after afterEx thing = do
   _ <- before
   r <- thing `onException` afterEx
   _ <- after
   return r

f :: Codensity IO String
f = do
     bracket_' (say "acquired") (say "released-successful") (say "released-exception") (say "executed")
     say "Hello!"
     () <- error "error"
     return "success"
   where
     say = liftIO . putStrLn

main :: IO ()
main = runCodensity f (return . Right @String @String) >>= print

When run, this produces:

acquired
executed
released-successful
Hello!
released-exception
Bug.hs: error
CallStack (from HasCallStack):
  error, called at Bug.hs:23:12 in main:Main

Notice that the exception handler is run twice, once after a successful computation within bracket_', and once again for error, even though the call to error doesn't occur within a bracket_' at all.

@kaol

kaol commented Sep 27, 2025

Copy link
Copy Markdown
Contributor Author

I see, thank you for the explanation. I'm not seeing a way around this issue and I suspect it's a fundamental flaw with this approach. I could still edit this pull request to be only about MonadThrow if you like but MonadCatch was the one I was really interested in.

Luckily, in my case it was easy enough to move things around to have the exception be caught in a section where I was operating within the underlying monad (kaol/servant-wasm-client@b411fd6 if you are curious).

@RyanGlScott

Copy link
Copy Markdown
Collaborator

Up to you! I think a MonadThrow instance would be nice to have for symmetry with the ContT instance.

@kaol kaol changed the title Add MonadCatch and MonadThrow instances for Codensity Add MonadThrow instance for Codensity Sep 27, 2025
@kaol

kaol commented Sep 27, 2025

Copy link
Copy Markdown
Contributor Author

Okay, I force pushed a version with the MonadThrow instance only, for your consideration.

@RyanGlScott RyanGlScott left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@RyanGlScott RyanGlScott merged commit fb3d97f into ekmett:master Sep 27, 2025
11 checks passed
RyanGlScott added a commit that referenced this pull request Jan 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants