14

I was just writing a quick bit of code, and I wanted to use the guard function in the IO Monad. However, there is no definition of MonadPlus for IO which means that we cannot use guard in IO land. I have seen an example of using the MabyeT transformer to use guard in the Maybe Monad and then lifting all of the IO actions but I do not really want to do that if I do not have to.

Some example of what I want might be:

handleFlags :: [Flag] -> IO ()
handleFlags flags = do
    when (Help `elem` flags) (putStrLn "Usage: program_name options...")
    guard (Help `elem` flags)
    ... do stuff ...
    return ()

I was wondering if there was a nice way to get a guard function (or something similar) in the IO Monad through a declaration for MonadPlus or otherwise. Or perhaps I am doing it wrong; is there a better way to write that help message in the function above? Thanks.

(P.S. I could use if-then-else statements but it seems to defeat the point somehow. Not to mention that for a lot of options it will result in a huge amount of nesting.)

duplode
  • 33,731
  • 7
  • 79
  • 150
Robert Massaioli
  • 13,379
  • 7
  • 57
  • 73
  • N.B.: Several GHC releases after this question was posted, `IO` was given `Alternative` and `MonadPlus` in the manner described by [C. A. McCann's answer](https://stackoverflow.com/a/4504804), caveats inclusive. See also: [*MonadPlus IO isn't a monoid*](https://stackoverflow.com/q/57447800). – duplode Feb 20 '21 at 04:10

3 Answers3

23

Consider the definition of MonadPlus:

class Monad m => MonadPlus m where
    mzero :: m a 
    mplus :: m a -> m a -> m a

How would you implement mzero for IO? A value of type IO a represents an IO computation that returns something of type a, so mzero would have to be an IO computation returning something of any possible type. Clearly, there's no way to conjure up a value for some arbitrary type, and unlike Maybe there's no "empty" constructor we can use, so mzero would necessarily represent an IO computation that never returns.

How do you write an IO computation that never returns? Either go into an infinite loop or throw a runtime error, basically. The former is of dubious utility, so the latter is what you're stuck with.

In short, to write an instance of MonadPlus for IO what you'd do is this: Have mzero throw a runtime exception, and have mplus evaluate its first argument while catching any exceptions thrown by mzero. If no exceptions are raised, return the result. If an exception is raised, evaluate mplus's second argument instead while ignoring exceptions.

That said, runtime exceptions are often considered undesirable, so I'd hesitate before going down that path. If you do want to do it that way (and don't mind increasing the chance that your program may crash at runtime) you'll find everything you need to implement the above in Control.Exception.

In practice, I'd probably either use the monad transformer approach if I wanted a lot of guarding on the result of evaluating monadic expressions, or if most of the conditionals depend on pure values provided as function arguments (which the flags in your example are) use the pattern guards as in @Anthony's answer.

C. A. McCann
  • 76,893
  • 19
  • 209
  • 302
  • That is an excellent answer and it let me understand why MonadPlus for IO really makes no sense without errors or infinite loops. – Robert Massaioli Dec 21 '10 at 23:57
  • 2
    @Robert Massaioli: Which isn't to say there's anything *wrong* with that. It's a perfectly correct `MonadPlus` instance, it's equivalent to how `MonadPlus` works for using `Either e` as an error monad, and I think such an instance for `IO` even exists in one of the monad transformer libraries. The only problem is that you have to be much more careful about catching errors, because unlike `MaybeT` or such, the exceptions can escape all the way up to `main`. – C. A. McCann Dec 22 '10 at 03:54
8

I do this sort of thing with guards.

handleFlags :: [Flag] -> IO ()
handleFlags flags
  | Help `elem` flags = putStrLn "Usage: program_name options..."
  | otherwise = return ()
Anthony
  • 3,771
  • 23
  • 16
  • 2
    Sometimes you know that you are on the right track (using guards) but then somebody points out the right idea (this) and you have a forehead slapping moment. Thanks, I used this and it is exactly what I wanted. Though I marked camccann as the answer because he showed why my idea was a bad one, what my alternatives are and then pointed to yours as what he would have done. – Robert Massaioli Dec 22 '10 at 00:00
  • 1
    Note that there are libraries like cmdargs that deal with such stuffs for you :) – Alp Mestanogullari Dec 22 '10 at 00:30
  • fwiw, cmdargs has too much magic for my liking. I prefer parseargs because it's all above-the-board. – sclv Dec 22 '10 at 00:46
  • I prefer this approach, especially if you have to check many options, but if you only have one option, then Control.Monad.when might help. (I know this is an old thread and all, it's just in case sb finds it, like I did) – Ivan Perez Feb 07 '12 at 19:05
0

There're functions precisely made for this: in Control.Monad, the functions when and its counterpart unless. Anthony's answer can be rewritten as such:

handleFlags :: [Flag] -> IO ()
handleFlags flags =
    when (Help `elem` flags) $ putStrLn "Usage: program_name options..."

specifications:

when :: (Applicative m) => Bool -> m () -> m ()
unless bool = when (not bool)

Link to docs on hackage.haskell.org

If more is needed, here's a link to another package, specifically monad-oriented and with several more utilities: Control.Monad.IfElse

icelake
  • 13
  • 3