Instance MonadPlus IO
is unique because mzero
throws:
Prelude Control.Monad> mzero
*** Exception: user error (mzero)
So accordingly, MonadPlus IO
implies that it is also intended for errors.
mzero
apparently serves as the identity element if the other action doesn't throw:
Prelude Control.Monad> mzero `mplus` return 0
0
Prelude Control.Monad> return 0 `mplus` mzero
0
But it doesn't when both actions throw:
Prelude Control.Monad> fail "Hello, world!" `mplus` mzero
*** Exception: user error (mzero)
Prelude Control.Monad> mzero `mplus` fail "Hello, world!"
*** Exception: user error (Hello, world!)
So MonadPlus IO
is not a monoid.
If it violates MonadPlus
laws when user intends errors, what is it actually intended for?
IO
under mplus
is a monoid relative to an equivalence class that identifies exceptions. Not that satisfying. An alternative approach might look like this:
m <|> n = m `catches`
[ Handler $ \ ~EmptyIO -> n
, Handler $ \ ~se@(SomeException _) ->
n `catch` \ ~EmptyIO -> throwIO se ]
The main problem with this approach is that handlers can stack up. When the first action fails, we can't just commit to the second action. A smaller issue is that there's no completely reliable way to determine whether an exception is synchronous (and should be rethrown using throwIO
) or asynchronous (in which case we need to rethrow it using throwTo
with our own thread ID). So that way lies messes.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With