MonadPlus IO isn't a monoid
Asked Answered
G

1

3

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?

Groff answered 11/8, 2019 at 6:26 Comment(1)
Note that fail is not intended as a general-purpose method to raise an error; it's an implementation detail for pattern-match failures in do notation and list comprehensions.Vatican
P
8

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.

Plimsoll answered 11/8, 2019 at 9:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.