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?
IOundermplusis a monoid relative to an equivalence class that identifies exceptions. Not that satisfying. An alternative approach might look like this: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 usingthrowTowith our own thread ID). So that way lies messes.